diff options
-rw-r--r-- | Lib/test/test_typing.py | 32 | ||||
-rw-r--r-- | Lib/typing.py | 34 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2018-03-24-19-54-48.bpo-32873.cHyoAm.rst | 3 |
3 files changed, 58 insertions, 11 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index f56caa1..09e39fe 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1057,20 +1057,20 @@ class GenericTests(BaseTestCase): self.assertEqual(x.foo, 42) self.assertEqual(x.bar, 'abc') self.assertEqual(x.__dict__, {'foo': 42, 'bar': 'abc'}) - samples = [Any, Union, Tuple, Callable, ClassVar] + samples = [Any, Union, Tuple, Callable, ClassVar, + Union[int, str], ClassVar[List], Tuple[int, ...], Callable[[str], bytes]] for s in samples: for proto in range(pickle.HIGHEST_PROTOCOL + 1): z = pickle.dumps(s, proto) x = pickle.loads(z) self.assertEqual(s, x) - more_samples = [List, typing.Iterable, typing.Type] + more_samples = [List, typing.Iterable, typing.Type, List[int], + typing.Type[typing.Mapping]] for s in more_samples: for proto in range(pickle.HIGHEST_PROTOCOL + 1): z = pickle.dumps(s, proto) x = pickle.loads(z) - self.assertEqual(repr(s), repr(x)) # TODO: fix this - # see also comment in test_copy_and_deepcopy - # the issue is typing/#512 + self.assertEqual(s, x) def test_copy_and_deepcopy(self): T = TypeVar('T') @@ -1082,7 +1082,27 @@ class GenericTests(BaseTestCase): Union['T', int], List['T'], typing.Mapping['T', int]] for t in things + [Any]: self.assertEqual(t, copy(t)) - self.assertEqual(repr(t), repr(deepcopy(t))) # Use repr() because of TypeVars + self.assertEqual(t, deepcopy(t)) + + def test_immutability_by_copy_and_pickle(self): + # Special forms like Union, Any, etc., generic aliases to containers like List, + # Mapping, etc., and type variabcles are considered immutable by copy and pickle. + global TP, TPB, TPV # for pickle + TP = TypeVar('TP') + TPB = TypeVar('TPB', bound=int) + TPV = TypeVar('TPV', bytes, str) + for X in [TP, TPB, TPV, List, typing.Mapping, ClassVar, typing.Iterable, + Union, Any, Tuple, Callable]: + self.assertIs(copy(X), X) + self.assertIs(deepcopy(X), X) + self.assertIs(pickle.loads(pickle.dumps(X)), X) + # Check that local type variables are copyable. + TL = TypeVar('TL') + TLB = TypeVar('TLB', bound=int) + TLV = TypeVar('TLV', bytes, str) + for X in [TL, TLB, TLV]: + self.assertIs(copy(X), X) + self.assertIs(deepcopy(X), X) def test_copy_generic_instances(self): T = TypeVar('T') diff --git a/Lib/typing.py b/Lib/typing.py index 56126cf..510574c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -285,8 +285,17 @@ class _Final: if '_root' not in kwds: raise TypeError("Cannot subclass special typing classes") +class _Immutable: + """Mixin to indicate that object should not be copied.""" -class _SpecialForm(_Final, _root=True): + def __copy__(self): + return self + + def __deepcopy__(self, memo): + return self + + +class _SpecialForm(_Final, _Immutable, _root=True): """Internal indicator of special typing constructs. See _doc instance attribute for specific docs. """ @@ -328,8 +337,8 @@ class _SpecialForm(_Final, _root=True): def __repr__(self): return 'typing.' + self._name - def __copy__(self): - return self # Special forms are immutable. + def __reduce__(self): + return self._name def __call__(self, *args, **kwds): raise TypeError(f"Cannot instantiate {self!r}") @@ -496,7 +505,11 @@ class ForwardRef(_Final, _root=True): return f'ForwardRef({self.__forward_arg__!r})' -class TypeVar(_Final, _root=True): +def _find_name(mod, name): + return getattr(sys.modules[mod], name) + + +class TypeVar(_Final, _Immutable, _root=True): """Type variable. Usage:: @@ -536,10 +549,12 @@ class TypeVar(_Final, _root=True): T.__covariant__ == False T.__contravariant__ = False A.__constraints__ == (str, bytes) + + Note that only type variables defined in global scope can be pickled. """ __slots__ = ('__name__', '__bound__', '__constraints__', - '__covariant__', '__contravariant__') + '__covariant__', '__contravariant__', '_def_mod') def __init__(self, name, *constraints, bound=None, covariant=False, contravariant=False): @@ -558,6 +573,7 @@ class TypeVar(_Final, _root=True): self.__bound__ = _type_check(bound, "Bound must be a type.") else: self.__bound__ = None + self._def_mod = sys._getframe(1).f_globals['__name__'] # for pickling def __getstate__(self): return {'name': self.__name__, @@ -582,6 +598,9 @@ class TypeVar(_Final, _root=True): prefix = '~' return prefix + self.__name__ + def __reduce__(self): + return (_find_name, (self._def_mod, self.__name__)) + # Special typing constructs Union, Optional, Generic, Callable and Tuple # use three special attributes for internal bookkeeping of generic types: @@ -724,6 +743,11 @@ class _GenericAlias(_Final, _root=True): raise TypeError("Subscripted generics cannot be used with" " class and instance checks") + def __reduce__(self): + if self._special: + return self._name + return super().__reduce__() + class _VariadicGenericAlias(_GenericAlias, _root=True): """Same as _GenericAlias above but for variadic aliases. Currently, diff --git a/Misc/NEWS.d/next/Library/2018-03-24-19-54-48.bpo-32873.cHyoAm.rst b/Misc/NEWS.d/next/Library/2018-03-24-19-54-48.bpo-32873.cHyoAm.rst new file mode 100644 index 0000000..99f8088 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-03-24-19-54-48.bpo-32873.cHyoAm.rst @@ -0,0 +1,3 @@ +Treat type variables and special typing forms as immutable by copy and +pickle. This fixes several minor issues and inconsistencies, and improves +backwards compatibility with Python 3.6. |