Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit da39645

Browse files
committed
inspect.Signature: Add 'Signature.from_callable' classmethod. Closes #17373
1 parent a5d63dd commit da39645

5 files changed

Lines changed: 87 additions & 30 deletions

File tree

Doc/library/inspect.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,6 +506,18 @@ function.
506506
>>> str(new_sig)
507507
"(a, b) -> 'new return anno'"
508508

509+
.. classmethod:: Signature.from_callable(obj)
510+
511+
Return a :class:`Signature` (or its subclass) object for a given callable
512+
``obj``. This method simplifies subclassing of :class:`Signature`:
513+
514+
::
515+
516+
class MySignature(Signature):
517+
pass
518+
sig = MySignature.from_callable(min)
519+
assert isinstance(sig, MySignature)
520+
509521

510522
.. class:: Parameter(name, kind, \*, default=Parameter.empty, annotation=Parameter.empty)
511523

Doc/whatsnew/3.5.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ Improved Modules
140140
* :class:`inspect.Signature` and :class:`inspect.Parameter` are now
141141
picklable (contributed by Yury Selivanov in :issue:`20726`).
142142

143+
* New class method :meth:`inspect.Signature.from_callable`, which makes
144+
subclassing of :class:`~inspect.Signature` easier (contributed
145+
by Yury Selivanov and Eric Snow in :issue:`17373`).
146+
143147

144148
Optimizations
145149
=============

Lib/inspect.py

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -969,7 +969,8 @@ def getfullargspec(func):
969969

970970
sig = _signature_internal(func,
971971
follow_wrapper_chains=False,
972-
skip_bound_arg=False)
972+
skip_bound_arg=False,
973+
sigcls=Signature)
973974
except Exception as ex:
974975
# Most of the times 'signature' will raise ValueError.
975976
# But, it can also raise AttributeError, and, maybe something
@@ -1861,17 +1862,23 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
18611862
return _signature_fromstr(cls, func, s, skip_bound_arg)
18621863

18631864

1864-
def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
1865+
def _signature_internal(obj, *,
1866+
follow_wrapper_chains=True,
1867+
skip_bound_arg=True,
1868+
sigcls):
18651869

18661870
if not callable(obj):
18671871
raise TypeError('{!r} is not a callable object'.format(obj))
18681872

18691873
if isinstance(obj, types.MethodType):
18701874
# In this case we skip the first parameter of the underlying
18711875
# function (usually `self` or `cls`).
1872-
sig = _signature_internal(obj.__func__,
1873-
follow_wrapper_chains,
1874-
skip_bound_arg)
1876+
sig = _signature_internal(
1877+
obj.__func__,
1878+
follow_wrapper_chains=follow_wrapper_chains,
1879+
skip_bound_arg=skip_bound_arg,
1880+
sigcls=sigcls)
1881+
18751882
if skip_bound_arg:
18761883
return _signature_bound_method(sig)
18771884
else:
@@ -1902,9 +1909,12 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
19021909
# (usually `self`, or `cls`) will not be passed
19031910
# automatically (as for boundmethods)
19041911

1905-
wrapped_sig = _signature_internal(partialmethod.func,
1906-
follow_wrapper_chains,
1907-
skip_bound_arg)
1912+
wrapped_sig = _signature_internal(
1913+
partialmethod.func,
1914+
follow_wrapper_chains=follow_wrapper_chains,
1915+
skip_bound_arg=skip_bound_arg,
1916+
sigcls=sigcls)
1917+
19081918
sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
19091919

19101920
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):
19151925
if isfunction(obj) or _signature_is_functionlike(obj):
19161926
# If it's a pure Python function, or an object that is duck type
19171927
# of a Python function (Cython functions, for instance), then:
1918-
return Signature.from_function(obj)
1928+
return sigcls.from_function(obj)
19191929

