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
-------
obj in (type, object))
+def _signature_is_functionlike(obj):
+ # Internal helper to test if `obj` 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,
+ # and not classes.
+ return False
+
+ 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
+ (defaults is None or isinstance(defaults, tuple)) and
+ (kwdefaults is None or isinstance(kwdefaults, dict)) and
+ isinstance(annotations, dict))
+
+
def _signature_get_bound_param(spec):
# Internal helper to get first parameter name from a
# __text_signature__ of a builtin method, which should
if _signature_is_builtin(obj):
return Signature.from_builtin(obj)
- if isinstance(obj, types.FunctionType):
+ 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)
if isinstance(obj, functools.partial):
def from_function(cls, func):
'''Constructs Signature for the given python function'''
- if not isinstance(func, types.FunctionType):
+ if not (isfunction(func) or _signature_is_functionlike(func)):
+ # If it's not a pure Python function, and not a duck type
+ # of pure function:
raise TypeError('{!r} is not a Python function'.format(func))
Parameter = cls._parameter_cls
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):
+ pass
+
+ class funclike:
+ # Has to be callable, and have correct
+ # __code__, __annotations__, __defaults__, __name__,
+ # and __kwdefaults__ attributes
+
+ def __init__(self, func):
+ self.__name__ = func.__name__
+ self.__code__ = func.__code__
+ self.__annotations__ = func.__annotations__
+ self.__defaults__ = func.__defaults__
+ self.__kwdefaults__ = func.__kwdefaults__
+ self.func = func
+
+ def __call__(self, *args, **kwargs):
+ return self.func(*args, **kwargs)
+
+ sig_func = inspect.Signature.from_function(func)
+
+ sig_funclike = inspect.Signature.from_function(funclike(func))
+ self.assertEqual(sig_funclike, sig_func)
+
+ sig_funclike = inspect.signature(funclike(func))
+ self.assertEqual(sig_funclike, sig_func)
+
+ # If object is not a duck type of function, then
+ # signature will try to get a signature for its '__call__'
+ # method
+ fl = funclike(func)
+ del fl.__defaults__
+ self.assertEqual(self.signature(fl),
+ ((('args', ..., ..., "var_positional"),
+ ('kwargs', ..., ..., "var_keyword")),
+ ...))
+
+ def test_signature_functionlike_class(self):
+ # We only want to duck type function-like objects,
+ # not classes.
+
+ def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs):
+ pass
+
+ class funclike:
+ def __init__(self, marker):
+ pass
+
+ __name__ = func.__name__
+ __code__ = func.__code__
+ __annotations__ = func.__annotations__
+ __defaults__ = func.__defaults__
+ __kwdefaults__ = func.__kwdefaults__
+
+ with self.assertRaisesRegex(TypeError, 'is not a Python function'):
+ inspect.Signature.from_function(funclike)
+
+ self.assertEqual(str(inspect.signature(funclike)), '(marker)')
+
def test_signature_on_method(self):
class Test:
def __init__(*args):
Library
-------
+- Issue #17159: inspect.signature now accepts duck types of functions,
+ which adds support for Cython functions. Initial patch by Stefan Behnel.
+
- Issue #18801: Fix inspect.classify_class_attrs to correctly classify
object.__new__ and object.__init__.