Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bpo-44342: [Enum] change pickling from by-value to by-name #26658

Merged
merged 1 commit into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 4 additions & 21 deletions Lib/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,23 +456,6 @@ def __new__(metacls, cls, bases, classdict, *, boundary=None, _simple=False, **k
classdict['_all_bits_'] = 2 ** ((flag_mask).bit_length()) - 1
classdict['_inverted_'] = None
#
# If a custom type is mixed into the Enum, and it does not know how
# to pickle itself, pickle.dumps will succeed but pickle.loads will
# fail. Rather than have the error show up later and possibly far
# from the source, sabotage the pickle protocol for this class so
# that pickle.dumps also fails.
#
# However, if the new class implements its own __reduce_ex__, do not
# sabotage -- it's on them to make sure it works correctly. We use
# __reduce_ex__ instead of any of the others as it is preferred by
# pickle over __reduce__, and it handles all pickle protocols.
if '__reduce_ex__' not in classdict:
if member_type is not object:
methods = ('__getnewargs_ex__', '__getnewargs__',
'__reduce_ex__', '__reduce__')
if not any(m in member_type.__dict__ for m in methods):
_make_class_unpicklable(classdict)
#
# create a default docstring if one has not been provided
if '__doc__' not in classdict:
classdict['__doc__'] = 'An enumeration.'
Expand Down Expand Up @@ -792,7 +775,7 @@ def _convert_(cls, name, module, filter, source=None, *, boundary=None):
body['__module__'] = module
tmp_cls = type(name, (object, ), body)
cls = _simple_enum(etype=cls, boundary=boundary or KEEP)(tmp_cls)
cls.__reduce_ex__ = _reduce_ex_by_name
cls.__reduce_ex__ = _reduce_ex_by_global_name
global_enum(cls)
module_globals[name] = cls
return cls
Expand Down Expand Up @@ -1030,7 +1013,7 @@ def __hash__(self):
return hash(self._name_)

def __reduce_ex__(self, proto):
return self.__class__, (self._value_, )
return getattr, (self.__class__, self._name_)

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


def _reduce_ex_by_name(self, proto):
def _reduce_ex_by_global_name(self, proto):
return self.name

class FlagBoundary(StrEnum):
Expand Down Expand Up @@ -1795,6 +1778,6 @@ def _old_convert_(etype, name, module, filter, source=None, *, boundary=None):
# unless some values aren't comparable, in which case sort by name
members.sort(key=lambda t: t[0])
cls = etype(name, members, module=module, boundary=boundary or KEEP)
cls.__reduce_ex__ = _reduce_ex_by_name
cls.__reduce_ex__ = _reduce_ex_by_global_name
cls.__repr__ = global_enum_repr
return cls
8 changes: 4 additions & 4 deletions Lib/test/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -830,7 +830,7 @@ def test_pickle_by_name(self):
class ReplaceGlobalInt(IntEnum):
ONE = 1
TWO = 2
ReplaceGlobalInt.__reduce_ex__ = enum._reduce_ex_by_name
ReplaceGlobalInt.__reduce_ex__ = enum._reduce_ex_by_global_name
for proto in range(HIGHEST_PROTOCOL):
self.assertEqual(ReplaceGlobalInt.TWO.__reduce_ex__(proto), 'TWO')

Expand Down Expand Up @@ -1527,10 +1527,10 @@ class NEI(NamedInt, Enum):
NI5 = NamedInt('test', 5)
self.assertEqual(NI5, 5)
self.assertEqual(NEI.y.value, 2)
test_pickle_exception(self.assertRaises, TypeError, NEI.x)
test_pickle_exception(self.assertRaises, PicklingError, NEI)
test_pickle_dump_load(self.assertIs, NEI.y)
test_pickle_dump_load(self.assertIs, NEI)

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