]> granicus.if.org Git - python/commitdiff
inspect.Signature: Add 'Signature.from_callable' classmethod. Closes #17373
authorYury Selivanov <yselivanov@sprymix.com>
Thu, 27 Mar 2014 16:09:24 +0000 (12:09 -0400)
committerYury Selivanov <yselivanov@sprymix.com>
Thu, 27 Mar 2014 16:09:24 +0000 (12:09 -0400)
Doc/library/inspect.rst
Doc/whatsnew/3.5.rst
Lib/inspect.py
Lib/test/test_inspect.py
Misc/NEWS

index 0c087123e80d1326a394a8fec5de369e44e4e4cd..85d9593693b0637b9b00ef9e884e352930676b30 100644 (file)
@@ -506,6 +506,18 @@ function.
          >>> 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)
 
index c94102da241cef0a7e678be3a604b2ee57e5b6a5..78bddcb9536055582ee5ad4b6aa7c246a794840f 100644 (file)
@@ -140,6 +140,10 @@ Improved Modules
 * :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
 =============
index bce0516a090026ea8b674b634249a32b56946650..acb80714ffadc9223cf713574fd7761df44e7890 100644 (file)
@@ -969,7 +969,8 @@ def getfullargspec(func):
 
         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 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True):
     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 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
     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 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
             # (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 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
     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 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
         # 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 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
                     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 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
         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 @@ def _signature_internal(obj, follow_wrapper_chains=True, skip_bound_arg=True):
 
     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 @@ class Signature:
     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 @@ class Signature:
 
         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
index 373bd4c360f9049678d48b32aaeda6f8e2dfe2cf..5bdd327903d798016d5e5946d0ad2572dd3ece82 100644 (file)
@@ -2517,6 +2517,19 @@ class TestSignatureObject(unittest.TestCase):
         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):
index 20e8c29c8cb16b72486c03db13d99fbf8707d73e..b53c518b907f2a050a6b4b14b963c186426b59e6 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -109,6 +109,8 @@ Library
 
 - Issue #20726: inspect.signature: Make Signature and Parameter picklable.
 
+- Issue #17373: Add inspect.Signature.from_callable method.
+
 Documentation
 -------------