inspect.Signature: Add 'Signature.from_callable' classmethod. Closes #17373
This commit is contained in:
parent
a5d63dd7b8
commit
da39645ad3
|
@ -506,6 +506,18 @@ function.
|
||||||
>>> str(new_sig)
|
>>> str(new_sig)
|
||||||
"(a, b) -> 'new return anno'"
|
"(a, b) -> 'new return anno'"
|
||||||
|
|
||||||
|
.. classmethod:: Signature.from_callable(obj)
|
||||||
|
|
||||||
|
Return a :class:`Signature` (or its subclass) object for a given callable
|
||||||
|
``obj``. This method simplifies subclassing of :class:`Signature`:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
class MySignature(Signature):
|
||||||
|
pass
|
||||||
|
sig = MySignature.from_callable(min)
|
||||||
|
assert isinstance(sig, MySignature)
|
||||||
|
|
||||||
|
|
||||||
.. class:: Parameter(name, kind, \*, default=Parameter.empty, annotation=Parameter.empty)
|
.. class:: Parameter(name, kind, \*, default=Parameter.empty, annotation=Parameter.empty)
|
||||||
|
|
||||||
|
|
|
@ -140,6 +140,10 @@ Improved Modules
|
||||||
* :class:`inspect.Signature` and :class:`inspect.Parameter` are now
|
* :class:`inspect.Signature` and :class:`inspect.Parameter` are now
|
||||||
picklable (contributed by Yury Selivanov in :issue:`20726`).
|
picklable (contributed by Yury Selivanov in :issue:`20726`).
|
||||||
|
|
||||||
|
* New class method :meth:`inspect.Signature.from_callable`, which makes
|
||||||
|
subclassing of :class:`~inspect.Signature` easier (contributed
|
||||||
|
by Yury Selivanov and Eric Snow in :issue:`17373`).
|
||||||
|
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -969,7 +969,8 @@ def getfullargspec(func):
|
||||||
|
|
||||||
sig = _signature_internal(func,
|
sig = _signature_internal(func,
|
||||||
follow_wrapper_chains=False,
|
follow_wrapper_chains=False,
|
||||||
skip_bound_arg=False)
|
skip_bound_arg=False,
|
||||||
|
sigcls=Signature)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# Most of the times 'signature' will raise ValueError.
|
# Most of the times 'signature' will raise ValueError.
|
||||||
# But, it can also raise AttributeError, and, maybe something
|
# But, it can also raise AttributeError, and, maybe something
|
||||||
|
@ -1861,7 +1862,10 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
|
||||||
return _signature_fromstr(cls, func, s, skip_bound_arg)
|
return _signature_fromstr(cls, func, s, skip_bound_arg)
|
||||||
|
|
||||||
|
|
||||||
def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
|
def _signature_internal(obj, *,
|
||||||
|
follow_wrapper_chains=True,
|
||||||
|
skip_bound_arg=True,
|
||||||
|
sigcls):
|
||||||
|
|
||||||
if not callable(obj):
|
if not callable(obj):
|
||||||
raise TypeError('{!r} is not a callable object'.format(obj))
|
raise TypeError('{!r} is not a callable object'.format(obj))
|
||||||
|
@ -1869,9 +1873,12 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
|
||||||
if isinstance(obj, types.MethodType):
|
if isinstance(obj, types.MethodType):
|
||||||
# In this case we skip the first parameter of the underlying
|
# In this case we skip the first parameter of the underlying
|
||||||
# function (usually `self` or `cls`).
|
# function (usually `self` or `cls`).
|
||||||
sig = _signature_internal(obj.__func__,
|
sig = _signature_internal(
|
||||||
follow_wrapper_chains,
|
obj.__func__,
|
||||||
skip_bound_arg)
|
follow_wrapper_chains=follow_wrapper_chains,
|
||||||
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
sigcls=sigcls)
|
||||||
|
|
||||||
if skip_bound_arg:
|
if skip_bound_arg:
|
||||||
return _signature_bound_method(sig)
|
return _signature_bound_method(sig)
|
||||||
else:
|
else:
|
||||||
|
@ -1902,9 +1909,12 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
|
||||||
# (usually `self`, or `cls`) will not be passed
|
# (usually `self`, or `cls`) will not be passed
|
||||||
# automatically (as for boundmethods)
|
# automatically (as for boundmethods)
|
||||||
|
|
||||||
wrapped_sig = _signature_internal(partialmethod.func,
|
wrapped_sig = _signature_internal(
|
||||||
follow_wrapper_chains,
|
partialmethod.func,
|
||||||
skip_bound_arg)
|
follow_wrapper_chains=follow_wrapper_chains,
|
||||||
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
sigcls=sigcls)
|
||||||
|
|
||||||
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
|
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
|
||||||
|
|
||||||
first_wrapped_param = tuple(wrapped_sig.parameters.values())[0]
|
first_wrapped_param = tuple(wrapped_sig.parameters.values())[0]
|
||||||
|
@ -1915,16 +1925,18 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
|
||||||
if isfunction(obj) or _signature_is_functionlike(obj):
|
if isfunction(obj) or _signature_is_functionlike(obj):
|
||||||
# If it's a pure Python function, or an object that is duck type
|
# If it's a pure Python function, or an object that is duck type
|
||||||
# of a Python function (Cython functions, for instance), then:
|
# of a Python function (Cython functions, for instance), then:
|
||||||
return Signature.from_function(obj)
|
return sigcls.from_function(obj)
|
||||||
|
|
||||||
if _signature_is_builtin(obj):
|
if _signature_is_builtin(obj):
|
||||||
return _signature_from_builtin(Signature, obj,
|
return _signature_from_builtin(sigcls, obj,
|
||||||
skip_bound_arg=skip_bound_arg)
|
skip_bound_arg=skip_bound_arg)
|
||||||
|
|
||||||
if isinstance(obj, functools.partial):
|
if isinstance(obj, functools.partial):
|
||||||
wrapped_sig = _signature_internal(obj.func,
|
wrapped_sig = _signature_internal(
|
||||||
follow_wrapper_chains,
|
obj.func,
|
||||||
skip_bound_arg)
|
follow_wrapper_chains=follow_wrapper_chains,
|
||||||
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
sigcls=sigcls)
|
||||||
return _signature_get_partial(wrapped_sig, obj)
|
return _signature_get_partial(wrapped_sig, obj)
|
||||||
|
|
||||||
sig = None
|
sig = None
|
||||||
|
@ -1935,23 +1947,29 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
|
||||||
# in its metaclass
|
# in its metaclass
|
||||||
call = _signature_get_user_defined_method(type(obj), '__call__')
|
call = _signature_get_user_defined_method(type(obj), '__call__')
|
||||||
if call is not None:
|
if call is not None:
|
||||||
sig = _signature_internal(call,
|
sig = _signature_internal(
|
||||||
follow_wrapper_chains,
|
call,
|
||||||
skip_bound_arg)
|
follow_wrapper_chains=follow_wrapper_chains,
|
||||||
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
sigcls=sigcls)
|
||||||
else:
|
else:
|
||||||
# Now we check if the 'obj' class has a '__new__' method
|
# Now we check if the 'obj' class has a '__new__' method
|
||||||
new = _signature_get_user_defined_method(obj, '__new__')
|
new = _signature_get_user_defined_method(obj, '__new__')
|
||||||
if new is not None:
|
if new is not None:
|
||||||
sig = _signature_internal(new,
|
sig = _signature_internal(
|
||||||
follow_wrapper_chains,
|
new,
|
||||||
skip_bound_arg)
|
follow_wrapper_chains=follow_wrapper_chains,
|
||||||
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
sigcls=sigcls)
|
||||||
else:
|
else:
|
||||||
# Finally, we should have at least __init__ implemented
|
# Finally, we should have at least __init__ implemented
|
||||||
init = _signature_get_user_defined_method(obj, '__init__')
|
init = _signature_get_user_defined_method(obj, '__init__')
|
||||||
if init is not None:
|
if init is not None:
|
||||||
sig = _signature_internal(init,
|
sig = _signature_internal(
|
||||||
follow_wrapper_chains,
|
init,
|
||||||
skip_bound_arg)
|
follow_wrapper_chains=follow_wrapper_chains,
|
||||||
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
sigcls=sigcls)
|
||||||
|
|
||||||
if sig is None:
|
if sig is None:
|
||||||
# At this point we know, that `obj` is a class, with no user-
|
# At this point we know, that `obj` is a class, with no user-
|
||||||
|
@ -1973,7 +1991,7 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
|
||||||
if text_sig:
|
if text_sig:
|
||||||
# If 'obj' class has a __text_signature__ attribute:
|
# If 'obj' class has a __text_signature__ attribute:
|
||||||
# return a signature based on it
|
# return a signature based on it
|
||||||
return _signature_fromstr(Signature, obj, text_sig)
|
return _signature_fromstr(sigcls, obj, text_sig)
|
||||||
|
|
||||||
# No '__text_signature__' was found for the 'obj' class.
|
# No '__text_signature__' was found for the 'obj' class.
|
||||||
# Last option is to check if its '__init__' is
|
# Last option is to check if its '__init__' is
|
||||||
|
@ -1993,9 +2011,11 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
|
||||||
call = _signature_get_user_defined_method(type(obj), '__call__')
|
call = _signature_get_user_defined_method(type(obj), '__call__')
|
||||||
if call is not None:
|
if call is not None:
|
||||||
try:
|
try:
|
||||||
sig = _signature_internal(call,
|
sig = _signature_internal(
|
||||||
follow_wrapper_chains,
|
call,
|
||||||
skip_bound_arg)
|
follow_wrapper_chains=follow_wrapper_chains,
|
||||||
|
skip_bound_arg=skip_bound_arg,
|
||||||
|
sigcls=sigcls)
|
||||||
except ValueError as ex:
|
except ValueError as ex:
|
||||||
msg = 'no signature found for {!r}'.format(obj)
|
msg = 'no signature found for {!r}'.format(obj)
|
||||||
raise ValueError(msg) from ex
|
raise ValueError(msg) from ex
|
||||||
|
@ -2015,10 +2035,6 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
|
||||||
|
|
||||||
raise ValueError('callable {!r} is not supported by signature'.format(obj))
|
raise ValueError('callable {!r} is not supported by signature'.format(obj))
|
||||||
|
|
||||||
def signature(obj):
|
|
||||||
'''Get a signature object for the passed callable.'''
|
|
||||||
return _signature_internal(obj)
|
|
||||||
|
|
||||||
|
|
||||||
class _void:
|
class _void:
|
||||||
'''A private marker - used in Parameter & Signature'''
|
'''A private marker - used in Parameter & Signature'''
|
||||||
|
@ -2464,6 +2480,10 @@ class Signature:
|
||||||
def from_builtin(cls, func):
|
def from_builtin(cls, func):
|
||||||
return _signature_from_builtin(cls, func)
|
return _signature_from_builtin(cls, func)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_callable(cls, obj):
|
||||||
|
return _signature_internal(obj, sigcls=cls)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parameters(self):
|
def parameters(self):
|
||||||
return self._parameters
|
return self._parameters
|
||||||
|
@ -2723,6 +2743,12 @@ class Signature:
|
||||||
|
|
||||||
return rendered
|
return rendered
|
||||||
|
|
||||||
|
|
||||||
|
def signature(obj):
|
||||||
|
'''Get a signature object for the passed callable.'''
|
||||||
|
return Signature.from_callable(obj)
|
||||||
|
|
||||||
|
|
||||||
def _main():
|
def _main():
|
||||||
""" Logic for inspecting an object given at command line """
|
""" Logic for inspecting an object given at command line """
|
||||||
import argparse
|
import argparse
|
||||||
|
|
|
@ -2517,6 +2517,19 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
self.assertEqual(self.signature(Spam.foo),
|
self.assertEqual(self.signature(Spam.foo),
|
||||||
self.signature(Ham.foo))
|
self.signature(Ham.foo))
|
||||||
|
|
||||||
|
def test_signature_from_callable_python_obj(self):
|
||||||
|
class MySignature(inspect.Signature): pass
|
||||||
|
def foo(a, *, b:1): pass
|
||||||
|
foo_sig = MySignature.from_callable(foo)
|
||||||
|
self.assertTrue(isinstance(foo_sig, MySignature))
|
||||||
|
|
||||||
|
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||||
|
"Signature information for builtins requires docstrings")
|
||||||
|
def test_signature_from_callable_builtin_obj(self):
|
||||||
|
class MySignature(inspect.Signature): pass
|
||||||
|
sig = MySignature.from_callable(_pickle.Pickler)
|
||||||
|
self.assertTrue(isinstance(sig, MySignature))
|
||||||
|
|
||||||
|
|
||||||
class TestParameterObject(unittest.TestCase):
|
class TestParameterObject(unittest.TestCase):
|
||||||
def test_signature_parameter_kinds(self):
|
def test_signature_parameter_kinds(self):
|
||||||
|
|
|
@ -109,6 +109,8 @@ Library
|
||||||
|
|
||||||
- Issue #20726: inspect.signature: Make Signature and Parameter picklable.
|
- Issue #20726: inspect.signature: Make Signature and Parameter picklable.
|
||||||
|
|
||||||
|
- Issue #17373: Add inspect.Signature.from_callable method.
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue