summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2023-06-09 01:01:51 (GMT)
committerGitHub <noreply@github.com>2023-06-09 01:01:51 (GMT)
commit2f4a2d6c1b0c95f6be33a8c778b0212922a50530 (patch)
treea9f4a13c9b6138e9cf42b1996681c0d8f21dc10e
parent68eeab7fdd1afd11bb058df173cab40d9ebe2b06 (diff)
downloadcpython-2f4a2d6c1b0c95f6be33a8c778b0212922a50530.zip
cpython-2f4a2d6c1b0c95f6be33a8c778b0212922a50530.tar.gz
cpython-2f4a2d6c1b0c95f6be33a8c778b0212922a50530.tar.bz2
[3.12] gh-105332: [Enum] Fix unpickling flags in edge-cases (GH-105348) (GH-105520)
* revert enum pickling from by-name to by-value (cherry picked from commit 4ff5690e591b7d11cf11e34bf61004e2ea58ab3c) Co-authored-by: Nikita Sobolev <mail@sobolevn.me> Co-authored-by: Ethan Furman <ethan@stoneleaf.us>
-rw-r--r--Doc/howto/enum.rst11
-rw-r--r--Lib/enum.py30
-rw-r--r--Lib/test/test_enum.py28
-rw-r--r--Misc/NEWS.d/next/Library/2023-06-06-11-50-33.gh-issue-105332.tmpgRA.rst1
4 files changed, 47 insertions, 23 deletions
diff --git a/Doc/howto/enum.rst b/Doc/howto/enum.rst
index 68b75c5..4312b4c 100644
--- a/Doc/howto/enum.rst
+++ b/Doc/howto/enum.rst
@@ -517,7 +517,16 @@ from that module.
nested in other classes.
It is possible to modify how enum members are pickled/unpickled by defining
-:meth:`__reduce_ex__` in the enumeration class.
+:meth:`__reduce_ex__` in the enumeration class. The default method is by-value,
+but enums with complicated values may want to use by-name::
+
+ >>> class MyEnum(Enum):
+ ... __reduce_ex__ = enum.pickle_by_enum_name
+
+.. note::
+
+ Using by-name for flags is not recommended, as unnamed aliases will
+ not unpickle.
Functional API
diff --git a/Lib/enum.py b/Lib/enum.py
index bb71c84..62df304 100644
--- a/Lib/enum.py
+++ b/Lib/enum.py
@@ -12,6 +12,7 @@ __all__ = [
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
'global_flag_repr', 'global_enum_repr', 'global_str', 'global_enum',
'EnumCheck', 'CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE',
+ 'pickle_by_global_name', 'pickle_by_enum_name',
]
@@ -922,7 +923,6 @@ class EnumType(type):
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_global_name
if as_global:
global_enum(cls)
else:
@@ -1240,7 +1240,7 @@ class Enum(metaclass=EnumType):
return hash(self._name_)
def __reduce_ex__(self, proto):
- return getattr, (self.__class__, self._name_)
+ return self.__class__, (self._value_, )
# enum.property is used to provide access to the `name` and
# `value` attributes of enum members while keeping some measure of
@@ -1307,8 +1307,14 @@ class StrEnum(str, ReprEnum):
return name.lower()
-def _reduce_ex_by_global_name(self, proto):
+def pickle_by_global_name(self, proto):
+ # should not be used with Flag-type enums
return self.name
+_reduce_ex_by_global_name = pickle_by_global_name
+
+def pickle_by_enum_name(self, proto):
+ # should not be used with Flag-type enums
+ return getattr, (self.__class__, self._name_)
class FlagBoundary(StrEnum):
"""
@@ -1330,23 +1336,6 @@ class Flag(Enum, boundary=STRICT):
Support for flags
"""
- def __reduce_ex__(self, proto):
- cls = self.__class__
- unknown = self._value_ & ~cls._flag_mask_
- member_value = self._value_ & cls._flag_mask_
- if unknown and member_value:
- return _or_, (cls(member_value), unknown)
- for val in _iter_bits_lsb(member_value):
- rest = member_value & ~val
- if rest:
- return _or_, (cls(rest), cls._value2member_map_.get(val))
- else:
- break
- if self._name_ is None:
- return cls, (self._value_,)
- else:
- return getattr, (cls, self._name_)
-
_numeric_repr_ = repr
@staticmethod
@@ -2073,7 +2062,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_global_name
return cls
_stdlib_enums = IntEnum, StrEnum, IntFlag
diff --git a/Lib/test/test_enum.py b/Lib/test/test_enum.py
index 98010d1..b4ac3ab 100644
--- a/Lib/test/test_enum.py
+++ b/Lib/test/test_enum.py
@@ -31,6 +31,11 @@ def load_tests(loader, tests, ignore):
'../../Doc/library/enum.rst',
optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE,
))
+ if os.path.exists('Doc/howto/enum.rst'):
+ tests.addTests(doctest.DocFileSuite(
+ '../../Doc/howto/enum.rst',
+ optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE,
+ ))
return tests
MODULE = __name__
@@ -66,6 +71,7 @@ try:
LARRY = 1
CURLY = 2
MOE = 4
+ BIG = 389
except Exception as exc:
FlagStooges = exc
@@ -74,17 +80,20 @@ class FlagStoogesWithZero(Flag):
LARRY = 1
CURLY = 2
MOE = 4
+ BIG = 389
class IntFlagStooges(IntFlag):
LARRY = 1
CURLY = 2
MOE = 4
+ BIG = 389
class IntFlagStoogesWithZero(IntFlag):
NOFLAG = 0
LARRY = 1
CURLY = 2
MOE = 4
+ BIG = 389
# for pickle test and subclass tests
class Name(StrEnum):
@@ -1942,7 +1951,6 @@ class TestSpecial(unittest.TestCase):
__qualname__ = 'NEI'
x = ('the-x', 1)
y = ('the-y', 2)
-
self.assertIs(NEI.__new__, Enum.__new__)
self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
globals()['NamedInt'] = NamedInt
@@ -1950,6 +1958,10 @@ class TestSpecial(unittest.TestCase):
NI5 = NamedInt('test', 5)
self.assertEqual(NI5, 5)
self.assertEqual(NEI.y.value, 2)
+ with self.assertRaisesRegex(TypeError, "name and value must be specified"):
+ test_pickle_dump_load(self.assertIs, NEI.y)
+ # fix pickle support and try again
+ NEI.__reduce_ex__ = enum.pickle_by_enum_name
test_pickle_dump_load(self.assertIs, NEI.y)
test_pickle_dump_load(self.assertIs, NEI)
@@ -3252,11 +3264,17 @@ class OldTestFlag(unittest.TestCase):
test_pickle_dump_load(self.assertEqual,
FlagStooges.CURLY&~FlagStooges.CURLY)
test_pickle_dump_load(self.assertIs, FlagStooges)
+ test_pickle_dump_load(self.assertEqual, FlagStooges.BIG)
+ test_pickle_dump_load(self.assertEqual,
+ FlagStooges.CURLY|FlagStooges.BIG)
test_pickle_dump_load(self.assertIs, FlagStoogesWithZero.CURLY)
test_pickle_dump_load(self.assertEqual,
FlagStoogesWithZero.CURLY|FlagStoogesWithZero.MOE)
test_pickle_dump_load(self.assertIs, FlagStoogesWithZero.NOFLAG)
+ test_pickle_dump_load(self.assertEqual, FlagStoogesWithZero.BIG)
+ test_pickle_dump_load(self.assertEqual,
+ FlagStoogesWithZero.CURLY|FlagStoogesWithZero.BIG)
test_pickle_dump_load(self.assertIs, IntFlagStooges.CURLY)
test_pickle_dump_load(self.assertEqual,
@@ -3266,11 +3284,19 @@ class OldTestFlag(unittest.TestCase):
test_pickle_dump_load(self.assertEqual, IntFlagStooges(0))
test_pickle_dump_load(self.assertEqual, IntFlagStooges(0x30))
test_pickle_dump_load(self.assertIs, IntFlagStooges)
+ test_pickle_dump_load(self.assertEqual, IntFlagStooges.BIG)
+ test_pickle_dump_load(self.assertEqual, IntFlagStooges.BIG|1)
+ test_pickle_dump_load(self.assertEqual,
+ IntFlagStooges.CURLY|IntFlagStooges.BIG)
test_pickle_dump_load(self.assertIs, IntFlagStoogesWithZero.CURLY)
test_pickle_dump_load(self.assertEqual,
IntFlagStoogesWithZero.CURLY|IntFlagStoogesWithZero.MOE)
test_pickle_dump_load(self.assertIs, IntFlagStoogesWithZero.NOFLAG)
+ test_pickle_dump_load(self.assertEqual, IntFlagStoogesWithZero.BIG)
+ test_pickle_dump_load(self.assertEqual, IntFlagStoogesWithZero.BIG|1)
+ test_pickle_dump_load(self.assertEqual,
+ IntFlagStoogesWithZero.CURLY|IntFlagStoogesWithZero.BIG)
def test_contains_tf(self):
Open = self.Open
diff --git a/Misc/NEWS.d/next/Library/2023-06-06-11-50-33.gh-issue-105332.tmpgRA.rst b/Misc/NEWS.d/next/Library/2023-06-06-11-50-33.gh-issue-105332.tmpgRA.rst
new file mode 100644
index 0000000..31b6855
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-06-06-11-50-33.gh-issue-105332.tmpgRA.rst
@@ -0,0 +1 @@
+Revert pickling method from by-name back to by-value.