Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ca1b794

Browse files
committed
Close issue20534: all pickle protocols now supported.
1 parent 01e46ee commit ca1b794

3 files changed

Lines changed: 233 additions & 21 deletions

File tree

Doc/library/enum.rst

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -369,10 +369,10 @@ The usual restrictions for pickling apply: picklable enums must be defined in
369369
the top level of a module, since unpickling requires them to be importable
370370
from that module.
371371

372-
.. warning::
372+
.. note::
373373

374-
In order to support the singleton nature of enumeration members, pickle
375-
protocol version 2 or higher must be used.
374+
With pickle protocol version 4 it is possible to easily pickle enums
375+
nested in other classes.
376376

377377

378378
Functional API
@@ -420,6 +420,14 @@ The solution is to specify the module name explicitly as follows::
420420

421421
>>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__)
422422

423+
The new pickle protocol 4 also, in some circumstances, relies on
424+
:attr:``__qualname__`` being set to the location where pickle will be able
425+
to find the class. For example, if the class was made available in class
426+
SomeData in the global scope::
427+
428+
>>> Animals = Enum('Animals', 'ant bee cat dog', qualname='SomeData.Animals')
429+
430+
423431
Derived Enumerations
424432
--------------------
425433

Lib/enum.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ def _is_sunder(name):
3131

3232
def _make_class_unpicklable(cls):
3333
"""Make the given class un-picklable."""
34-
def _break_on_call_reduce(self):
34+
def _break_on_call_reduce(self, proto):
3535
raise TypeError('%r cannot be pickled' % self)
36-
cls.__reduce__ = _break_on_call_reduce
36+
cls.__reduce_ex__ = _break_on_call_reduce
3737
cls.__module__ = '<unknown>'
3838

3939

@@ -115,12 +115,13 @@ def __new__(metacls, cls, bases, classdict):
115115
# Reverse value->name map for hashable values.
116116
enum_class._value2member_map_ = {}
117117

118-
# check for a __getnewargs__, and if not present sabotage
118+
# check for a supported pickle protocols, and if not present sabotage
119119
# pickling, since it won't work anyway
120-
if (member_type is not object and
121-
member_type.__dict__.get('__getnewargs__') is None
122-
):
123-
_make_class_unpicklable(enum_class)
120+
if member_type is not object:
121+
methods = ('__getnewargs_ex__', '__getnewargs__',
122+
'__reduce_ex__', '__reduce__')
123+
if not any(map(member_type.__dict__.get, methods)):
124+
_make_class_unpicklable(enum_class)
124125

125126
# instantiate them, checking for duplicates as we go
126127
# we instantiate first instead of checking for duplicates first in case
@@ -166,7 +167,7 @@ def __new__(metacls, cls, bases, classdict):
166167

167168
# double check that repr and friends are not the mixin's or various
168169
# things break (such as pickle)
169-
for name in ('__repr__', '__str__', '__format__', '__getnewargs__'):
170+
for name in ('__repr__', '__str__', '__format__', '__getnewargs__', '__reduce_ex__'):
170171
class_method = getattr(enum_class, name)
171172
obj_method = getattr(member_type, name, None)
172173
enum_method = getattr(first_enum, name, None)
@@ -183,7 +184,7 @@ def __new__(metacls, cls, bases, classdict):
183184
enum_class.__new__ = Enum.__new__
184185
return enum_class
185186

