summaryrefslogtreecommitdiffstats
path: root/Lib/inspect.py
diff options
context:
space:
mode:
authorYury Selivanov <yselivanov@sprymix.com>2014-04-08 15:28:02 (GMT)
committerYury Selivanov <yselivanov@sprymix.com>2014-04-08 15:28:02 (GMT)
commit0fceaf45e2f6695685785e18852902740210a128 (patch)
tree091cf01bb32ba468714abd33b2415ecadbb9a198 /Lib/inspect.py
parent7ddf3eba90576f51c14b8da0df2970589761b78e (diff)
downloadcpython-0fceaf45e2f6695685785e18852902740210a128.zip
cpython-0fceaf45e2f6695685785e18852902740210a128.tar.gz
cpython-0fceaf45e2f6695685785e18852902740210a128.tar.bz2
inspect.signautre: Fix functools.partial support. Issue #21117
Diffstat (limited to 'Lib/inspect.py')
-rw-r--r--Lib/inspect.py135
1 files changed, 62 insertions, 73 deletions
diff --git a/Lib/inspect.py b/Lib/inspect.py
index 9f9a600..4c3e33d 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1511,7 +1511,8 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()):
# look like after applying a 'functools.partial' object (or alike)
# on it.
- new_params = OrderedDict(wrapped_sig.parameters.items())
+ old_params = wrapped_sig.parameters
+ new_params = OrderedDict(old_params.items())
partial_args = partial.args or ()
partial_keywords = partial.keywords or {}
@@ -1525,32 +1526,57 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()):
msg = 'partial object {!r} has incorrect arguments'.format(partial)
raise ValueError(msg) from ex
- for arg_name, arg_value in ba.arguments.items():
- param = new_params[arg_name]
- if arg_name in partial_keywords:
- # We set a new default value, because the following code
- # is correct:
- #
- # >>> def foo(a): print(a)
- # >>> print(partial(partial(foo, a=10), a=20)())
- # 20
- # >>> print(partial(partial(foo, a=10), a=20)(a=30))
- # 30
- #
- # So, with 'partial' objects, passing a keyword argument is
- # like setting a new default value for the corresponding
- # parameter
- #
- # We also mark this parameter with '_partial_kwarg'
- # flag. Later, in '_bind', the 'default' value of this
- # parameter will be added to 'kwargs', to simulate
- # the 'functools.partial' real call.
- new_params[arg_name] = param.replace(default=arg_value,
- _partial_kwarg=True)
-
- elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and
- not param._partial_kwarg):
- new_params.pop(arg_name)
+
+ transform_to_kwonly = False
+ for param_name, param in old_params.items():
+ try:
+ arg_value = ba.arguments[param_name]
+ except KeyError:
+ pass
+ else:
+ if param.kind is _POSITIONAL_ONLY:
+ # If positional-only parameter is bound by partial,
+ # it effectively disappears from the signature
+ new_params.pop(param_name)
+ continue
+
+ if param.kind is _POSITIONAL_OR_KEYWORD:
+ if param_name in partial_keywords:
+ # This means that this parameter, and all parameters
+ # after it should be keyword-only (and var-positional
+ # should be removed). Here's why. Consider the following
+ # function:
+ # foo(a, b, *args, c):
+ # pass
+ #
+ # "partial(foo, a='spam')" will have the following
+ # signature: "(*, a='spam', b, c)". Because attempting
+ # to call that partial with "(10, 20)" arguments will
+ # raise a TypeError, saying that "a" argument received
+ # multiple values.
+ transform_to_kwonly = True
+ # Set the new default value
+ new_params[param_name] = param.replace(default=arg_value)
+ else:
+ # was passed as a positional argument
+ new_params.pop(param.name)
+ continue
+
+ if param.kind is _KEYWORD_ONLY:
+ # Set the new default value
+ new_params[param_name] = param.replace(default=arg_value)
+
+ if transform_to_kwonly:
+ assert param.kind is not _POSITIONAL_ONLY
+
+ if param.kind is _POSITIONAL_OR_KEYWORD:
+ new_param = new_params[param_name].replace(kind=_KEYWORD_ONLY)
+ new_params[param_name] = new_param
+ new_params.move_to_end(param_name)
+ elif param.kind in (_KEYWORD_ONLY, _VAR_KEYWORD):
+ new_params.move_to_end(param_name)
+ elif param.kind is _VAR_POSITIONAL:
+ new_params.pop(param.name)
return wrapped_sig.replace(parameters=new_params.values())
@@ -2069,7 +2095,7 @@ class Parameter:
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
'''
- __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg')
+ __slots__ = ('_name', '_kind', '_default', '_annotation')
POSITIONAL_ONLY = _POSITIONAL_ONLY
POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
@@ -2079,8 +2105,7 @@ class Parameter:
empty = _empty
- def __init__(self, name, kind, *, default=_empty, annotation=_empty,
- _partial_kwarg=False):
+ def __init__(self, name, kind, *, default=_empty, annotation=_empty):
if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD,
_VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD):
@@ -2105,8 +2130,6 @@ class Parameter:
self._name = name
- self._partial_kwarg = _partial_kwarg
-
@property
def name(self):
return self._name
@@ -2123,8 +2146,8 @@ class Parameter:
def kind(self):
return self._kind
- def replace(self, *, name=_void, kind=_void, annotation=_void,
- default=_void, _partial_kwarg=_void):
+ def replace(self, *, name=_void, kind=_void,
+ annotation=_void, default=_void):
'''Creates a customized copy of the Parameter.'''
if name is _void:
@@ -2139,11 +2162,7 @@ class Parameter:
if default is _void:
default = self._default
- if _partial_kwarg is _void:
- _partial_kwarg = self._partial_kwarg
-
- return type(self)(name, kind, default=default, annotation=annotation,
- _partial_kwarg=_partial_kwarg)
+ return type(self)(name, kind, default=default, annotation=annotation)
def __str__(self):
kind = self.kind
@@ -2169,17 +2188,6 @@ class Parameter:
id(self), self.name)
def __eq__(self, other):
- # NB: We deliberately do not compare '_partial_kwarg' attributes
- # here. Imagine we have a following situation:
- #
- # def foo(a, b=1): pass
- # def bar(a, b): pass
- # bar2 = functools.partial(bar, b=1)
- #
- # For the above scenario, signatures for `foo` and `bar2` should
- # be equal. '_partial_kwarg' attribute is an internal flag, to
- # distinguish between keyword parameters with defaults and
- # keyword parameters which got their defaults from functools.partial
return (issubclass(other.__class__, Parameter) and
self._name == other._name and
self._kind == other._kind and
@@ -2219,12 +2227,7 @@ class BoundArguments:
def args(self):
args = []
for param_name, param in self._signature.parameters.items():
- if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
- param._partial_kwarg):
- # Keyword arguments mapped by 'functools.partial'
- # (Parameter._partial_kwarg is True) are mapped
- # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
- # KEYWORD_ONLY
+ if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
break
try:
@@ -2249,8 +2252,7 @@ class BoundArguments:
kwargs_started = False
for param_name, param in self._signature.parameters.items():
if not kwargs_started:
- if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or
- param._partial_kwarg):
+ if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY):
kwargs_started = True
else:
if param_name not in self.arguments:
@@ -2332,18 +2334,14 @@ class Signature:
name = param.name
if kind < top_kind:
- msg = 'wrong parameter order: {} before {}'
+ msg = 'wrong parameter order: {!r} before {!r}'
msg = msg.format(top_kind, kind)
raise ValueError(msg)
elif kind > top_kind:
kind_defaults = False
top_kind = kind
- if (kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD) and
- not param._partial_kwarg):
- # If we have a positional-only or positional-or-keyword
- # parameter, that does not have its default value set
- # by 'functools.partial' or other "partial" signature:
+ if kind in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD):
if param.default is _empty:
if kind_defaults:
# No default for this parameter, but the
@@ -2518,15 +2516,6 @@ class Signature:
parameters_ex = ()
arg_vals = iter(args)
- if partial:
- # Support for binding arguments to 'functools.partial' objects.
- # See 'functools.partial' case in 'signature()' implementation
- # for details.
- for param_name, param in self.parameters.items():
- if (param._partial_kwarg and param_name not in kwargs):
- # Simulating 'functools.partial' behavior
- kwargs[param_name] = param.default
-
while True:
# Let's iterate through the positional arguments and corresponding
# parameters