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`
|
* :class:`xmlrpc.client.ServerProxy` is now a :term:`context manager`
|
||||||
(contributed by Claudiu Popa in :issue:`20627`).
|
(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
|
Optimizations
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -2106,6 +2106,18 @@ class Parameter:
|
||||||
|
|
||||||
self._partial_kwarg = _partial_kwarg
|
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
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
return self._name
|
return self._name
|
||||||
|
@ -2658,6 +2670,14 @@ class Signature:
|
||||||
'''
|
'''
|
||||||
return args[0]._bind(args[1:], kwargs, partial=True)
|
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):
|
def __str__(self):
|
||||||
result = []
|
result = []
|
||||||
render_pos_only_separator = False
|
render_pos_only_separator = False
|
||||||
|
|
|
@ -8,6 +8,7 @@ import linecache
|
||||||
import os
|
import os
|
||||||
from os.path import normcase
|
from os.path import normcase
|
||||||
import _pickle
|
import _pickle
|
||||||
|
import pickle
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
@ -73,6 +74,7 @@ def generator_function_example(self):
|
||||||
for i in range(2):
|
for i in range(2):
|
||||||
yield i
|
yield i
|
||||||
|
|
||||||
|
|
||||||
class TestPredicates(IsTestBase):
|
class TestPredicates(IsTestBase):
|
||||||
def test_sixteen(self):
|
def test_sixteen(self):
|
||||||
count = len([x for x in dir(inspect) if x.startswith('is')])
|
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))
|
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):
|
class TestSignatureObject(unittest.TestCase):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def signature(func):
|
def signature(func):
|
||||||
|
@ -1654,6 +1667,36 @@ class TestSignatureObject(unittest.TestCase):
|
||||||
with self.assertRaisesRegex(ValueError, 'follows default argument'):
|
with self.assertRaisesRegex(ValueError, 'follows default argument'):
|
||||||
S((pkd, pk))
|
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_signature_immutability(self):
|
||||||
def test(a):
|
def test(a):
|
||||||
pass
|
pass
|
||||||
|
@ -2845,6 +2888,16 @@ class TestBoundArguments(unittest.TestCase):
|
||||||
ba4 = inspect.signature(bar).bind(1)
|
ba4 = inspect.signature(bar).bind(1)
|
||||||
self.assertNotEqual(ba, ba4)
|
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):
|
class TestSignaturePrivateHelpers(unittest.TestCase):
|
||||||
def test_signature_get_bound_param(self):
|
def test_signature_get_bound_param(self):
|
||||||
|
|
|
@ -107,6 +107,8 @@ Library
|
||||||
|
|
||||||
- Issue #19573: inspect.signature: Use enum for parameter kind constants.
|
- Issue #19573: inspect.signature: Use enum for parameter kind constants.
|
||||||
|
|
||||||
|
- Issue #20726: inspect.signature: Make Signature and Parameter picklable.
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue