inspect.signature: Make Signature and Parameter picklable. Closes #20726

This commit is contained in:
Yury Selivanov 2014-03-27 11:31:43 -04:00
parent 21e83a5564
commit a5d63dd7b8
4 changed files with 78 additions and 0 deletions

View File

@ -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
============= =============

View File

@ -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

View File

@ -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):

View File

@ -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
------------- -------------