]> granicus.if.org Git - python/commitdiff
inspect.signature: Make Signature and Parameter picklable. Closes #20726
authorYury Selivanov <yselivanov@sprymix.com>
Thu, 27 Mar 2014 15:31:43 +0000 (11:31 -0400)
committerYury Selivanov <yselivanov@sprymix.com>
Thu, 27 Mar 2014 15:31:43 +0000 (11:31 -0400)
Doc/whatsnew/3.5.rst
Lib/inspect.py
Lib/test/test_inspect.py
Misc/NEWS

index b18bcd2d79ddb9da4fa15131410037166e2e3463..c94102da241cef0a7e678be3a604b2ee57e5b6a5 100644 (file)
@@ -137,6 +137,9 @@ Improved Modules
 * :class:`xmlrpc.client.ServerProxy` is now a :term:`context manager`
   (contributed by Claudiu Popa in :issue:`20627`).
 
+* :class:`inspect.Signature` and :class:`inspect.Parameter` are now
+  picklable (contributed by Yury Selivanov in :issue:`20726`).
+
 
 Optimizations
 =============
index 6054c390a0f42b15170a3a12696c4723800a127a..bce0516a090026ea8b674b634249a32b56946650 100644 (file)
@@ -2106,6 +2106,18 @@ class Parameter:
 
         self._partial_kwarg = _partial_kwarg
 
+    def __reduce__(self):
+        return (type(self),
+                (self._name, self._kind),
+                {'_partial_kwarg': self._partial_kwarg,
+                 '_default': self._default,
+                 '_annotation': self._annotation})
+
+    def __setstate__(self, state):
+        self._partial_kwarg = state['_partial_kwarg']
+        self._default = state['_default']
+        self._annotation = state['_annotation']
+
     @property
     def name(self):
         return self._name
@@ -2658,6 +2670,14 @@ class Signature:
         '''
         return args[0]._bind(args[1:], kwargs, partial=True)
 
+    def __reduce__(self):
+        return (type(self),
+                (tuple(self._parameters.values()),),
+                {'_return_annotation': self._return_annotation})
+
+    def __setstate__(self, state):
+        self._return_annotation = state['_return_annotation']
+
     def __str__(self):
         result = []
         render_pos_only_separator = False
index 5c6ae394d52083d6fc57adaac46e8b9a8b4cef4f..373bd4c360f9049678d48b32aaeda6f8e2dfe2cf 100644 (file)
@@ -8,6 +8,7 @@ import linecache
 import os
 from os.path import normcase
 import _pickle
+import pickle
 import re
 import shutil
 import sys
@@ -73,6 +74,7 @@ def generator_function_example(self):
     for i in range(2):
         yield i
 
+
 class TestPredicates(IsTestBase):
     def test_sixteen(self):
         count = len([x for x in dir(inspect) if x.startswith('is')])
@@ -1597,6 +1599,17 @@ class TestGetGeneratorState(unittest.TestCase):
         self.assertRaises(TypeError, inspect.getgeneratorlocals, (2,3))
 
 
+class MySignature(inspect.Signature):
+    # Top-level to make it picklable;
+    # used in test_signature_object_pickle
+    pass
+
+class MyParameter(inspect.Parameter):
+    # Top-level to make it picklable;
+    # used in test_signature_object_pickle
+    pass
+
+
 class TestSignatureObject(unittest.TestCase):
     @staticmethod
     def signature(func):
@@ -1654,6 +1667,36 @@ class TestSignatureObject(unittest.TestCase):
         with self.assertRaisesRegex(ValueError, 'follows default argument'):
             S((pkd, pk))
 
+    def test_signature_object_pickle(self):
+        def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass
+        foo_partial = functools.partial(foo, a=1)
+
+        sig = inspect.signature(foo_partial)
+        self.assertTrue(sig.parameters['a']._partial_kwarg)
+
+        for ver in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.subTest(pickle_ver=ver, subclass=False):
+                sig_pickled = pickle.loads(pickle.dumps(sig, ver))
+                self.assertEqual(sig, sig_pickled)
+                self.assertTrue(sig_pickled.parameters['a']._partial_kwarg)
+
+        # Test that basic sub-classing works
+        sig = inspect.signature(foo)
+        myparam = MyParameter(name='z', kind=inspect.Parameter.POSITIONAL_ONLY)
+        myparams = collections.OrderedDict(sig.parameters, a=myparam)
+        mysig = MySignature().replace(parameters=myparams.values(),
+                                      return_annotation=sig.return_annotation)
+        self.assertTrue(isinstance(mysig, MySignature))
+        self.assertTrue(isinstance(mysig.parameters['z'], MyParameter))
+
+        for ver in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.subTest(pickle_ver=ver, subclass=True):
+                sig_pickled = pickle.loads(pickle.dumps(mysig, ver))
+                self.assertEqual(mysig, sig_pickled)
+                self.assertTrue(isinstance(sig_pickled, MySignature))
+                self.assertTrue(isinstance(sig_pickled.parameters['z'],
+                                           MyParameter))
+
     def test_signature_immutability(self):
         def test(a):
             pass
@@ -2845,6 +2888,16 @@ class TestBoundArguments(unittest.TestCase):
         ba4 = inspect.signature(bar).bind(1)
         self.assertNotEqual(ba, ba4)
 
+    def test_signature_bound_arguments_pickle(self):
+        def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass
+        sig = inspect.signature(foo)
+        ba = sig.bind(20, 30, z={})
+
+        for ver in range(pickle.HIGHEST_PROTOCOL + 1):
+            with self.subTest(pickle_ver=ver):
+                ba_pickled = pickle.loads(pickle.dumps(ba, ver))
+                self.assertEqual(ba, ba_pickled)
+
 
 class TestSignaturePrivateHelpers(unittest.TestCase):
     def test_signature_get_bound_param(self):
index 854f740b0173acc3cced3d1247232f936e200148..20e8c29c8cb16b72486c03db13d99fbf8707d73e 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -107,6 +107,8 @@ Library
 
 - Issue #19573: inspect.signature: Use enum for parameter kind constants.
 
+- Issue #20726: inspect.signature: Make Signature and Parameter picklable.
+
 Documentation
 -------------