Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit fed1b5a

Browse files
miss-islingtonsobolevnethanfurman
authored
[3.11] gh-105332: [Enum] Fix unpickling flags in edge-cases (GH-105348) (GH-105519)
* revert enum pickling from by-name to by-value (cherry picked from commit 4ff5690) Co-authored-by: Nikita Sobolev <[email protected]> Co-authored-by: Ethan Furman <[email protected]>
1 parent c3b8f9d commit fed1b5a

File tree

4 files changed

+47
-23
lines changed

4 files changed

+47
-23
lines changed

Doc/howto/enum.rst

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,16 @@ from that module.
484484
nested in other classes.
485485

486486
It is possible to modify how enum members are pickled/unpickled by defining
487-
:meth:`__reduce_ex__` in the enumeration class.
487+
:meth:`__reduce_ex__` in the enumeration class. The default method is by-value,
488+
but enums with complicated values may want to use by-name::
489+
490+
>>> class MyEnum(Enum):
491+
... __reduce_ex__ = enum.pickle_by_enum_name
492+
493+
.. note::
494+
495+
Using by-name for flags is not recommended, as unnamed aliases will
496+
not unpickle.
488497

489498

490499
Functional API

Lib/enum.py

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
'FlagBoundary', 'STRICT', 'CONFORM', 'EJECT', 'KEEP',
1313
'global_flag_repr', 'global_enum_repr', 'global_str', 'global_enum',
1414
'EnumCheck', 'CONTINUOUS', 'NAMED_FLAGS', 'UNIQUE',
15+
'pickle_by_global_name', 'pickle_by_enum_name',
1516
]
1617

1718

@@ -918,7 +919,6 @@ def _convert_(cls, name, module, filter, source=None, *, boundary=None, as_globa
918919
body['__module__'] = module
919920
tmp_cls = type(name, (object, ), body)
920921
cls = _simple_enum(etype=cls, boundary=boundary or KEEP)(tmp_cls)
921-
cls.__reduce_ex__ = _reduce_ex_by_global_name
922922
if as_global:
923923
global_enum(cls)
924924
else:
@@ -1225,7 +1225,7 @@ def __hash__(self):
12251225
return hash(self._name_)
12261226

12271227
def __reduce_ex__(self, proto):
1228-
return getattr, (self.__class__, self._name_)
1228+
return self.__class__, (self._value_, )
12291229

12301230
# enum.property is used to provide access to the `name` and
12311231
# `value` attributes of enum members while keeping some measure of
@@ -1291,8 +1291,14 @@ def _generate_next_value_(name, start, count, last_values):
12911291
return name.lower()
12921292

12931293

1294-
def _reduce_ex_by_global_name(self, proto):
1294+
def pickle_by_global_name(self, proto):
1295+
# should not be used with Flag-type enums
12951296
return self.name
1297+
_reduce_ex_by_global_name = pickle_by_global_name
1298+
1299+
def pickle_by_enum_name(self, proto):
1300+
# should not be used with Flag-type enums
1301+
return getattr, (self.__class__, self._name_)
12961302

12971303
class FlagBoundary(StrEnum):
12981304
"""
@@ -1314,23 +1320,6 @@ class Flag(Enum, boundary=STRICT):
13141320
Support for flags
13151321
"""
13161322

1317-
def __reduce_ex__(self, proto):
1318-
cls = self.__class__
1319-
unknown = self._value_ & ~cls._flag_mask_
1320-
member_value = self._value_ & cls._flag_mask_
1321-
if unknown and member_value:
1322-
return _or_, (cls(member_value), unknown)
1323-
for val in _iter_bits_lsb(member_value):
1324-
rest = member_value & ~val
1325-
if rest:
1326-
return _or_, (cls(rest), cls._value2member_map_.get(val))
1327-
else:
1328-
break
1329-
if self._name_ is None:
1330-
return cls, (self._value_,)
1331-
else:
1332-
return getattr, (cls, self._name_)
1333-
13341323
_numeric_repr_ = repr
13351324

13361325
def _generate_next_value_(name, start, count, last_values):
@@ -2047,7 +2036,6 @@ def _old_convert_(etype, name, module, filter, source=None, *, boundary=None):
20472036
# unless some values aren't comparable, in which case sort by name
20482037
members.sort(key=lambda t: t[0])
20492038
cls = etype(name, members, module=module, boundary=boundary or KEEP)
2050-
cls.__reduce_ex__ = _reduce_ex_by_global_name
20512039
return cls
20522040

20532041
_stdlib_enums = IntEnum, StrEnum, IntFlag

Lib/test/test_enum.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ def load_tests(loader, tests, ignore):
3232
'../../Doc/library/enum.rst',
3333
optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE,
3434
))
35+
if os.path.exists('Doc/howto/enum.rst'):
36+
tests.addTests(doctest.DocFileSuite(
37+
'../../Doc/howto/enum.rst',
38+
optionflags=doctest.ELLIPSIS|doctest.NORMALIZE_WHITESPACE,
39+
))
3540
return tests
3641

