8000 bpo-43224: Implement pickling of TypeVarTuples (#32119) · python/cpython@5e130a8 · GitHub
[go: up one dir, main page]

Skip to content

Commit 5e130a8

Browse files
bpo-43224: Implement pickling of TypeVarTuples (#32119)
Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com>
1 parent 2551a6c commit 5e130a8

File tree

2 files changed

+74
-7
lines changed

2 files changed

+74
-7
lines changed

Lib/test/test_typing.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import contextlib
22
import collections
33
from collections import defaultdict
4-
from functools import lru_cache
4+
from functools import lru_cache, wraps
55
import inspect
66
import pickle
77
import re
@@ -70,6 +70,18 @@ def clear_caches(self):
7070
f()
7171

7272

73+
def all_pickle_protocols(test_func):
74+
"""Runs `test_func` with various values for `proto` argument."""
75+
76+
@wraps(test_func)
77+
def wrapper(self):
78+
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
79+
with self.subTest(pickle_proto=proto):
80+
test_func(self, proto=proto)
81+
82+
return wrapper
83+
84+
7385
class Employee:
7486
pass
7587

@@ -911,6 +923,48 @@ class C(Generic[Unpack[Ts]]): pass
911923
self.assertNotEqual(C[Unpack[Ts1]], C[Unpack[Ts2]])
912924

913925

926+
class TypeVarTuplePicklingTests(BaseTestCase):
927+
# These are slightly awkward tests to run, because TypeVarTuples are only
928+
# picklable if defined in the global scope. We therefore need to push
929+
# various things defined in these tests into the global scope with `global`
930+
# statements at the start of each test.
931+
932+
@all_pickle_protocols
933+
def test_pickling_then_unpickling_results_in_same_identity(self, proto):
934+
global Ts1 # See explanation at start of class.
935+
Ts1 = TypeVarTuple('Ts1')
936+
Ts2 = pickle.loads(pickle.dumps(Ts1, proto))
937+
self.assertIs(Ts1, Ts2)
938+
939+
@all_pickle_protocols
940+
def test_pickling_then_unpickling_unpacked_results_in_same_identity(self, proto):
941+
global Ts # See explanation at start of class.
942+
Ts = TypeVarTuple('Ts')
943+
unpacked1 = Unpack[Ts]
944+
unpacked2 = pickle.loads(pickle.dumps(unpacked1, proto))
945+
self.assertIs(unpacked1, unpacked2)
946+
947+
@all_pickle_protocols
948+
def test_pickling_then_unpickling_tuple_with_typevartuple_equality(
949+
self, proto
950+
):
951+
global T, Ts # See explanation at start of class.
952+
T = TypeVar('T')
953+
Ts = TypeVarTuple('Ts')
954+
955+
a1 = Tuple[Unpack[Ts]]
956+
a2 = pickle.loads(pickle.dumps(a1, proto))
957+
self.assertEqual(a1, a2)
958+
959+
a1 = Tuple[T, Unpack[Ts]]
960+
a2 = pickle.loads(pickle.dumps(a1, proto))
961+
self.assertEqual(a1, a2)
962+
963+
a1 = Tuple[int, Unpack[Ts]]
964+
a2 = pickle.loads(pickle.dumps(a1, proto))
965+
self.assertEqual(a1, a2)
966+
967+
914968
class UnionTests(BaseTestCase):
915969

916970
def test_basics(self):

Lib/typing.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,13 @@ def _is_typevar_like(x: Any) -> bool:
867867
return isinstance(x, (TypeVar, ParamSpec)) or _is_unpacked_typevartuple(x)
868868

869869

870+
class _PickleUsingNameMixin:
871+
"""Mixin enabling pickling based on self.__name__."""
872+
873+
def __reduce__(self):
874+
return self.__name__
875+
876+
870877
class _BoundVarianceMixin:
871878
"""Mixin giving __init__ bound and variance arguments.
872879
@@ -903,11 +910,9 @@ def __repr__(self):
903910
prefix = '~'
904911
return prefix + self.__name__
905912

906-
def __reduce__(self):
907-
return self.__name__
908913

909-
910-
class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _root=True):
914+
class TypeVar(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
915+
_root=True):
911916
"""Type variable.
912917
913918
Usage::
@@ -973,7 +978,7 @@ def __typing_subst__(self, arg):
973978
return arg
974979

975980

976-
class TypeVarTuple(_Final, _Immutable, _root=True):
981+
class TypeVarTuple(_Final, _Immutable, _PickleUsingNameMixin, _root=True):
977982
"""Type variable tuple.
978983
979984
Usage:
@@ -994,11 +999,18 @@ class C(Generic[*Ts]): ...
994999
C[()] # Even this is fine
9951000
9961001
For more details, see PEP 646.
1002+
1003+
Note that only TypeVarTuples defined in global scope can be pickled.
9971004
"""
9981005

9991006
def __init__(self, name):
10001007
self.__name__ = name
10011008

1009+
# Used for pickling.
1010+
def_mod = _caller()
1011+
if def_mod != 'typing':
1012+
self.__module__ = def_mod
1013+
10021014
def __iter__(self):
10031015
yield Unpack[self]
10041016

@@ -1057,7 +1069,8 @@ def __eq__(self, other):
10571069
return self.__origin__ == other.__origin__
10581070

10591071

1060-
class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _root=True):
1072+
class ParamSpec(_Final, _Immutable, _BoundVarianceMixin, _PickleUsingNameMixin,
1073+
_root=True):
10611074
"""Parameter specification variable.
10621075
10631076
Usage::

0 commit comments

Comments
 (0)
0