186-
def __call__(cls, value, names=None, *, module=None, type=None):
187+
def __call__(cls, value, names=None, *, module=None, qualname=None, type=None):
187188
"""Either returns an existing member, or creates a new enum class.
188189
189190
This method is used both when an enum class is given a value to match
@@ -202,7 +203,7 @@ def __call__(cls, value, names=None, *, module=None, type=None):
202203
if names is None: # simple value lookup
203204
return cls.__new__(cls, value)
204205
# otherwise, functional API: we're creating a new Enum type
205-
return cls._create_(value, names, module=module, type=type)
206+
return cls._create_(value, names, module=module, qualname=qualname, type=type)
206207

207208
def __contains__(cls, member):
208209
return isinstance(member, cls) and member.name in cls._member_map_
@@ -273,7 +274,7 @@ def __setattr__(cls, name, value):
273274
raise AttributeError('Cannot reassign members.')
274275
super().__setattr__(name, value)
275276

276-
def _create_(cls, class_name, names=None, *, module=None, type=None):
277+
def _create_(cls, class_name, names=None, *, module=None, qualname=None, type=None):
277278
"""Convenience method to create a new Enum class.
278279
279280
`names` can be:
@@ -315,6 +316,8 @@ def _create_(cls, class_name, names=None, *, module=None, type=None):
315316
_make_class_unpicklable(enum_class)
316317
else:
317318
enum_class.__module__ = module
319+
if qualname is not None:
320+
enum_class.__qualname__ = qualname
318321

319322
return enum_class
320323

@@ -468,6 +471,9 @@ def __getnewargs__(self):
468471
def __hash__(self):
469472
return hash(self._name_)
470473

474+
def __reduce_ex__(self, proto):
475+
return self.__class__, self.__getnewargs__()
476+
471477
# DynamicClassAttribute is used to provide access to the `name` and
472478
# `value` properties of enum members while keeping some measure of
473479
# protection from modification, while still allowing for an enumeration

Lib/test/test_enum.py

Lines changed: 205 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ class Name(StrEnum):
5252
except Exception as exc:
5353
Answer = exc
5454

55+
try:
56+
Theory = Enum('Theory', 'rule law supposition', qualname='spanish_inquisition')
57+
except Exception as exc:
58+
Theory = exc
59+
5560
# for doctests
5661
try:
5762
class Fruit(Enum):
@@ -61,14 +66,18 @@ class Fruit(Enum):
6166
except Exception:
6267
pass
6368

64-
def test_pickle_dump_load(assertion, source, target=None):
69+
def test_pickle_dump_load(assertion, source, target=None,
70+
*, protocol=(0, HIGHEST_PROTOCOL)):
71+
start, stop = protocol
6572
if target is None:
6673
target = source
67-
for protocol in range(2, HIGHEST_PROTOCOL+1):
74+
for protocol in range(start, stop+1):
6875
assertion(loads(dumps(source, protocol=protocol)), target)
6976

70-
def test_pickle_exception(assertion, exception, obj):
71-
for protocol in range(2, HIGHEST_PROTOCOL+1):
77+
def test_pickle_exception(assertion, exception, obj,
78+
*, protocol=(0, HIGHEST_PROTOCOL)):
79+
start, stop = protocol
80+
for protocol in range(start, stop+1):
7281
with assertion(exception):
7382
dumps(obj, protocol=protocol)
7483

@@ -101,6 +110,7 @@ def test_is_dunder(self):
101110

102111

103112
class TestEnum(unittest.TestCase):
113+
104114
def setUp(self):
105115
class Season(Enum):
106116
SPRING = 1
@@ -540,11 +550,31 @@ def test_pickle_enum_function_with_module(self):
540550
test_pickle_dump_load(self.assertIs, Question.who)
541551
test_pickle_dump_load(self.assertIs, Question)
542552

