Skip to content

Commit 62f1d2b

Browse files
authored
bpo-44342: [Enum] changed pickling from by-value to by-name (GH-26658)
by-value lookups could fail on complex enums, necessitating a check for __reduce__ and possibly sabotaging the final enum; by-name lookups should never fail, and sabotaging is no longer necessary for class-based enum creation.
1 parent 0507303 commit 62f1d2b

File tree

3 files changed

+9
-25
lines changed

3 files changed

+9
-25
lines changed

Lib/enum.py

+4-21
Original file line numberDiff line numberDiff line change
@@ -456,23 +456,6 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
456456
classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1
457457
classdict['_inverted_'] = None
458458
#
459-
# If a custom type is mixed into the Enum, and it does not know how
460-
# to pickle itself, pickle.dumps will succeed but pickle.loads will
461-
# fail. Rather than have the error show up later and possibly far
462-
# from the source, sabotage the pickle protocol for this class so
463-
# that pickle.dumps also fails.
464-
#
465-
# However, if the new class implements its own __reduce_ex__, do not
466-
# sabotage -- it's on them to make sure it works correctly. We use
467-
# __reduce_ex__ instead of any of the others as it is preferred by
468-
# pickle over __reduce__, and it handles all pickle protocols.
469-
if '__reduce_ex__' not in classdict:
470-
if member_type is not object:
471-
methods = ('__getnewargs_ex__', '__getnewargs__',
472-
'__reduce_ex__', '__reduce__')
473-
if not any(m in member_type.__dict__ for m in methods):
474-
_make_class_unpicklable(classdict)
475-
#
476459
# create a default docstring if one has not been provided
477460
if '__doc__' not in classdict:
478461
classdict['__doc__'] = 'An enumeration.'
@@ -792,7 +775,7 @@ def _convert_(cls, name, module, filter, source=None, *, boundary=None):
792775
body['__module__'] = module
793776
tmp_cls = type(name, (object, ), body)
794777
cls = _simple_enum(etype=cls, boundary=boundary or KEEP)(tmp_cls)
795-
cls.__reduce_ex__ = _reduce_ex_by_name
778+
cls.__reduce_ex__ = _reduce_ex_by_global_name
796779
global_enum(cls)
797780
module_globals[name] = cls
798781
return cls
@@ -1030,7 +1013,7 @@ def __hash__(self):
10301013
return hash(self._name_)
10311014

10321015
def __reduce_ex__(self, proto):
1033-
return self.__class__, (self._value_, )
1016+
return getattr, (self.__class__, self._name_)
10341017

10351018
# enum.property is used to provide access to the `name` and
10361019
# `value` attributes of enum members while keeping some measure of
@@ -1091,7 +1074,7 @@ def _generate_next_value_(name, start, count, last_values):
10911074
return name.lower()
10921075

10931076

1094-
def _reduce_ex_by_name(self, proto):
1077+
def _reduce_ex_by_global_name(self, proto):
10951078
return self.name
10961079

10971080
class FlagBoundary(StrEnum):
@@ -1795,6 +1778,6 @@ def _old_convert_(etype, name, module, filter, source=None, *, boundary=None):
17951778
# unless some values aren't comparable, in which case sort by name
17961779
members.sort(key=lambda t: t[0])
17971780
cls = etype(name, members, module=module, boundary=boundary or KEEP)
1798-
cls.__reduce_ex__ = _reduce_ex_by_name
1781+
cls.__reduce_ex__ = _reduce_ex_by_global_name
17991782
cls.__repr__ = global_enum_repr
18001783
return cls

Lib/test/test_enum.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -830,7 +830,7 @@ def test_pickle_by_name(self):
830830
class ReplaceGlobalInt(IntEnum):
831831
ONE = 1
832832
TWO = 2
833-
ReplaceGlobalInt.__reduce_ex__ = enum._reduce_ex_by_name
833+
ReplaceGlobalInt.__reduce_ex__ = enum._reduce_ex_by_global_name
834834
for proto in range(HIGHEST_PROTOCOL):
835835
self.assertEqual(ReplaceGlobalInt.TWO.__reduce_ex__(proto), 'TWO')
836836

@@ -1527,10 +1527,10 @@ class NEI(NamedInt, Enum):
15271527
NI5 = NamedInt('test', 5)
15281528
self.assertEqual(NI5, 5)
15291529
self.assertEqual(NEI.y.value, 2)
1530-
test_pickle_exception(self.assertRaises, TypeError, NEI.x)
1531-
test_pickle_exception(self.assertRaises, PicklingError, NEI)
1530+
test_pickle_dump_load(self.assertIs, NEI.y)
1531+
test_pickle_dump_load(self.assertIs, NEI)
15321532

1533-
def test_subclasses_without_direct_pickle_support_using_name(self):
1533+
def test_subclasses_with_direct_pickle_support(self):
15341534
class NamedInt(int):
15351535
__qualname__ = 'NamedInt'
15361536
def __new__(cls, *args):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Enum] Change pickling from by-value to by-name.

0 commit comments

Comments
 (0)