classification
Title: inspect.Signature and default arguments
Type: enhancement Stage: resolved
Components: Documentation Versions: Python 3.4, Python 3.5
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: docs@python Nosy List: docs@python, doerwalter, python-dev, r.david.murray, terry.reedy, yselivanov
Priority: normal Keywords: patch

Created on 2014-12-04 20:14 by doerwalter, last changed 2014-12-09 13:33 by doerwalter. This issue is now closed.

Files
File name Uploaded Description Edit
signature-bind.diff doerwalter, 2014-12-04 20:14 review
Messages (10)
msg232150 - (view) Author: Walter Dörwald (doerwalter) * (Python committer) Date: 2014-12-04 20:14
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).
msg232151 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-12-04 20:20
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?
msg232152 - (view) Author: Walter Dörwald (doerwalter) * (Python committer) Date: 2014-12-04 20:43
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.
msg232153 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-12-04 21:28
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.
msg232154 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-12-04 22:05
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).
msg232175 - (view) Author: Roundup Robot (python-dev) (Python triager) Date: 2014-12-05 03:49
New changeset 71c38c233e5c by Yury Selivanov in branch '3.4':
docs.inspect: Fix BoundArguments example. Issue #22998.
https://hg.python.org/cpython/rev/71c38c233e5c

New changeset 697adefaba6b by Yury Selivanov in branch 'default':
docs.inspect: Fix BoundArguments example. Issue #22998.
https://hg.python.org/cpython/rev/697adefaba6b
msg232227 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-12-05 23:57
Should this be closed now?
msg232228 - (view) Author: Yury Selivanov (yselivanov) * (Python committer) Date: 2014-12-06 00:05
> 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.
msg232234 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-12-06 01:32
I'm good with your solution, but I'm going to adjust the resolution by changing the component :)
msg232375 - (view) Author: Walter Dörwald (doerwalter) * (Python committer) Date: 2014-12-09 13:33
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
History
Date User Action Args
2014-12-09 13:33:49doerwaltersetmessages: + msg232375
2014-12-06 01:32:18r.david.murraysetassignee: docs@python
components: + Documentation, - Library (Lib)

nosy: + docs@python
messages: + msg232234
resolution: wont fix -> fixed
stage: resolved
2014-12-06 00:05:53yselivanovsetstatus: open -> closed
resolution: wont fix
messages: + msg232228
2014-12-05 23:57:30terry.reedysetnosy: + terry.reedy
messages: + msg232227
2014-12-05 03:49:03python-devsetnosy: + python-dev
messages: + msg232175
2014-12-04 22:05:21yselivanovsetmessages: + msg232154
2014-12-04 21:28:34r.david.murraysetnosy: + yselivanov

messages: + msg232153
versions: + Python 3.4, Python 3.5
2014-12-04 20:43:18doerwaltersetmessages: + msg232152
2014-12-04 20:20:38r.david.murraysetnosy: + r.david.murray
messages: + msg232151
2014-12-04 20:14:01doerwaltercreate