[Python-checkins] cpython: inspect.signature: Support duck-types of Python functions (Cython, for

yury.selivanov python-checkins at python.org
Fri Jan 31 20:48:52 CET 2014


http://hg.python.org/cpython/rev/32a660a52aae
changeset:   88866:32a660a52aae
user:        Yury Selivanov <yselivanov at sprymix.com>
date:        Fri Jan 31 14:48:37 2014 -0500
summary:
  inspect.signature: Support duck-types of Python functions (Cython, for instance) #17159

files:
  Doc/whatsnew/3.4.rst     |   4 +
  Lib/inspect.py           |  32 ++++++++++++++-
  Lib/test/test_inspect.py |  60 ++++++++++++++++++++++++++++
  Misc/NEWS                |   3 +
  4 files changed, 97 insertions(+), 2 deletions(-)


diff --git a/Doc/whatsnew/3.4.rst b/Doc/whatsnew/3.4.rst
--- a/Doc/whatsnew/3.4.rst
+++ b/Doc/whatsnew/3.4.rst
@@ -793,6 +793,10 @@
 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
 -------
diff --git a/Lib/inspect.py b/Lib/inspect.py
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1601,6 +1601,30 @@
             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
@@ -1670,7 +1694,9 @@
     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):
@@ -2071,7 +2097,9 @@
     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
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -1740,6 +1740,66 @@
         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):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -13,6 +13,9 @@
 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__.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list