553+
def test_enum_function_with_qualname(self):
554+
if isinstance(Theory, Exception):
555+
raise Theory
556+
self.assertEqual(Theory.__qualname__, 'spanish_inquisition')
557+
558+
def test_class_nested_enum_and_pickle_protocol_four(self):
559+
# would normally just have this directly in the class namespace
560+
class NestedEnum(Enum):
561+
twigs = 'common'
562+
shiny = 'rare'
563+
564+
self.__class__.NestedEnum = NestedEnum
565+
self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__
566+
test_pickle_exception(
567+
self.assertRaises, PicklingError, self.NestedEnum.twigs,
568+
protocol=(0, 3))
569+
test_pickle_dump_load(self.assertIs, self.NestedEnum.twigs,
570+
protocol=(4, HIGHEST_PROTOCOL))
571+
543572
def test_exploding_pickle(self):
544-
BadPickle = Enum('BadPickle', 'dill sweet bread-n-butter')
545-
BadPickle.__qualname__ = 'BadPickle' # needed for pickle protocol 4
573+
BadPickle = Enum(
574+
'BadPickle', 'dill sweet bread-n-butter', module=__name__)
546575
globals()['BadPickle'] = BadPickle
547-
enum._make_class_unpicklable(BadPickle) # will overwrite __qualname__
576+
# now break BadPickle to test exception raising
577+
enum._make_class_unpicklable(BadPickle)
548578
test_pickle_exception(self.assertRaises, TypeError, BadPickle.dill)
549579
test_pickle_exception(self.assertRaises, PicklingError, BadPickle)
550580

@@ -927,6 +957,174 @@ class NEI(NamedInt, Enum):
927957
self.assertEqual(NEI.y.value, 2)
928958
test_pickle_dump_load(self.assertIs, NEI.y)
929959

