inspect.signature: Make Signature and Parameter picklable. Closes #20726
This commit is contained in:
parent
21e83a5564
commit
a5d63dd7b8
|
@ -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
|
||||
=============
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue