cpython: 32a660a52aae (original) (raw)
Mercurial > cpython
changeset 88866:32a660a52aae
inspect.signature: Support duck-types of Python functions (Cython, for instance) #17159 [#17159]
Yury Selivanov yselivanov@sprymix.com | |
---|---|
date | Fri, 31 Jan 2014 14:48:37 -0500 |
parents | d0f95094033d |
children | 8a91132ed6aa |
files | Doc/whatsnew/3.4.rst Lib/inspect.py Lib/test/test_inspect.py Misc/NEWS |
diffstat | 4 files changed, 97 insertions(+), 2 deletions(-)[+] [-] Doc/whatsnew/3.4.rst 4 Lib/inspect.py 32 Lib/test/test_inspect.py 60 Misc/NEWS 3 |
line wrap: on
line diff
--- a/Doc/whatsnew/3.4.rst
+++ b/Doc/whatsnew/3.4.rst
@@ -793,6 +793,10 @@ callables that follow __signature__
recommended to update your code to use :func:~inspect.signature
directly. (Contributed by Yury Selivanov in :issue:17481
)
+:func:~inspect.signature
now supports duck types of CPython functions,
+which adds support for functions compiled with Cython. (Contributed
+by Stefan Behnel and Yury Selivanov in :issue:17159
)
+
logging
-------
--- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -1601,6 +1601,30 @@ def _signature_is_builtin(obj): obj in (type, object)) +def _signature_is_functionlike(obj):
Internal helper to test ifobj
is a duck type of FunctionType.A good example of such objects are functions compiled with
Cython, which have all attributes that a pure Python function
would have, but have their code statically compiled.
- if not callable(obj) or isclass(obj):
# All function-like objects are obviously callables,[](#l2.14)
# and not classes.[](#l2.15)
return False[](#l2.16)
- name = getattr(obj, 'name', None)
- code = getattr(obj, 'code', None)
- defaults = getattr(obj, 'defaults', _void) # Important to use _void ...
- kwdefaults = getattr(obj, 'kwdefaults', _void) # ... and not None here
- annotations = getattr(obj, 'annotations', None)
- return (isinstance(code, types.CodeType) and
isinstance(name, str) and[](#l2.25)
(defaults is None or isinstance(defaults, tuple)) and[](#l2.26)
(kwdefaults is None or isinstance(kwdefaults, dict)) and[](#l2.27)
isinstance(annotations, dict))[](#l2.28)
+ + def _signature_get_bound_param(spec): # Internal helper to get first parameter name from a # text_signature of a builtin method, which should @@ -1670,7 +1694,9 @@ def signature(obj): if _signature_is_builtin(obj): return Signature.from_builtin(obj)
- if isfunction(obj) or _signature_is_functionlike(obj):
# If it's a pure Python function, or an object that is duck type[](#l2.40)
# of a Python function (Cython functions, for instance), then:[](#l2.41) return Signature.from_function(obj)[](#l2.42)
if isinstance(obj, functools.partial): @@ -2071,7 +2097,9 @@ class Signature: def from_function(cls, func): '''Constructs Signature for the given python function'''
if not isinstance(func, types.FunctionType):[](#l2.49)
if not (isfunction(func) or _signature_is_functionlike(func)):[](#l2.50)
# If it's not a pure Python function, and not a duck type[](#l2.51)
# of pure function:[](#l2.52) raise TypeError('{!r} is not a Python function'.format(func))[](#l2.53)
Parameter = cls._parameter_cls
--- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -1740,6 +1740,66 @@ class TestSignatureObject(unittest.TestC with self.assertRaisesRegex(TypeError, 'is not a Python builtin'): inspect.Signature.from_builtin(42)
- def test_signature_from_functionlike_object(self):
def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs):[](#l3.8)
pass[](#l3.9)
class funclike:[](#l3.11)
# Has to be callable, and have correct[](#l3.12)
# __code__, __annotations__, __defaults__, __name__,[](#l3.13)
# and __kwdefaults__ attributes[](#l3.14)
def __init__(self, func):[](#l3.16)
self.__name__ = func.__name__[](#l3.17)
self.__code__ = func.__code__[](#l3.18)
self.__annotations__ = func.__annotations__[](#l3.19)
self.__defaults__ = func.__defaults__[](#l3.20)
self.__kwdefaults__ = func.__kwdefaults__[](#l3.21)
self.func = func[](#l3.22)
def __call__(self, *args, **kwargs):[](#l3.24)
return self.func(*args, **kwargs)[](#l3.25)
sig_func = inspect.Signature.from_function(func)[](#l3.27)
sig_funclike = inspect.Signature.from_function(funclike(func))[](#l3.29)
self.assertEqual(sig_funclike, sig_func)[](#l3.30)
sig_funclike = inspect.signature(funclike(func))[](#l3.32)
self.assertEqual(sig_funclike, sig_func)[](#l3.33)
# If object is not a duck type of function, then[](#l3.35)
# signature will try to get a signature for its '__call__'[](#l3.36)
# method[](#l3.37)
fl = funclike(func)[](#l3.38)
del fl.__defaults__[](#l3.39)
self.assertEqual(self.signature(fl),[](#l3.40)
((('args', ..., ..., "var_positional"),[](#l3.41)
('kwargs', ..., ..., "var_keyword")),[](#l3.42)
...))[](#l3.43)
- def test_signature_functionlike_class(self):
# We only want to duck type function-like objects,[](#l3.46)
# not classes.[](#l3.47)
def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs):[](#l3.49)
pass[](#l3.50)
class funclike:[](#l3.52)
def __init__(self, marker):[](#l3.53)
pass[](#l3.54)
__name__ = func.__name__[](#l3.56)
__code__ = func.__code__[](#l3.57)
__annotations__ = func.__annotations__[](#l3.58)
__defaults__ = func.__defaults__[](#l3.59)
__kwdefaults__ = func.__kwdefaults__[](#l3.60)
with self.assertRaisesRegex(TypeError, 'is not a Python function'):[](#l3.62)
inspect.Signature.from_function(funclike)[](#l3.63)
self.assertEqual(str(inspect.signature(funclike)), '(marker)')[](#l3.65)
+ def test_signature_on_method(self): class Test: def init(*args):
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Core and Builtins Library ------- +- Issue #17159: inspect.signature now accepts duck types of functions,