inspect.Signature: Add 'Signature.from_callable' classmethod. Closes #17373

This commit is contained in:
Yury Selivanov 2014-03-27 12:09:24 -04:00
parent a5d63dd7b8
commit da39645ad3
5 changed files with 87 additions and 30 deletions

View File

@ -506,6 +506,18 @@ function.
>>> str(new_sig)
"(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)

View File

@ -140,6 +140,10 @@ Improved Modules
* :class:`inspect.Signature` and :class:`inspect.Parameter` are now
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
=============

View File

@ -969,7 +969,8 @@ def getfullargspec(func):
sig = _signature_internal(func,
follow_wrapper_chains=False,
skip_bound_arg=False)
skip_bound_arg=False,
sigcls=Signature)
except Exception as ex:
# Most of the times 'signature' will raise ValueError.
# 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)
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):
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):
# In this case we skip the first parameter of the underlying
# function (usually `self` or `cls`).
sig = _signature_internal(obj.__func__,
follow_wrapper_chains,
skip_bound_arg)
sig = _signature_internal(
obj.__func__,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
sigcls=sigcls)
if skip_bound_arg:
return _signature_bound_method(sig)
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
# automatically (as for boundmethods)
wrapped_sig = _signature_internal(partialmethod.func,
follow_wrapper_chains,
skip_bound_arg)
wrapped_sig = _signature_internal(
partialmethod.func,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
sigcls=sigcls)
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
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 it's a pure Python function, or an object that is duck type
# of a Python function (Cython functions, for instance), then:
return Signature.from_function(obj)
return sigcls.from_function(obj)
if _signature_is_builtin(obj):
return _signature_from_builtin(Signature, obj,
return _signature_from_builtin(sigcls, obj,
skip_bound_arg=skip_bound_arg)
if isinstance(obj, functools.partial):
wrapped_sig = _signature_internal(obj.func,
follow_wrapper_chains,
skip_bound_arg)
wrapped_sig = _signature_internal(
obj.func,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
sigcls=sigcls)
return _signature_get_partial(wrapped_sig, obj)
sig = None
@ -1935,23 +1947,29 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
# in its metaclass
call = _signature_get_user_defined_method(type(obj), '__call__')
if call is not None:
sig = _signature_internal(call,
follow_wrapper_chains,
skip_bound_arg)
sig = _signature_internal(
call,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
sigcls=sigcls)
else:
# Now we check if the 'obj' class has a '__new__' method
new = _signature_get_user_defined_method(obj, '__new__')
if new is not None:
sig = _signature_internal(new,
follow_wrapper_chains,
skip_bound_arg)
sig = _signature_internal(
new,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
sigcls=sigcls)
else:
# Finally, we should have at least __init__ implemented
init = _signature_get_user_defined_method(obj, '__init__')
if init is not None:
sig = _signature_internal(init,
follow_wrapper_chains,
skip_bound_arg)
sig = _signature_internal(
init,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
sigcls=sigcls)
if sig is None:
# 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 'obj' class has a __text_signature__ attribute:
# 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.
# 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__')
if call is not None:
try:
sig = _signature_internal(call,
follow_wrapper_chains,
skip_bound_arg)
sig = _signature_internal(
call,
follow_wrapper_chains=follow_wrapper_chains,
skip_bound_arg=skip_bound_arg,
sigcls=sigcls)
except ValueError as ex:
msg = 'no signature found for {!r}'.format(obj)
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))
def signature(obj):
'''Get a signature object for the passed callable.'''
return _signature_internal(obj)
class _void:
'''A private marker - used in Parameter & Signature'''
@ -2464,6 +2480,10 @@ class Signature:
def from_builtin(cls, func):
return _signature_from_builtin(cls, func)
@classmethod
def from_callable(cls, obj):
return _signature_internal(obj, sigcls=cls)
@property
def parameters(self):
return self._parameters
@ -2723,6 +2743,12 @@ class Signature:
return rendered
def signature(obj):
'''Get a signature object for the passed callable.'''
return Signature.from_callable(obj)
def _main():
""" Logic for inspecting an object given at command line """
import argparse

View File

@ -2517,6 +2517,19 @@ class TestSignatureObject(unittest.TestCase):
self.assertEqual(self.signature(Spam.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):
def test_signature_parameter_kinds(self):

View File

@ -109,6 +109,8 @@ Library
- Issue #20726: inspect.signature: Make Signature and Parameter picklable.
- Issue #17373: Add inspect.Signature.from_callable method.
Documentation
-------------