mirror of
https://github.com/python/cpython.git
synced 2025-09-18 22:50:26 +00:00
inspect.getfullargspec: Use inspect.signature API behind the scenes #17481
This commit is contained in:
parent
07a9e452ac
commit
d82eddcf05
4 changed files with 155 additions and 7 deletions
|
@ -786,6 +786,13 @@ As part of the implementation of the new :mod:`enum` module, the
|
||||||
metaclasses (Contributed by Ethan Furman in :issue:`18929` and
|
metaclasses (Contributed by Ethan Furman in :issue:`18929` and
|
||||||
:issue:`19030`)
|
:issue:`19030`)
|
||||||
|
|
||||||
|
:func:`~inspect.getfullargspec` and :func:`~inspect.getargspec`
|
||||||
|
now use the :func:`~inspect.signature` API. This allows them to
|
||||||
|
support much broader range of functions, including some builtins and
|
||||||
|
callables that follow ``__signature__`` protocol. It is still
|
||||||
|
recommended to update your code to use :func:`~inspect.signature`
|
||||||
|
directly. (Contributed by Yury Selivanov in :issue:`17481`)
|
||||||
|
|
||||||
|
|
||||||
logging
|
logging
|
||||||
-------
|
-------
|
||||||
|
|
111
Lib/inspect.py
111
Lib/inspect.py
|
@ -934,7 +934,7 @@ FullArgSpec = namedtuple('FullArgSpec',
|
||||||
'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations')
|
'args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, annotations')
|
||||||
|
|
||||||
def getfullargspec(func):
|
def getfullargspec(func):
|
||||||
"""Get the names and default values of a function's arguments.
|
"""Get the names and default values of a callable object's arguments.
|
||||||
|
|
||||||
A tuple of seven things is returned:
|
A tuple of seven things is returned:
|
||||||
(args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults annotations).
|
(args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults annotations).
|
||||||
|
@ -948,13 +948,90 @@ def getfullargspec(func):
|
||||||
The first four items in the tuple correspond to getargspec().
|
The first four items in the tuple correspond to getargspec().
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
builtin_method_param = None
|
||||||
|
|
||||||
if ismethod(func):
|
if ismethod(func):
|
||||||
|
# There is a notable difference in behaviour between getfullargspec
|
||||||
|
# and Signature: the former always returns 'self' parameter for bound
|
||||||
|
# methods, whereas the Signature always shows the actual calling
|
||||||
|
# signature of the passed object.
|
||||||
|
#
|
||||||
|
# To simulate this behaviour, we "unbind" bound methods, to trick
|
||||||
|
# inspect.signature to always return their first parameter ("self",
|
||||||
|
# usually)
|
||||||
func = func.__func__
|
func = func.__func__
|
||||||
if not isfunction(func):
|
|
||||||
raise TypeError('{!r} is not a Python function'.format(func))
|
elif isbuiltin(func):
|
||||||
args, varargs, kwonlyargs, varkw = _getfullargs(func.__code__)
|
# We have a builtin function or method. For that, we check the
|
||||||
return FullArgSpec(args, varargs, varkw, func.__defaults__,
|
# special '__text_signature__' attribute, provided by the
|
||||||
kwonlyargs, func.__kwdefaults__, func.__annotations__)
|
# Argument Clinic. If it's a method, we'll need to make sure
|
||||||
|
# that its first parameter (usually "self") is always returned
|
||||||
|
# (see the previous comment).
|
||||||
|
text_signature = getattr(func, '__text_signature__', None)
|
||||||
|
if text_signature and text_signature.startswith('($'):
|
||||||
|
builtin_method_param = _signature_get_bound_param(text_signature)
|
||||||
|
|
||||||
|
try:
|
||||||
|
sig = signature(func)
|
||||||
|
except Exception as ex:
|
||||||
|
# Most of the times 'signature' will raise ValueError.
|
||||||
|
# But, it can also raise AttributeError, and, maybe something
|
||||||
|
# else. So to be fully backwards compatible, we catch all
|
||||||
|
# possible exceptions here, and reraise a TypeError.
|
||||||
|
raise TypeError('unsupported callable') from ex
|
||||||
|
|
||||||
|
args = []
|
||||||
|
varargs = None
|
||||||
|
varkw = None
|
||||||
|
kwonlyargs = []
|
||||||
|
defaults = ()
|
||||||
|
annotations = {}
|
||||||
|
defaults = ()
|
||||||
|
kwdefaults = {}
|
||||||
|
|
||||||
|
if sig.return_annotation is not sig.empty:
|
||||||
|
annotations['return'] = sig.return_annotation
|
||||||
|
|
||||||
|
for param in sig.parameters.values():
|
||||||
|
kind = param.kind
|
||||||
|
name = param.name
|
||||||
|
|
||||||
|
if kind is _POSITIONAL_ONLY:
|
||||||
|
args.append(name)
|
||||||
|
elif kind is _POSITIONAL_OR_KEYWORD:
|
||||||
|
args.append(name)
|
||||||
|
if param.default is not param.empty:
|
||||||
|
defaults += (param.default,)
|
||||||
|
elif kind is _VAR_POSITIONAL:
|
||||||
|
varargs = name
|
||||||
|
elif kind is _KEYWORD_ONLY:
|
||||||
|
kwonlyargs.append(name)
|
||||||
|
if param.default is not param.empty:
|
||||||
|
kwdefaults[name] = param.default
|
||||||
|
elif kind is _VAR_KEYWORD:
|
||||||
|
varkw = name
|
||||||
|
|
||||||
|
if param.annotation is not param.empty:
|
||||||
|
annotations[name] = param.annotation
|
||||||
|
|
||||||
|
if not kwdefaults:
|
||||||
|
# compatibility with 'func.__kwdefaults__'
|
||||||
|
kwdefaults = None
|
||||||
|
|
||||||
|
if not defaults:
|
||||||
|
# compatibility with 'func.__defaults__'
|
||||||
|
defaults = None
|
||||||
|
|
||||||
|
if builtin_method_param and (not args or args[0] != builtin_method_param):
|
||||||
|
# `func` is a method, and we always need to return its
|
||||||
|
# first parameter -- usually "self" (to be backwards
|
||||||
|
# compatible with the previous implementation of
|
||||||
|
# getfullargspec)
|
||||||
|
args.insert(0, builtin_method_param)
|
||||||
|
|
||||||
|
return FullArgSpec(args, varargs, varkw, defaults,
|
||||||
|
kwonlyargs, kwdefaults, annotations)
|
||||||
|
|
||||||
|
|
||||||
ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals')
|
ArgInfo = namedtuple('ArgInfo', 'args varargs keywords locals')
|
||||||
|
|
||||||
|
@ -1524,6 +1601,28 @@ def _signature_is_builtin(obj):
|
||||||
obj in (type, object))
|
obj in (type, object))
|
||||||
|
|
||||||
|
|
||||||
|
def _signature_get_bound_param(spec):
|
||||||
|
# Internal helper to get first parameter name from a
|
||||||
|
# __text_signature__ of a builtin method, which should
|
||||||
|
# be in the following format: '($param1, ...)'.
|
||||||
|
# Assumptions are that the first argument won't have
|
||||||
|
# a default value or an annotation.
|
||||||
|
|
||||||
|
assert spec.startswith('($')
|
||||||
|
|
||||||
|
pos = spec.find(',')
|
||||||
|
if pos == -1:
|
||||||
|
pos = spec.find(')')
|
||||||
|
|
||||||
|
cpos = spec.find(':')
|
||||||
|
assert cpos == -1 or cpos > pos
|
||||||
|
|
||||||
|
cpos = spec.find('=')
|
||||||
|
assert cpos == -1 or cpos > pos
|
||||||
|
|
||||||
|
return spec[2:pos]
|
||||||
|
|
||||||
|
|
||||||
def signature(obj):
|
def signature(obj):
|
||||||
'''Get a signature object for the passed callable.'''
|
'''Get a signature object for the passed callable.'''
|
||||||
|
|
||||||
|
|
|
@ -578,6 +578,36 @@ class TestClassesAndFunctions(unittest.TestCase):
|
||||||
kwonlyargs_e=['arg'],
|
kwonlyargs_e=['arg'],
|
||||||
formatted='(*, arg)')
|
formatted='(*, arg)')
|
||||||
|
|
||||||
|
def test_getfullargspec_signature_attr(self):
|
||||||
|
def test():
|
||||||
|
pass
|
||||||
|
spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY)
|
||||||
|
test.__signature__ = inspect.Signature(parameters=(spam_param,))
|
||||||
|
|
||||||
|
self.assertFullArgSpecEquals(test, args_e=['spam'], formatted='(spam)')
|
||||||
|
|
||||||
|
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||||
|
"Signature information for builtins requires docstrings")
|
||||||
|
def test_getfullargspec_builtin_methods(self):
|
||||||
|
self.assertFullArgSpecEquals(_pickle.Pickler.dump,
|
||||||
|
args_e=['self', 'obj'], formatted='(self, obj)')
|
||||||
|
|
||||||
|
self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump,
|
||||||
|
args_e=['self', 'obj'], formatted='(self, obj)')
|
||||||
|
|
||||||
|
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||||
|
"Signature information for builtins requires docstrings")
|
||||||
|
def test_getfullagrspec_builtin_func(self):
|
||||||
|
builtin = _testcapi.docstring_with_signature_with_defaults
|
||||||
|
spec = inspect.getfullargspec(builtin)
|
||||||
|
self.assertEqual(spec.defaults[0], 'avocado')
|
||||||
|
|
||||||
|
@unittest.skipIf(MISSING_C_DOCSTRINGS,
|
||||||
|
"Signature information for builtins requires docstrings")
|
||||||
|
def test_getfullagrspec_builtin_func_no_signature(self):
|
||||||
|
builtin = _testcapi.docstring_no_signature
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
inspect.getfullargspec(builtin)
|
||||||
|
|
||||||
def test_getargspec_method(self):
|
def test_getargspec_method(self):
|
||||||
class A(object):
|
class A(object):
|
||||||
|
@ -2614,6 +2644,15 @@ class TestBoundArguments(unittest.TestCase):
|
||||||
self.assertNotEqual(ba, ba4)
|
self.assertNotEqual(ba, ba4)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSignaturePrivateHelpers(unittest.TestCase):
|
||||||
|
def test_signature_get_bound_param(self):
|
||||||
|
getter = inspect._signature_get_bound_param
|
||||||
|
|
||||||
|
self.assertEqual(getter('($self)'), 'self')
|
||||||
|
self.assertEqual(getter('($self, obj)'), 'self')
|
||||||
|
self.assertEqual(getter('($cls, /, obj)'), 'cls')
|
||||||
|
|
||||||
|
|
||||||
class TestUnwrap(unittest.TestCase):
|
class TestUnwrap(unittest.TestCase):
|
||||||
|
|
||||||
def test_unwrap_one(self):
|
def test_unwrap_one(self):
|
||||||
|
@ -2719,7 +2758,8 @@ def test_main():
|
||||||
TestGetcallargsFunctions, TestGetcallargsMethods,
|
TestGetcallargsFunctions, TestGetcallargsMethods,
|
||||||
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
|
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
|
||||||
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
|
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
|
||||||
TestBoundArguments, TestGetClosureVars, TestUnwrap, TestMain
|
TestBoundArguments, TestSignaturePrivateHelpers, TestGetClosureVars,
|
||||||
|
TestUnwrap, TestMain
|
||||||
)
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -47,6 +47,8 @@ Library
|
||||||
- Issue #20105: the codec exception chaining now correctly sets the
|
- Issue #20105: the codec exception chaining now correctly sets the
|
||||||
traceback of the original exception as its __traceback__ attribute.
|
traceback of the original exception as its __traceback__ attribute.
|
||||||
|
|
||||||
|
- Issue #17481: inspect.getfullargspec() now uses inspect.signature() API.
|
||||||
|
|
||||||
IDLE
|
IDLE
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue