[Python-checkins] cpython: inspect.Signature: Add 'Signature.from_callable' classmethod. Closes #17373

yury.selivanov python-checkins at python.org
Thu Mar 27 17:12:02 CET 2014


http://hg.python.org/cpython/rev/8a4e44473fdd
changeset:   89994:8a4e44473fdd
user:        Yury Selivanov <yselivanov at sprymix.com>
date:        Thu Mar 27 12:09:24 2014 -0400
summary:
  inspect.Signature: Add 'Signature.from_callable' classmethod. Closes #17373

files:
  Doc/library/inspect.rst  |  12 +++
  Doc/whatsnew/3.5.rst     |   4 +
  Lib/inspect.py           |  86 ++++++++++++++++++---------
  Lib/test/test_inspect.py |  13 ++++
  Misc/NEWS                |   2 +
  5 files changed, 87 insertions(+), 30 deletions(-)


diff --git a/Doc/library/inspect.rst b/Doc/library/inspect.rst
--- a/Doc/library/inspect.rst
+++ b/Doc/library/inspect.rst
@@ -506,6 +506,18 @@
          >>> str(new_sig)
          "(a, b) -> 'new return anno'"
 
+    .. classmethod:: Signature.from_callable(obj)
+
+       Return a :class:`Signature` (or its subclass) object for a given callable
+       ``obj``. This method simplifies subclassing of :class:`Signature`:
+
+       ::
+
+         class MySignature(Signature):
+             pass
+         sig = MySignature.from_callable(min)
+         assert isinstance(sig, MySignature)
+
 
 .. class:: Parameter(name, kind, \*, default=Parameter.empty, annotation=Parameter.empty)
 
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -140,6 +140,10 @@
 * :class:`inspect.Signature` and :class:`inspect.Parameter` are now
   picklable (contributed by Yury Selivanov in :issue:`20726`).
 
+* New class method :meth:`inspect.Signature.from_callable`, which makes
+  subclassing of :class:`~inspect.Signature` easier (contributed
+  by Yury Selivanov and Eric Snow in :issue:`17373`).
+
 
 Optimizations
 =============
diff --git a/Lib/inspect.py b/Lib/inspect.py
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -969,7 +969,8 @@
 
         sig = _signature_internal(func,
                                   follow_wrapper_chains=False,
-                                  skip_bound_arg=False)
+                                  skip_bound_arg=False,
+                                  sigcls=Signature)
     except Exception as ex:
         # Most of the times 'signature' will raise ValueError.
         # But, it can also raise AttributeError, and, maybe something
@@ -1861,7 +1862,10 @@
     return _signature_fromstr(cls, func, s, skip_bound_arg)
 
 
-def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
+def _signature_internal(obj, *,
+                        follow_wrapper_chains=True,
+                        skip_bound_arg=True,
+                        sigcls):
 
     if not callable(obj):
         raise TypeError('{!r} is not a callable object'.format(obj))
@@ -1869,9 +1873,12 @@
     if isinstance(obj, types.MethodType):
         # In this case we skip the first parameter of the underlying
         # function (usually `self` or `cls`).
-        sig = _signature_internal(obj.__func__,
-                                  follow_wrapper_chains,
-                                  skip_bound_arg)
+        sig = _signature_internal(
+            obj.__func__,
+            follow_wrapper_chains=follow_wrapper_chains,
+            skip_bound_arg=skip_bound_arg,
+            sigcls=sigcls)
+
         if skip_bound_arg:
             return _signature_bound_method(sig)
         else:
@@ -1902,9 +1909,12 @@
             # (usually `self`, or `cls`) will not be passed
             # automatically (as for boundmethods)
 
-            wrapped_sig = _signature_internal(partialmethod.func,
-                                              follow_wrapper_chains,
-                                              skip_bound_arg)
+            wrapped_sig = _signature_internal(
+                partialmethod.func,
+                follow_wrapper_chains=follow_wrapper_chains,
+                skip_bound_arg=skip_bound_arg,
+                sigcls=sigcls)
+
             sig = _signature_get_partial(wrapped_sig, partialmethod, (None,))
 
             first_wrapped_param = tuple(wrapped_sig.parameters.values())[0]
@@ -1915,16 +1925,18 @@
     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)
+        return sigcls.from_function(obj)
 
     if _signature_is_builtin(obj):
-        return _signature_from_builtin(Signature, obj,
+        return _signature_from_builtin(sigcls, obj,
                                        skip_bound_arg=skip_bound_arg)
 
     if isinstance(obj, functools.partial):
-        wrapped_sig = _signature_internal(obj.func,
-                                          follow_wrapper_chains,
-                                          skip_bound_arg)
+        wrapped_sig = _signature_internal(
+            obj.func,
+            follow_wrapper_chains=follow_wrapper_chains,
+            skip_bound_arg=skip_bound_arg,
+            sigcls=sigcls)
         return _signature_get_partial(wrapped_sig, obj)
 
     sig = None
@@ -1935,23 +1947,29 @@
         # in its metaclass
         call = _signature_get_user_defined_method(type(obj), '__call__')
         if call is not None:
-            sig = _signature_internal(call,
-                                      follow_wrapper_chains,
-                                      skip_bound_arg)
+            sig = _signature_internal(
+                call,
+                follow_wrapper_chains=follow_wrapper_chains,
+                skip_bound_arg=skip_bound_arg,
+                sigcls=sigcls)
         else:
             # Now we check if the 'obj' class has a '__new__' method
             new = _signature_get_user_defined_method(obj, '__new__')
             if new is not None:
-                sig = _signature_internal(new,
-                                          follow_wrapper_chains,
-                                          skip_bound_arg)
+                sig = _signature_internal(
+                    new,
+                    follow_wrapper_chains=follow_wrapper_chains,
+                    skip_bound_arg=skip_bound_arg,
+                    sigcls=sigcls)
             else:
                 # Finally, we should have at least __init__ implemented
                 init = _signature_get_user_defined_method(obj, '__init__')
                 if init is not None:
-                    sig = _signature_internal(init,
-                                              follow_wrapper_chains,
-                                              skip_bound_arg)
+                    sig = _signature_internal(
+                        init,
+                        follow_wrapper_chains=follow_wrapper_chains,
+                        skip_bound_arg=skip_bound_arg,
+                        sigcls=sigcls)
 
         if sig is None:
             # At this point we know, that `obj` is a class, with no user-
@@ -1973,7 +1991,7 @@
                     if text_sig:
                         # If 'obj' class has a __text_signature__ attribute:
                         # return a signature based on it
-                        return _signature_fromstr(Signature, obj, text_sig)
+                        return _signature_fromstr(sigcls, obj, text_sig)
 
             # No '__text_signature__' was found for the 'obj' class.
             # Last option is to check if its '__init__' is
@@ -1993,9 +2011,11 @@
         call = _signature_get_user_defined_method(type(obj), '__call__')
         if call is not None:
             try:
-                sig = _signature_internal(call,
-                                          follow_wrapper_chains,
-                                          skip_bound_arg)
+                sig = _signature_internal(
+                    call,
+                    follow_wrapper_chains=follow_wrapper_chains,
+                    skip_bound_arg=skip_bound_arg,
+                    sigcls=sigcls)
             except ValueError as ex:
                 msg = 'no signature found for {!r}'.format(obj)
                 raise ValueError(msg) from ex
@@ -2015,10 +2035,6 @@
 
     raise ValueError('callable {!r} is not supported by signature'.format(obj))
 
-def signature(obj):
-    '''Get a signature object for the passed callable.'''
-    return _signature_internal(obj)
-
 
 class _void:
     '''A private marker - used in Parameter & Signature'''
@@ -2464,6 +2480,10 @@
     def from_builtin(cls, func):
         return _signature_from_builtin(cls, func)
 
+    @classmethod
+    def from_callable(cls, obj):
+        return _signature_internal(obj, sigcls=cls)
+
     @property
     def parameters(self):
         return self._parameters
@@ -2723,6 +2743,12 @@
 
         return rendered
 
+
+def signature(obj):
+    '''Get a signature object for the passed callable.'''
+    return Signature.from_callable(obj)
+
+
 def _main():
     """ Logic for inspecting an object given at command line """
     import argparse
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
@@ -2517,6 +2517,19 @@
         self.assertEqual(self.signature(Spam.foo),
                          self.signature(Ham.foo))
 
+    def test_signature_from_callable_python_obj(self):
+        class MySignature(inspect.Signature): pass
+        def foo(a, *, b:1): pass
+        foo_sig = MySignature.from_callable(foo)
+        self.assertTrue(isinstance(foo_sig, MySignature))
+
+    @unittest.skipIf(MISSING_C_DOCSTRINGS,
+                     "Signature information for builtins requires docstrings")
+    def test_signature_from_callable_builtin_obj(self):
+        class MySignature(inspect.Signature): pass
+        sig = MySignature.from_callable(_pickle.Pickler)
+        self.assertTrue(isinstance(sig, MySignature))
+
 
 class TestParameterObject(unittest.TestCase):
     def test_signature_parameter_kinds(self):
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -109,6 +109,8 @@
 
 - Issue #20726: inspect.signature: Make Signature and Parameter picklable.
 
+- Issue #17373: Add inspect.Signature.from_callable method.
+
 Documentation
 -------------
 

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


More information about the Python-checkins mailing list