960+
def test_subclasses_with_getnewargs_ex(self):
961+
class NamedInt(int):
962+
__qualname__ = 'NamedInt' # needed for pickle protocol 4
963+
def __new__(cls, *args):
964+
_args = args
965+
name, *args = args
966+
if len(args) == 0:
967+
raise TypeError("name and value must be specified")
968+
self = int.__new__(cls, *args)
969+
self._intname = name
970+
self._args = _args
971+
return self
972+
def __getnewargs_ex__(self):
973+
return self._args, {}
974+
@property
975+
def __name__(self):
976+
return self._intname
977+
def __repr__(self):
978+
# repr() is updated to include the name and type info
979+
return "{}({!r}, {})".format(type(self).__name__,
980+
self.__name__,
981+
int.__repr__(self))
982+
def __str__(self):
983+
# str() is unchanged, even if it relies on the repr() fallback
984+
base = int
985+
base_str = base.__str__
986+
if base_str.__objclass__ is object:
987+
return base.__repr__(self)
988+
return base_str(self)
989+
# for simplicity, we only define one operator that
990+
# propagates expressions
991+
def __add__(self, other):
992+
temp = int(self) + int( other)
993+
if isinstance(self, NamedInt) and isinstance(other, NamedInt):
994+
return NamedInt(
995+
'({0} + {1})'.format(self.__name__, other.__name__),
996+
temp )
997+
else:
998+
return temp
999+
1000+
class NEI(NamedInt, Enum):
1001+
__qualname__ = 'NEI' # needed for pickle protocol 4
1002+
x = ('the-x', 1)
1003+
y = ('the-y', 2)
1004+
1005+
1006+
self.assertIs(NEI.__new__, Enum.__new__)
1007+
self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
1008+
globals()['NamedInt'] = NamedInt
1009+
globals()['NEI'] = NEI
1010+
NI5 = NamedInt('test', 5)
1011+
self.assertEqual(NI5, 5)
1012+
test_pickle_dump_load(self.assertEqual, NI5, 5, protocol=(4, 4))
1013+
self.assertEqual(NEI.y.value, 2)
1014+
test_pickle_dump_load(self.assertIs, NEI.y, protocol=(4, 4))
1015+
1016+
def test_subclasses_with_reduce(self):
1017+
class NamedInt(int):
1018+
__qualname__ = 'NamedInt' # needed for pickle protocol 4
1019+
def __new__(cls, *args):
1020+
_args = args
1021+
name, *args = args
1022+
if len(args) == 0:
1023+
raise TypeError("name and value must be specified")
1024+
self = int.__new__(cls, *args)
1025+
self._intname = name
1026+
self._args = _args
1027+
return self
1028+
def __reduce__(self):
1029+
return self.__class__, self._args
1030+
@property
1031+
def __name__(self):
1032+
return self._intname
1033+
def __repr__(self):
1034+
# repr() is updated to include the name and type info
1035+
return "{}({!r}, {})".format(type(self).__name__,
1036+
self.__name__,
1037+
int.__repr__(self))
1038+
def __str__(self):
1039+
# str() is unchanged, even if it relies on the repr() fallback
1040+
base = int
1041+
base_str = base.__str__
1042+
if base_str.__objclass__ is object:
1043+
return base.__repr__(self)
1044+
return base_str(self)
1045+
# for simplicity, we only define one operator that
1046+
# propagates expressions
1047+
def __add__(self, other):
1048+
temp = int(self) + int( other)
1049+
if isinstance(self, NamedInt) and isinstance(other, NamedInt):
1050+
return NamedInt(
1051+
'({0} + {1})'.format(self.__name__, other.__name__),
1052+
temp )
1053+
else:
1054+
return temp
1055+
1056+
class NEI(NamedInt, Enum):
1057+
__qualname__ = 'NEI' # needed for pickle protocol 4
1058+
x = ('the-x', 1)
1059+
y = ('the-y', 2)
1060+
1061+
1062+
self.assertIs(NEI.__new__, Enum.__new__)
1063+
self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
1064+
globals()['NamedInt'] = NamedInt
1065+
globals()['NEI'] = NEI
1066+
NI5 = NamedInt('test', 5)
1067+
self.assertEqual(NI5, 5)
1068+
test_pickle_dump_load(self.assertEqual, NI5, 5)
1069+
self.assertEqual(NEI.y.value, 2)
1070+
test_pickle_dump_load(self.assertIs, NEI.y)
1071+
1072+
def test_subclasses_with_reduce_ex(self):
1073+
class NamedInt(int):
1074+
__qualname__ = 'NamedInt' # needed for pickle protocol 4
1075+
def __new__(cls, *args):
1076+
_args = args
1077+
name, *args = args
1078+
if len(args) == 0:
1079+
raise TypeError("name and value must be specified")
1080+
self = int.__new__(cls, *args)
1081+
self._intname = name
1082+
self._args = _args
1083+
return self
1084+
def __reduce_ex__(self, proto):
1085+
return self.__class__, self._args
1086+
@property
1087+
def __name__(self):
1088+
return self._intname
1089+
def __repr__(self):
1090+
# repr() is updated to include the name and type info
1091+
return "{}({!r}, {})".format(type(self).__name__,
1092+
self.__name__,
1093+
int.__repr__(self))
1094+
def __str__(self):
1095+
# str() is unchanged, even if it relies on the repr() fallback
1096+
base = int
1097+
base_str = base.__str__
1098+
if base_str.__objclass__ is object:
1099+
return base.__repr__(self)
1100+
return base_str(self)
1101+
# for simplicity, we only define one operator that
1102+
# propagates expressions
1103+
def __add__(self, other):
1104+
temp = int(self) + int( other)
1105+
if isinstance(self, NamedInt) and isinstance(other, NamedInt):
1106+
return NamedInt(
1107+
'({0} + {1})'.format(self.__name__, other.__name__),
1108+
temp )
1109+
else:
1110+
return temp
1111+
1112+
class NEI(NamedInt, Enum):
1113+
__qualname__ = 'NEI' # needed for pickle protocol 4
1114+
x = ('the-x', 1)
1115+
y = ('the-y', 2)
1116+
1117+
1118+
self.assertIs(NEI.__new__, Enum.__new__)
1119+
self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
1120+
globals()['NamedInt'] = NamedInt
1121+
globals()['NEI'] = NEI
1122+
NI5 = NamedInt('test', 5)
1123+
self.assertEqual(NI5, 5)
1124+
test_pickle_dump_load(self.assertEqual, NI5, 5)
1125+
self.assertEqual(NEI.y.value, 2)
1126+
test_pickle_dump_load(self.assertIs, NEI.y)
1127+
9301128
def test_subclasses_without_getnewargs(self):
9311129
class NamedInt(int):
9321130
__qualname__ = 'NamedInt'

0 commit comments

Comments
 (0)