I have a function with keyword-only parameters. In the function, the function needs access to the keyword arguments passed into it, in the order they were listed on the call site, and only those which were passed in.
I looked at inspect.getargvalues(inspect.currentframe()) but it returns the complete dictionary of parameters, eg. p0 and p1 whereas I would like a way to see that only p1 was passed in.
A solution would likely use the **kwargs syntax as the only parameter and do default value processing in the function body but the existing code base does not use that. Is there a way ?
On the face of it, I would suggest that some kind of ‘glue code’ would be your best bet; code that takes the return values of a function that is not within your control and then transforms said into something that can be passed to the function that is within your control: that is of course, if I’m understanding your post correctly.
As a fun coincidence, a wrapper I posted elsewhere gets close to the solution here. Use a decorator that uses functools.wraps to maintain the signature, and you can then write a solution using *args and **kwargs.
I realize that the description was rather short and may need more context. Here is a somewhat more elaborate example building on above, with expected return values:
def func(p0=0, p1=1):
callerDict = getcallargumentsonly(perhapsCurrentFrame)
#do something depending how function was called
#process keys in callerDict in order
#process all remaining parameters not processed yet
return callerDict
>>> func(p1=2)
{'p1': 2}
>>> func()
{}
>>> func(p0=3, p1=4)
{'p0': 3, 'p1': 4}
>>> func(p1=4, p0=3)
{'p1': 4, 'p0': 3}
The code base is autogenerated from a stylesheet which I do not want to change too much. I was hoping there is a way without resorting to **kwargs (in a wrapper perhaps) but there may not be (except perhaps for getting the source code of the calling frame with the calling line and do manual parsing of the arguments…)
It is not possible to change the API from a list of keyword arguments to just a single dict, unfortunately, because that is the API.
I guess I’m unclear on what flexibility you do have. Where can you add code? Does it have to be inside the function body?
functools.wraps has the nice property that it updates the signature to look like the original function. So if you can add a decorator I think you can do this…really you just want to look at kwargs, and rely on the ordering to be preserved.
I don’t really see why you’d not use the **kwargs that the API provides. You can then unpack them, repack them, in fact do what you whatever you need to do with them, before passing control to another function. It matters not if these functions are ones that you’ve written or not, so long as you know what you need to pass to them, in order to have them return something that is of use.
Yes, it seems like this is necessary. I was just hoping there is a more direct way to get the arguments of the function call while using named parameters with defaults in the function signature, along the lines of inspect.getargvalues().
Hm, if the arguments exclude None (or some kind of well-defined sentinel object), couldn’t you simply do:
def func(p0=None, p1=None):
return dict((k, v) for (k, v) in locals().items() if v is not None)
If any value, including None, is allowed, then you could define a special marker class or enum and use that instead of None as default argument.
Or should the order in the returned dict also mirror the order in the actual call? Do func(p1=0, p0=1) and func(p0=1, p1=0) need to return dicts with a different creation order? Apart from the ordering of keys, the returned dicts would already be identical. If you really need the call order, then I also don’t see a way around using an extra decorator:
Thanks, the order of the returned dict should mirror the order in the actual call, see example. I know this is an unusual requirement but it turned out that the order is important. Also, the defaults should not be sentinel values but actual default values. That would be one the main points of using named arguments in the first place.
It seems that the information of how a function is exactly called is lost, not accessible from within a function with named arguments.
I think my extra solution may solve those issues? (Also, None is no longer really a sentinel there. So, any values can be used, also as defaults in f).