From 44eaaa483a59387cef6a15e4f8e62abb4bf59d49 Mon Sep 17 00:00:00 2001 From: Ethan Furman Date: Thu, 10 Jun 2021 15:13:48 -0700 Subject: [PATCH] [Enum] changed pickling from by-value to by-name 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. --- Lib/enum.py | 25 +++---------------- Lib/test/test_enum.py | 8 +++--- .../2021-06-10-15-06-47.bpo-44342.qqkGlj.rst | 1 + 3 files changed, 9 insertions(+), 25 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2021-06-10-15-06-47.bpo-44342.qqkGlj.rst diff --git a/Lib/enum.py b/Lib/enum.py index 54633d8a7fbb01..5263e510d59361 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -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.' @@ -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 @@ -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 @@ -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): @@ -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 diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py index 40794e3c1eb637..9a7882b8a9c6fe 100644 --- a/Lib/test/test_enum.py +++ b/Lib/test/test_enum.py @@ -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') @@ -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): diff --git a/Misc/NEWS.d/next/Library/2021-06-10-15-06-47.bpo-44342.qqkGlj.rst b/Misc/NEWS.d/next/Library/2021-06-10-15-06-47.bpo-44342.qqkGlj.rst new file mode 100644 index 00000000000000..6db75e3e9bcf11 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-06-10-15-06-47.bpo-44342.qqkGlj.rst @@ -0,0 +1 @@ +[Enum] Change pickling from by-value to by-name. 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