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

Skip to content

Commit d82eddc

Browse files
committed
inspect.getfullargspec: Use inspect.signature API behind the scenes #17481
1 parent 07a9e45 commit d82eddc

4 files changed

Lines changed: 155 additions & 7 deletions

File tree

Doc/whatsnew/3.4.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,13 @@ As part of the implementation of the new :mod:`enum` module, the
786786
metaclasses (Contributed by Ethan Furman in :issue:`18929` and
787787
:issue:`19030`)
788788

789+
:func:`~inspect.getfullargspec` and :func:`~inspect.getargspec`
790+
now use the :func:`~inspect.signature` API. This allows them to
791+
support much broader range of functions, including some builtins and
792+
callables that follow ``__signature__`` protocol. It is still
793+
recommended to update your code to use :func:`~inspect.signature`
794+
directly. (Contributed by Yury Selivanov in :issue:`17481`)
795+
789796

790797
logging
791798
-------

Lib/inspect.py

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ def getargspec(func):
934934
'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations')
935935

936936
def getfullargspec(func):
937-
"""Get the names and default values of a function's arguments.
937+
"""Get the names and default values of a callable object's arguments.
938938
939939
A tuple of seven things is returned:
940940
(args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults annotations).
@@ -948,13 +948,90 @@ def getfullargspec(func):
948948
The first four items in the tuple correspond to getargspec().
949949
"""
950950

951+
builtin_method_param = None
952+
951953
if ismethod(func):
954+
# There is a notable difference in behaviour between getfullargspec
955+
# and Signature: the former always returns 'self' parameter for bound
956+
# methods, whereas the Signature always shows the actual calling
957+
# signature of the passed object.
958+
#
959+
# To simulate this behaviour, we "unbind" bound methods, to trick
960+
# inspect.signature to always return their first parameter ("self",
961+
# usually)
952962
func = func.__func__
953-
if not isfunction(func):
954-
raise TypeError('{!r} is not a Python function'.format(func))
955-
args, varargs, kwonlyargs, varkw = _getfullargs(func.__code__)
956-
return FullArgSpec(args, varargs, varkw, func.__defaults__,
957-
kwonlyargs, func.__kwdefaults__, func.__annotations__)
963+
964+
elif isbuiltin(func):
965+
# We have a builtin function or method. For that, we check the
966+
# special '__text_signature__' attribute, provided by the
967+
# Argument Clinic. If it's a method, we'll need to make sure
968+
# that its first parameter (usually "self") is always returned
969+
# (see the previous comment).
970+
text_signature = getattr(func, '__text_signature__', None)
971+
if text_signature and text_signature.startswith('($'):
972+
builtin_method_param = _signature_get_bound_param(text_signature)
973+
974+
try:
975+
sig = signature(func)
976+
except Exception as ex:
977+
# Most of the times 'signature' will raise ValueError.
978+
# But, it can also raise AttributeError, and, maybe something
979+
# else. So to be fully backwards compatible, we catch all
980+
# possible exceptions here, and reraise a TypeError.
981+
raise TypeError('unsupported callable') from ex
982+
983+
args = []
984+
varargs = None
985+
varkw = None
986+
kwonlyargs = []
987+
defaults = ()
988+
annotations = {}
989+
defaults = ()
990+
kwdefaults = {}
991+
992+
if sig.return_annotation is not sig.empty:
993+
annotations['return'] = sig.return_annotation
994+
995+
for param in sig.parameters.values():
996+
kind = param.kind
997+
name = param.name
998+
999+
if kind is _POSITIONAL_ONLY:
1000+
args.append(name)
1001+
elif kind is _POSITIONAL_OR_KEYWORD:
1002+
args.append(name)
1003+
if param.default is not param.empty:
1004+
defaults += (param.default,)
1005+
elif kind is _VAR_POSITIONAL:
1006+
varargs = name
1007+
elif kind is _KEYWORD_ONLY:
1008+
kwonlyargs.append(name)
1009+
if param.default is not param.empty:
1010+
kwdefaults[name] = param.default
1011+
elif kind is _VAR_KEYWORD:
1012+
varkw = name
1013+
1014+
if param.annotation is not param.empty:
1015+
annotations[name] = param.annotation
1016+
1017+
if not kwdefaults:
1018+
# compatibility with 'func.__kwdefaults__'
1019+
kwdefaults = None
1020+
1021+
if not defaults:
1022+
# compatibility with 'func.__defaults__'
1023+
defaults = None
1024+
1025+
if builtin_method_param and (not args or args[0] != builtin_method_param):
1026+
# `func` is a method, and we always need to return its
1027+
# first parameter -- usually "self" (to be backwards
1028+
# compatible with the previous implementation of
1029+
# getfullargspec)
1030+
args.insert(0, builtin_method_param)
1031+
1032+
return FullArgSpec(args, varargs, varkw, defaults,
1033+
kwonlyargs, kwdefaults, annotations)
1034+
9581035

9591036
ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals')
9601037

@@ -1524,6 +1601,28 @@ def _signature_is_builtin(obj):
15241601
obj in (type, object))
15251602

15261603

1604+
def _signature_get_bound_param(spec):
1605+
# Internal helper to get first parameter name from a
1606+
# __text_signature__ of a builtin method, which should
1607+
# be in the following format: '($param1, ...)'.
1608+
# Assumptions are that the first argument won't have
1609+
# a default value or an annotation.
1610+
1611+
assert spec.startswith('($')
1612+
1613+
pos = spec.find(',')
1614+
if pos == -1:
1615+
pos = spec.find(')')
1616+
1617+
cpos = spec.find(':')
1618+
assert cpos == -1 or cpos > pos
1619+
1620+
cpos = spec.find('=')
1621+
assert cpos == -1 or cpos > pos
1622+
1623+
return spec[2:pos]
1624+
1625+
15271626
def signature(obj):
15281627
'''Get a signature object for the passed callable.'''
15291628

Lib/test/test_inspect.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,36 @@ def test_getfullargspec(self):
578578
kwonlyargs_e=['arg'],
579579
formatted='(*, arg)')
580580

581+
def test_getfullargspec_signature_attr(self):
582+
def test():
583+
pass
584+
spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY)
585+
test.__signature__ = inspect.Signature(parameters=(spam_param,))
586+
587+
self.assertFullArgSpecEquals(test, args_e=['spam'], formatted='(spam)')
588+
589+
@unittest.skipIf(MISSING_C_DOCSTRINGS,
590+
"Signature information for builtins requires docstrings")
591+
def test_getfullargspec_builtin_methods(self):
592+
self.assertFullArgSpecEquals(_pickle.Pickler.dump,
593+
args_e=['self', 'obj'], formatted='(self, obj)')
594+
595+
self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump,
596+
args_e=['self', 'obj'], formatted='(self, obj)')
597+
598+
@unittest.skipIf(MISSING_C_DOCSTRINGS,
599+
"Signature information for builtins requires docstrings")
600+
def test_getfullagrspec_builtin_func(self):
601+
builtin = _testcapi.docstring_with_signature_with_defaults
602+
spec = inspect.getfullargspec(builtin)
603+
self.assertEqual(spec.defaults[0], 'avocado')
604+
605+
@unittest.skipIf(MISSING_C_DOCSTRINGS,
606+
"Signature information for builtins requires docstrings")
607+
def test_getfullagrspec_builtin_func_no_signature(self):
608+
builtin = _testcapi.docstring_no_signature
609+
with self.assertRaises(TypeError):
610+
inspect.getfullargspec(builtin)
581611

582612
def test_getargspec_method(self):
583613
class A(object):
@@ -2614,6 +2644,15 @@ def bar(b): pass
26142644
self.assertNotEqual(ba, ba4)
26152645

26162646

2647+
class TestSignaturePrivateHelpers(unittest.TestCase):
2648+
def test_signature_get_bound_param(self):
2649+
getter = inspect._signature_get_bound_param
2650+
2651+
self.assertEqual(getter('($self)'), 'self')
2652+
self.assertEqual(getter('($self, obj)'), 'self')
2653+
self.assertEqual(getter('($cls, /, obj)'), 'cls')
2654+
2655+
26172656
class TestUnwrap(unittest.TestCase):
26182657

26192658
def test_unwrap_one(self):
@@ -2719,7 +2758,8 @@ def test_main():
27192758
TestGetcallargsFunctions, TestGetcallargsMethods,
27202759
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
27212760
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
2722-
TestBoundArguments, TestGetClosureVars, TestUnwrap, TestMain
2761+
TestBoundArguments, TestSignaturePrivateHelpers, TestGetClosureVars,
2762+
TestUnwrap, TestMain
27232763
)
27242764

27252765
if __name__ == "__main__":

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Library
4747
- Issue #20105: the codec exception chaining now correctly sets the
4848
traceback of the original exception as its __traceback__ attribute.
4949

50+
- Issue #17481: inspect.getfullargspec() now uses inspect.signature() API.
51+
5052
IDLE
5153
----
5254

0 commit comments

Comments
 (0)