3742
MODULE = __name__
@@ -67,6 +72,7 @@ class FlagStooges(Flag):
6772
LARRY = 1
6873
CURLY = 2
6974
MOE = 4
75+
BIG = 389
7076
except Exception as exc:
7177
FlagStooges = exc
7278

@@ -75,17 +81,20 @@ class FlagStoogesWithZero(Flag):
7581
LARRY = 1
7682
CURLY = 2
7783
MOE = 4
84+
BIG = 389
7885

7986
class IntFlagStooges(IntFlag):
8087
LARRY = 1
8188
CURLY = 2
8289
MOE = 4
90+
BIG = 389
8391

8492
class IntFlagStoogesWithZero(IntFlag):
8593
NOFLAG = 0
8694
LARRY = 1
8795
CURLY = 2
8896
MOE = 4
97+
BIG = 389
8998

9099
# for pickle test and subclass tests
91100
class Name(StrEnum):
@@ -1860,14 +1869,17 @@ class NEI(NamedInt, Enum):
18601869
__qualname__ = 'NEI'
18611870
x = ('the-x', 1)
18621871
y = ('the-y', 2)
1863-
18641872
self.assertIs(NEI.__new__, Enum.__new__)
18651873
self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
18661874
globals()['NamedInt'] = NamedInt
18671875
globals()['NEI'] = NEI
18681876
NI5 = NamedInt('test', 5)
18691877
self.assertEqual(NI5, 5)
18701878
self.assertEqual(NEI.y.value, 2)
1879+
with self.assertRaisesRegex(TypeError, "name and value must be specified"):
1880+
test_pickle_dump_load(self.assertIs, NEI.y)
1881+
# fix pickle support and try again
1882+
NEI.__reduce_ex__ = enum.pickle_by_enum_name
18711883
test_pickle_dump_load(self.assertIs, NEI.y)
18721884
test_pickle_dump_load(self.assertIs, NEI)
18731885

@@ -3120,11 +3132,17 @@ def test_pickle(self):
31203132
test_pickle_dump_load(self.assertEqual,
31213133
FlagStooges.CURLY&~FlagStooges.CURLY)
31223134
test_pickle_dump_load(self.assertIs, FlagStooges)
3135+
test_pickle_dump_load(self.assertEqual, FlagStooges.BIG)
3136+
test_pickle_dump_load(self.assertEqual,
3137+
FlagStooges.CURLY|FlagStooges.BIG)
31233138

31243139
test_pickle_dump_load(self.assertIs, FlagStoogesWithZero.CURLY)
31253140
test_pickle_dump_load(self.assertEqual,
31263141
FlagStoogesWithZero.CURLY|FlagStoogesWithZero.MOE)
31273142
test_pickle_dump_load(self.assertIs, FlagStoogesWithZero.NOFLAG)
3143+
test_pickle_dump_load(self.assertEqual, FlagStoogesWithZero.BIG)
3144+
test_pickle_dump_load(self.assertEqual,
3145+
FlagStoogesWithZero.CURLY|FlagStoogesWithZero.BIG)
31283146

31293147
test_pickle_dump_load(self.assertIs, IntFlagStooges.CURLY)
31303148
test_pickle_dump_load(self.assertEqual,
@@ -3134,11 +3152,19 @@ def test_pickle(self):
31343152
test_pickle_dump_load(self.assertEqual, IntFlagStooges(0))
31353153
test_pickle_dump_load(self.assertEqual, IntFlagStooges(0x30))
31363154
test_pickle_dump_load(self.assertIs, IntFlagStooges)
3155+
test_pickle_dump_load(self.assertEqual, IntFlagStooges.BIG)
3156+
test_pickle_dump_load(self.assertEqual, IntFlagStooges.BIG|1)
3157+
test_pickle_dump_load(self.assertEqual,
3158+
IntFlagStooges.CURLY|IntFlagStooges.BIG)
31373159

31383160
test_pickle_dump_load(self.assertIs, IntFlagStoogesWithZero.CURLY)
31393161
test_pickle_dump_load(self.assertEqual,
31403162
IntFlagStoogesWithZero.CURLY|IntFlagStoogesWithZero.MOE)
31413163
test_pickle_dump_load(self.assertIs, IntFlagStoogesWithZero.NOFLAG)
3164+
test_pickle_dump_load(self.assertEqual, IntFlagStoogesWithZero.BIG)
3165+
test_pickle_dump_load(self.assertEqual, IntFlagStoogesWithZero.BIG|1)
3166+
test_pickle_dump_load(self.assertEqual,
3167+
IntFlagStoogesWithZero.CURLY|IntFlagStoogesWithZero.BIG)
31423168

31433169
@unittest.skipIf(
31443170
python_version >= (3, 12),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Revert pickling method from by-name back to by-value.

0 commit comments

Comments
 (0)