19201930
if _signature_is_builtin(obj):
1921-
return _signature_from_builtin(Signature, obj,
1931+
return _signature_from_builtin(sigcls, obj,
19221932
skip_bound_arg=skip_bound_arg)
19231933

19241934
if isinstance(obj, functools.partial):
1925-
wrapped_sig = _signature_internal(obj.func,
1926-
follow_wrapper_chains,
1927-
skip_bound_arg)
1935+
wrapped_sig = _signature_internal(
1936+
obj.func,
1937+
follow_wrapper_chains=follow_wrapper_chains,
1938+
skip_bound_arg=skip_bound_arg,
1939+
sigcls=sigcls)
19281940
return _signature_get_partial(wrapped_sig, obj)
19291941

19301942
sig = None
@@ -1935,23 +1947,29 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
19351947
# in its metaclass
19361948
call = _signature_get_user_defined_method(type(obj), '__call__')
19371949
if call is not None:
1938-
sig = _signature_internal(call,
1939-
follow_wrapper_chains,
1940-
skip_bound_arg)
1950+
sig = _signature_internal(
1951+
call,
1952+
follow_wrapper_chains=follow_wrapper_chains,
1953+
skip_bound_arg=skip_bound_arg,
1954+
sigcls=sigcls)
19411955
else:
19421956
# Now we check if the 'obj' class has a '__new__' method
19431957
new = _signature_get_user_defined_method(obj, '__new__')
19441958
if new is not None:
1945-
sig = _signature_internal(new,
1946-
follow_wrapper_chains,
1947-
skip_bound_arg)
1959+
sig = _signature_internal(
1960+
new,
1961+
follow_wrapper_chains=follow_wrapper_chains,
1962+
skip_bound_arg=skip_bound_arg,
1963+
sigcls=sigcls)
19481964
else:
19491965
# Finally, we should have at least __init__ implemented
19501966
init = _signature_get_user_defined_method(obj, '__init__')
19511967
if init is not None:
1952-
sig = _signature_internal(init,
1953-
follow_wrapper_chains,
1954-
skip_bound_arg)
1968+
sig = _signature_internal(
1969+
init,
1970+
follow_wrapper_chains=follow_wrapper_chains,
1971+
skip_bound_arg=skip_bound_arg,
1972+
sigcls=sigcls)
19551973

19561974
if sig is None:
19571975
# 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):
19731991
if text_sig:
19741992
# If 'obj' class has a __text_signature__ attribute:
19751993
# return a signature based on it
1976-
return _signature_fromstr(Signature, obj, text_sig)
1994+
return _signature_fromstr(sigcls, obj, text_sig)
19771995

19781996
# No '__text_signature__' was found for the 'obj' class.
19791997
# 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):
19932011
call = _signature_get_user_defined_method(type(obj), '__call__')
19942012
if call is not None:
19952013
try:
1996-
sig = _signature_internal(call,
1997-
follow_wrapper_chains,
1998-
skip_bound_arg)
2014+
sig = _signature_internal(
2015+
call,
2016+
follow_wrapper_chains=follow_wrapper_chains,
2017+
skip_bound_arg=skip_bound_arg,
2018+
sigcls=sigcls)
19992019
except ValueError as ex:
20002020
msg = 'no signature found for {!r}'.format(obj)
20012021
raise ValueError(msg) from ex
@@ -2015,10 +2035,6 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
20152035

20162036
raise ValueError('callable {!r} is not supported by signature'.format(obj))
20172037

2018-
def signature(obj):
2019-
'''Get a signature object for the passed callable.'''
2020-
return _signature_internal(obj)
2021-
20222038

20232039
class _void:
20242040
'''A private marker - used in Parameter & Signature'''
@@ -2464,6 +2480,10 @@ def from_function(cls, func):
24642480
def from_builtin(cls, func):
24652481
return _signature_from_builtin(cls, func)
24662482

2483+
@classmethod
2484+
def from_callable(cls, obj):
2485+
return _signature_internal(obj, sigcls=cls)
2486+
24672487
@property
24682488
def parameters(self):
24692489
return self._parameters
@@ -2723,6 +2743,12 @@ def __str__(self):
27232743

27242744
return rendered
27252745

2746+
2747+
def signature(obj):
2748+
'''Get a signature object for the passed callable.'''
2749+
return Signature.from_callable(obj)
2750+
2751+
27262752
def _main():
27272753
""" Logic for inspecting an object given at command line """
27282754
import argparse

Lib/test/test_inspect.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2517,6 +2517,19 @@ class Ham(Spam):
25172517
self.assertEqual(self.signature(Spam.foo),
25182518
self.signature(Ham.foo))
25192519

2520+
def test_signature_from_callable_python_obj(self):
2521+
class MySignature(inspect.Signature): pass
2522+
def foo(a, *, b:1): pass
2523+
foo_sig = MySignature.from_callable(foo)
2524+
self.assertTrue(isinstance(foo_sig, MySignature))
2525+
2526+
@unittest.skipIf(MISSING_C_DOCSTRINGS,
2527+
"Signature information for builtins requires docstrings")
2528+
def test_signature_from_callable_builtin_obj(self):
2529+
class MySignature(inspect.Signature): pass
2530+
sig = MySignature.from_callable(_pickle.Pickler)
2531+
self.assertTrue(isinstance(sig, MySignature))
2532+
25202533

25212534
class TestParameterObject(unittest.TestCase):
25222535
def test_signature_parameter_kinds(self):

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ Library
109109

110110
- Issue #20726: inspect.signature: Make Signature and Parameter picklable.
111111

112+
- Issue #17373: Add inspect.Signature.from_callable method.
113+
112114
Documentation
113115
-------------
114116

0 commit comments

Comments
 (0)