@@ -1518,7 +1518,8 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()):
15181518 on it.
15191519 """
15201520
1521- new_params = OrderedDict (wrapped_sig .parameters .items ())
1521+ old_params = wrapped_sig .parameters
1522+ new_params = OrderedDict (old_params .items ())
15221523
15231524 partial_args = partial .args or ()
15241525 partial_keywords = partial .keywords or {}
@@ -1532,32 +1533,57 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()):
15321533 msg = 'partial object {!r} has incorrect arguments' .format (partial )
15331534 raise ValueError (msg ) from ex
15341535
1535- for arg_name , arg_value in ba .arguments .items ():
1536- param = new_params [arg_name ]
1537- if arg_name in partial_keywords :
1538- # We set a new default value, because the following code
1539- # is correct:
1540- #
1541- # >>> def foo(a): print(a)
1542- # >>> print(partial(partial(foo, a=10), a=20)())
1543- # 20
1544- # >>> print(partial(partial(foo, a=10), a=20)(a=30))
1545- # 30
1546- #
1547- # So, with 'partial' objects, passing a keyword argument is
1548- # like setting a new default value for the corresponding
1549- # parameter
1550- #
1551- # We also mark this parameter with '_partial_kwarg'
1552- # flag. Later, in '_bind', the 'default' value of this
1553- # parameter will be added to 'kwargs', to simulate
1554- # the 'functools.partial' real call.
1555- new_params [arg_name ] = param .replace (default = arg_value ,
1556- _partial_kwarg = True )
1557-
1558- elif (param .kind not in (_VAR_KEYWORD , _VAR_POSITIONAL ) and
1559- not param ._partial_kwarg ):
1560- new_params .pop (arg_name )
1536+
1537+ transform_to_kwonly = False
1538+ for param_name , param in old_params .items ():
1539+ try :
1540+ arg_value = ba .arguments [param_name ]
1541+ except KeyError :
1542+ pass
1543+ else :
1544+ if param .kind is _POSITIONAL_ONLY :
1545+ # If positional-only parameter is bound by partial,
1546+ # it effectively disappears from the signature
1547+ new_params .pop (param_name )
1548+ continue
1549+
1550+ if param .kind is _POSITIONAL_OR_KEYWORD :
1551+ if param_name in partial_keywords :
1552+ # This means that this parameter, and all parameters
1553+ # after it should be keyword-only (and var-positional
1554+ # should be removed). Here's why. Consider the following
1555+ # function:
1556+ # foo(a, b, *args, c):
1557+ # pass
1558+ #
1559+ # "partial(foo, a='spam')" will have the following
1560+ # signature: "(*, a='spam', b, c)". Because attempting
1561+ # to call that partial with "(10, 20)" arguments will
1562+ # raise a TypeError, saying that "a" argument received
1563+ # multiple values.
1564+ transform_to_kwonly = True
1565+ # Set the new default value
1566+ new_params [param_name ] = param .replace (default = arg_value )
1567+ else :
1568+ # was passed as a positional argument
1569+ new_params .pop (param .name )
1570+ continue
1571+
1572+ if param .kind is _KEYWORD_ONLY :
1573+ # Set the new default value
1574+ new_params [param_name ] = param .replace (default = arg_value )
1575+
1576+ if transform_to_kwonly :
1577+ assert param .kind is not _POSITIONAL_ONLY
1578+
1579+ if param .kind is _POSITIONAL_OR_KEYWORD :
1580+ new_param = new_params [param_name ].replace (kind = _KEYWORD_ONLY )
1581+ new_params [param_name ] = new_param
1582+ new_params .move_to_end (param_name )
1583+ elif param .kind in (_KEYWORD_ONLY , _VAR_KEYWORD ):
1584+ new_params .move_to_end (param_name )
1585+ elif param .kind is _VAR_POSITIONAL :
1586+ new_params .pop (param .name )
15611587
15621588 return wrapped_sig .replace (parameters = new_params .values ())
15631589
@@ -2103,7 +2129,7 @@ class Parameter:
21032129 `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
21042130 """
21052131
2106- __slots__ = ('_name' , '_kind' , '_default' , '_annotation' , '_partial_kwarg' )
2132+ __slots__ = ('_name' , '_kind' , '_default' , '_annotation' )
21072133
21082134 POSITIONAL_ONLY = _POSITIONAL_ONLY
21092135 POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
@@ -2113,8 +2139,7 @@ class Parameter:
21132139
21142140 empty = _empty
21152141
2116- def __init__ (self , name , kind , * , default = _empty , annotation = _empty ,
2117- _partial_kwarg = False ):
2142+ def __init__ (self , name , kind , * , default = _empty , annotation = _empty ):
21182143
21192144 if kind not in (_POSITIONAL_ONLY , _POSITIONAL_OR_KEYWORD ,
21202145 _VAR_POSITIONAL , _KEYWORD_ONLY , _VAR_KEYWORD ):
@@ -2139,17 +2164,13 @@ def __init__(self, name, kind, *, default=_empty, annotation=_empty,
21392164
21402165 self ._name = name
21412166
2142- self ._partial_kwarg = _partial_kwarg
2143-
21442167 def __reduce__ (self ):
21452168 return (type (self ),
21462169 (self ._name , self ._kind ),
2147- {'_partial_kwarg' : self ._partial_kwarg ,
2148- '_default' : self ._default ,
2170+ {'_default' : self ._default ,
21492171 '_annotation' : self ._annotation })
21502172
21512173 def __setstate__ (self , state ):
2152- self ._partial_kwarg = state ['_partial_kwarg' ]
21532174 self ._default = state ['_default' ]
21542175 self ._annotation = state ['_annotation' ]
21552176
@@ -2169,8 +2190,8 @@ def annotation(self):
21692190 def kind (self ):
21702191 return self ._kind
21712192
2172- def replace (self , * , name = _void , kind = _void , annotation = _void ,
2173- default = _void , _partial_kwarg = _void ):
2193+ def replace (self , * , name = _void , kind = _void ,
2194+ annotation = _void , default = _void ):
21742195 """Creates a customized copy of the Parameter."""
21752196
21762197 if name is _void :
@@ -2185,11 +2206,7 @@ def replace(self, *, name=_void, kind=_void, annotation=_void,
21852206 if default is _void :
21862207 default = self ._default
21872208
2188- if _partial_kwarg is _void :
2189- _partial_kwarg = self ._partial_kwarg
2190-
2191- return type (self )(name , kind , default = default , annotation = annotation ,
2192- _partial_kwarg = _partial_kwarg )
2209+ return type (self )(name , kind , default = default , annotation = annotation )
21932210
21942211 def __str__ (self ):
21952212 kind = self .kind
@@ -2215,17 +2232,6 @@ def __repr__(self):
22152232 id (self ), self )
22162233
22172234 def __eq__ (self , other ):
2218- # NB: We deliberately do not compare '_partial_kwarg' attributes
2219- # here. Imagine we have a following situation:
2220- #
2221- # def foo(a, b=1): pass
2222- # def bar(a, b): pass
2223- # bar2 = functools.partial(bar, b=1)
2224- #
2225- # For the above scenario, signatures for `foo` and `bar2` should
2226- # be equal. '_partial_kwarg' attribute is an internal flag, to
2227- # distinguish between keyword parameters with defaults and
2228- # keyword parameters which got their defaults from functools.partial
22292235 return (issubclass (other .__class__ , Parameter ) and
22302236 self ._name == other ._name and
22312237 self ._kind == other ._kind and
@@ -2265,12 +2271,7 @@ def signature(self):
22652271 def args (self ):
22662272 args = []
22672273 for param_name , param in self ._signature .parameters .items ():
2268- if (param .kind in (_VAR_KEYWORD , _KEYWORD_ONLY ) or
2269- param ._partial_kwarg ):
2270- # Keyword arguments mapped by 'functools.partial'
2271- # (Parameter._partial_kwarg is True) are mapped
2272- # in 'BoundArguments.kwargs', along with VAR_KEYWORD &
2273- # KEYWORD_ONLY
2274+ if param .kind in (_VAR_KEYWORD , _KEYWORD_ONLY ):
22742275 break
22752276
22762277 try :
@@ -2295,8 +2296,7 @@ def kwargs(self):
22952296 kwargs_started = False
22962297 for param_name , param in self ._signature .parameters .items ():
22972298 if not kwargs_started :
2298- if (param .kind in (_VAR_KEYWORD , _KEYWORD_ONLY ) or
2299- param ._partial_kwarg ):
2299+ if param .kind in (_VAR_KEYWORD , _KEYWORD_ONLY ):
23002300 kwargs_started = True
23012301 else :
23022302 if param_name not in self .arguments :
@@ -2378,18 +2378,14 @@ def __init__(self, parameters=None, *, return_annotation=_empty,
23782378 name = param .name
23792379
23802380 if kind < top_kind :
2381- msg = 'wrong parameter order: {} before {}'
2381+ msg = 'wrong parameter order: {!r } before {!r }'
23822382 msg = msg .format (top_kind , kind )
23832383 raise ValueError (msg )
23842384 elif kind > top_kind :
23852385 kind_defaults = False
23862386 top_kind = kind
23872387
2388- if (kind in (_POSITIONAL_ONLY , _POSITIONAL_OR_KEYWORD ) and
2389- not param ._partial_kwarg ):
2390- # If we have a positional-only or positional-or-keyword
2391- # parameter, that does not have its default value set
2392- # by 'functools.partial' or other "partial" signature:
2388+ if kind in (_POSITIONAL_ONLY , _POSITIONAL_OR_KEYWORD ):
23932389 if param .default is _empty :
23942390 if kind_defaults :
23952391 # No default for this parameter, but the
@@ -2570,15 +2566,6 @@ def _bind(self, args, kwargs, *, partial=False):
25702566 parameters_ex = ()
25712567 arg_vals = iter (args )
25722568
2573- if partial :
2574- # Support for binding arguments to 'functools.partial' objects.
2575- # See 'functools.partial' case in 'signature()' implementation
2576- # for details.
2577- for param_name , param in self .parameters .items ():
2578- if (param ._partial_kwarg and param_name not in kwargs ):
2579- # Simulating 'functools.partial' behavior
2580- kwargs [param_name ] = param .default
2581-
25822569 while True :
25832570 # Let's iterate through the positional arguments and corresponding
25842571 # parameters
0 commit comments