@@ -1440,6 +1440,51 @@ def _get_user_defined_method(cls, method_name):
14401440 return meth
14411441
14421442
1443+ def _get_partial_signature (wrapped_sig , partial , extra_args = ()):
1444+ new_params = OrderedDict (wrapped_sig .parameters .items ())
1445+
1446+ partial_args = partial .args or ()
1447+ partial_keywords = partial .keywords or {}
1448+
1449+ if extra_args :
1450+ partial_args = extra_args + partial_args
1451+
1452+ try :
1453+ ba = wrapped_sig .bind_partial (* partial_args , ** partial_keywords )
1454+ except TypeError as ex :
1455+ msg = 'partial object {!r} has incorrect arguments' .format (partial )
1456+ raise ValueError (msg ) from ex
1457+
1458+ for arg_name , arg_value in ba .arguments .items ():
1459+ param = new_params [arg_name ]
1460+ if arg_name in partial_keywords :
1461+ # We set a new default value, because the following code
1462+ # is correct:
1463+ #
1464+ # >>> def foo(a): print(a)
1465+ # >>> print(partial(partial(foo, a=10), a=20)())
1466+ # 20
1467+ # >>> print(partial(partial(foo, a=10), a=20)(a=30))
1468+ # 30
1469+ #
1470+ # So, with 'partial' objects, passing a keyword argument is
1471+ # like setting a new default value for the corresponding
1472+ # parameter
1473+ #
1474+ # We also mark this parameter with '_partial_kwarg'
1475+ # flag. Later, in '_bind', the 'default' value of this
1476+ # parameter will be added to 'kwargs', to simulate
1477+ # the 'functools.partial' real call.
1478+ new_params [arg_name ] = param .replace (default = arg_value ,
1479+ _partial_kwarg = True )
1480+
1481+ elif (param .kind not in (_VAR_KEYWORD , _VAR_POSITIONAL ) and
1482+ not param ._partial_kwarg ):
1483+ new_params .pop (arg_name )
1484+
1485+ return wrapped_sig .replace (parameters = new_params .values ())
1486+
1487+
14431488def signature (obj ):
14441489 '''Get a signature object for the passed callable.'''
14451490
@@ -1470,50 +1515,32 @@ def signature(obj):
14701515 if sig is not None :
14711516 return sig
14721517
1518+ try :
1519+ partialmethod = obj ._partialmethod
1520+ except AttributeError :
1521+ pass
1522+ else :
1523+ # Unbound partialmethod (see functools.partialmethod)
1524+ # This means, that we need to calculate the signature
1525+ # as if it's a regular partial object, but taking into
1526+ # account that the first positional argument
1527+ # (usually `self`, or `cls`) will not be passed
1528+ # automatically (as for boundmethods)
1529+
1530+ wrapped_sig = signature (partialmethod .func )
1531+ sig = _get_partial_signature (wrapped_sig , partialmethod , (None ,))
1532+
1533+ first_wrapped_param = tuple (wrapped_sig .parameters .values ())[0 ]
1534+ new_params = (first_wrapped_param ,) + tuple (sig .parameters .values ())
1535+
1536+ return sig .replace (parameters = new_params )
1537+
14731538 if isinstance (obj , types .FunctionType ):
14741539 return Signature .from_function (obj )
14751540
14761541 if isinstance (obj , functools .partial ):
1477- sig = signature (obj .func )
1478-
1479- new_params = OrderedDict (sig .parameters .items ())
1480-
1481- partial_args = obj .args or ()
1482- partial_keywords = obj .keywords or {}
1483- try :
1484- ba = sig .bind_partial (* partial_args , ** partial_keywords )
1485- except TypeError as ex :
1486- msg = 'partial object {!r} has incorrect arguments' .format (obj )
1487- raise ValueError (msg ) from ex
1488-
1489- for arg_name , arg_value in ba .arguments .items ():
1490- param = new_params [arg_name ]
1491- if arg_name in partial_keywords :
1492- # We set a new default value, because the following code
1493- # is correct:
1494- #
1495- # >>> def foo(a): print(a)
1496- # >>> print(partial(partial(foo, a=10), a=20)())
1497- # 20
1498- # >>> print(partial(partial(foo, a=10), a=20)(a=30))
1499- # 30
1500- #
1501- # So, with 'partial' objects, passing a keyword argument is
1502- # like setting a new default value for the corresponding
1503- # parameter
1504- #
1505- # We also mark this parameter with '_partial_kwarg'
1506- # flag. Later, in '_bind', the 'default' value of this
1507- # parameter will be added to 'kwargs', to simulate
1508- # the 'functools.partial' real call.
1509- new_params [arg_name ] = param .replace (default = arg_value ,
1510- _partial_kwarg = True )
1511-
1512- elif (param .kind not in (_VAR_KEYWORD , _VAR_POSITIONAL ) and
1513- not param ._partial_kwarg ):
1514- new_params .pop (arg_name )
1515-
1516- return sig .replace (parameters = new_params .values ())
1542+ wrapped_sig = signature (obj .func )
1543+ return _get_partial_signature (wrapped_sig , obj )
15171544
15181545 sig = None
15191546 if isinstance (obj , type ):
0 commit comments