@@ -1518,7 +1518,8 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()):
1518
1518
on it.
1519
1519
"""
1520
1520
1521
- new_params = OrderedDict (wrapped_sig .parameters .items ())
1521
+ old_params = wrapped_sig .parameters
1522
+ new_params = OrderedDict (old_params .items ())
1522
1523
1523
1524
partial_args = partial .args or ()
1524
1525
partial_keywords = partial .keywords or {}
@@ -1532,32 +1533,57 @@ def _signature_get_partial(wrapped_sig, partial, extra_args=()):
1532
1533
msg = 'partial object {!r} has incorrect arguments' .format (partial )
1533
1534
raise ValueError (msg ) from ex
1534
1535
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 )
1561
1587
1562
1588
return wrapped_sig .replace (parameters = new_params .values ())
1563
1589
@@ -2103,7 +2129,7 @@ class Parameter:
2103
2129
`Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`.
2104
2130
"""
2105
2131
2106
- __slots__ = ('_name' , '_kind' , '_default' , '_annotation' , '_partial_kwarg' )
2132
+ __slots__ = ('_name' , '_kind' , '_default' , '_annotation' )
2107
2133
2108
2134
POSITIONAL_ONLY = _POSITIONAL_ONLY
2109
2135
POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD
@@ -2113,8 +2139,7 @@ class Parameter:
2113
2139
2114
2140
empty = _empty
2115
2141
2116
- def __init__ (self , name , kind , * , default = _empty , annotation = _empty ,
2117
- _partial_kwarg = False ):
2142
+ def __init__ (self , name , kind , * , default = _empty , annotation = _empty ):
2118
2143
2119
2144
if kind not in (_POSITIONAL_ONLY , _POSITIONAL_OR_KEYWORD ,
2120
2145
_VAR_POSITIONAL , _KEYWORD_ONLY , _VAR_KEYWORD ):
@@ -2139,17 +2164,13 @@ def __init__(self, name, kind, *, default=_empty, annotation=_empty,
2139
2164
2140
2165
self ._name = name
2141
2166
2142
- self ._partial_kwarg = _partial_kwarg
2143
-
2144
2167
def __reduce__ (self ):
2145
2168
return (type (self ),
2146
2169
(self ._name , self ._kind ),
2147
- {'_partial_kwarg' : self ._partial_kwarg ,
2148
- '_default' : self ._default ,
2170
+ {'_default' : self ._default ,
2149
2171
'_annotation' : self ._annotation })
2150
2172
2151
2173
def __setstate__ (self , state ):
2152
- self ._partial_kwarg = state ['_partial_kwarg' ]
2153
2174
self ._default = state ['_default' ]
2154
2175
self ._annotation = state ['_annotation' ]
2155
2176
@@ -2169,8 +2190,8 @@ def annotation(self):
2169
2190
def kind (self ):
2170
2191
return self ._kind
2171
2192
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 ):
2174
2195
"""Creates a customized copy of the Parameter."""
2175
2196
2176
2197
if name is _void :
@@ -2185,11 +2206,7 @@ def replace(self, *, name=_void, kind=_void, annotation=_void,
2185
2206
if default is _void :
2186
2207
default = self ._default
2187
2208
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 )
2193
2210
2194
2211
def __str__ (self ):
2195
2212
kind = self .kind
@@ -2215,17 +2232,6 @@ def __repr__(self):
2215
2232
id (self ), self )
2216
2233
2217
2234
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
2229
2235
return (issubclass (other .__class__ , Parameter ) and
2230
2236
self ._name == other ._name and
2231
2237
self ._kind == other ._kind and
@@ -2265,12 +2271,7 @@ def signature(self):
2265
2271
def args (self ):
2266
2272
args = []
2267
2273
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 ):
2274
2275
break
2275
2276
2276
2277
try :
@@ -2295,8 +2296,7 @@ def kwargs(self):
2295
2296
kwargs_started = False
2296
2297
for param_name , param in self ._signature .parameters .items ():
2297
2298
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 ):
2300
2300
kwargs_started = True
2301
2301
else :
2302
2302
if param_name not in self .arguments :
@@ -2378,18 +2378,14 @@ def __init__(self, parameters=None, *, return_annotation=_empty,
2378
2378
name = param .name
2379
2379
2380
2380
if kind < top_kind :
2381
- msg = 'wrong parameter order: {} before {}'
2381
+ msg = 'wrong parameter order: {!r } before {!r }'
2382
2382
msg = msg .format (top_kind , kind )
2383
2383
raise ValueError (msg )
2384
2384
elif kind > top_kind :
2385
2385
kind_defaults = False
2386
2386
top_kind = kind
2387
2387
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 ):
2393
2389
if param .default is _empty :
2394
2390
if kind_defaults :
2395
2391
# No default for this parameter, but the
@@ -2570,15 +2566,6 @@ def _bind(self, args, kwargs, *, partial=False):
2570
2566
parameters_ex = ()
2571
2567
arg_vals = iter (args )
2572
2568
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
-
2582
2569
while True :
2583
2570
# Let's iterate through the positional arguments and corresponding
2584
2571
# parameters
0 commit comments