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

Lines changed: 4 additions & 21 deletions
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

Lines changed: 4 additions & 4 deletions
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):
Lines changed: 1 addition & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy