New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
inspect.Signature and default arguments #67187
Comments
inspect.Signature.bind() doesn't add values for parameters that are unspecified but have a default value. The documentation at https://docs.python.org/3/library/inspect.html#inspect.BoundArguments.arguments includes an example how to add default values, but that example doesn't work for the * and ** parameters. This patch adds a new method Signature.bind_with_defaults() that works like Signature.bind(), but includes parameters with a default value (and can handle values for the * and ** parameters). |
Can you give an example of what you say doesn't work? IIRC the reason this method isn't part of the API is because normally it isn't needed (the defaults are filled in when you do the actual call). What is your use case for needing the defaults to be pre-bound? |
The following doesn't work:: import inspect
def foo(*args, **kwargs):
return (args, kwargs) # Code from https://docs.python.org/3/library/inspect.html#inspect.BoundArguments.arguments to fill in the defaults sig = inspect.signature(foo)
ba = sig.bind()
for param in sig.parameters.values():
if param.name not in ba.arguments:
ba.arguments[param.name] = param.default
print(foo(*ba.args, **ba.kwargs)) instead it gives the following traceback:: Traceback (most recent call last):
File "sig_test.py", line 16, in <module>
print(foo(*ba.args, **ba.kwargs))
File "/Users/walter/.local/lib/python3.4/inspect.py", line 2246, in args
args.extend(arg)
TypeError: 'type' object is not iterable In my use case there isn't a call to a function implemented in Python. Instead I'm implementing a templating languages that supports defining a signature for a template. Calling the template binds the arguments and inside the template the variables simply are a dictionary. I.e. define the template like this: t = Template("<?print a+b?>", signature="a, b=23") Then you can call it like this: t(17) and inside the template the variables will be {"a": 17, "b": 23}. The signature argument in the Template constructor will be parsed into an inspect.Signature object and I'd like to use Signature.bind() to get the final variables dictionary. |
This is indeed a bit tricky. Your use case is pretty specialized (it doesn't involve any actual python functions), so I don't think by itself it argues for the inclusion of a _with_defaults method. The example fill-in in the docs fails because args and kwargs don't check for _empty as a possible value for the VAR_POSITIONAL (resp VAR_KEYWORD). So I think either we add that check to args and kwargs, or we add a _with_defaults method because filling in the defaults would no longer be a simple loop (and thus easy to get wrong). Let's see what Yury thinks. |
I think we should just fix the documentation, and update the code in example with a proper check: for param in sig.parameters.values():
if (param.name not in ba.arguments
and param.default is not param.empty):
ba.arguments[param.name] = param.default I'm -1 on adding '_with_defaults' method, because usually you don't need this (at least in my experience). |
New changeset 71c38c233e5c by Yury Selivanov in branch '3.4': New changeset 697adefaba6b by Yury Selivanov in branch 'default': |
Should this be closed now? |
Yes, let's close it. David and Walter, you're welcome to re-open the issue if you want to discuss it in more detail. |
I'm good with your solution, but I'm going to adjust the resolution by changing the component :) |
The updated code in the documentation still doesn't set the * and ** parameters. I would have preferred the following code: for param in sig.parameters.values():
if param.name not in ba.arguments:
if param.kind is inspect.Parameter.VAR_POSITIONAL:
default = ()
elif param.kind is inspect.Parameter.VAR_KEYWORD:
default = {}
else:
default = param.default
ba.arguments[param.name] = default |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: