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

Skip to content

Commit 9a0cbcc

Browse files
committed
Close issue20653: allow Enum subclasses to override __reduce_ex__
1 parent 59a5533 commit 9a0cbcc

2 files changed

Lines changed: 73 additions & 14 deletions

File tree

Lib/enum.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,14 @@ def __new__(metacls, cls, bases, classdict):
116116
enum_class._value2member_map_ = {}
117117

118118
# check for a supported pickle protocols, and if not present sabotage
119-
# pickling, since it won't work anyway
120-
if member_type is not object:
121-
methods = ('__getnewargs_ex__', '__getnewargs__',
122-
'__reduce_ex__', '__reduce__')
123-
if not any(map(member_type.__dict__.get, methods)):
124-
_make_class_unpicklable(enum_class)
119+
# pickling, since it won't work anyway.
120+
# if new class implements its own __reduce_ex__, do not sabotage
121+
if classdict.get('__reduce_ex__') is None:
122+
if member_type is not object:
123+
methods = ('__getnewargs_ex__', '__getnewargs__',
124+
'__reduce_ex__', '__reduce__')
125+
if not any(map(member_type.__dict__.get, methods)):
126+
_make_class_unpicklable(enum_class)
125127

126128
# instantiate them, checking for duplicates as we go
127129
# we instantiate first instead of checking for duplicates first in case
@@ -167,7 +169,7 @@ def __new__(metacls, cls, bases, classdict):
167169

168170
# double check that repr and friends are not the mixin's or various
169171
# things break (such as pickle)
170-
for name in ('__repr__', '__str__', '__format__', '__getnewargs__', '__reduce_ex__'):
172+
for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'):
171173
class_method = getattr(enum_class, name)
172174
obj_method = getattr(member_type, name, None)
173175
enum_method = getattr(first_enum, name, None)
@@ -192,8 +194,9 @@ def __call__(cls, value, names=None, *, module=None, qualname=None, type=None):
192194
(i.e. Color = Enum('Color', names='red green blue')).
193195
194196
When used for the functional API: `module`, if set, will be stored in
195-
the new class' __module__ attribute; `type`, if set, will be mixed in
196-
as the first base class.
197+
the new class' __module__ attribute; `qualname`, if set, will be stored
198+
in the new class' __qualname__ attribute; `type`, if set, will be mixed
199+
in as the first base class.
197200
198201
Note: if `module` is not set this routine will attempt to discover the
199202
calling module by walking the frame stack; if this is unsuccessful
@@ -465,14 +468,11 @@ def __format__(self, format_spec):
465468
val = self.value
466469
return cls.__format__(val, format_spec)
467470

468-
def __getnewargs__(self):
469-
return (self._value_, )
470-
471471
def __hash__(self):
472472
return hash(self._name_)
473473

474474
def __reduce_ex__(self, proto):
475-
return self.__class__, self.__getnewargs__()
475+
return self.__class__, (self._value_, )
476476

477477
# DynamicClassAttribute is used to provide access to the `name` and
478478
# `value` properties of enum members while keeping some measure of

Lib/test/test_enum.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ class NEI(NamedInt, Enum):
956956
test_pickle_dump_load(self.assertEqual, NI5, 5)
957957
self.assertEqual(NEI.y.value, 2)
958958
test_pickle_dump_load(self.assertIs, NEI.y)
959+
test_pickle_dump_load(self.assertIs, NEI)
959960

960961
def test_subclasses_with_getnewargs_ex(self):
961962
class NamedInt(int):
@@ -1012,6 +1013,7 @@ class NEI(NamedInt, Enum):
10121013
test_pickle_dump_load(self.assertEqual, NI5, 5, protocol=(4, 4))
10131014
self.assertEqual(NEI.y.value, 2)
10141015
test_pickle_dump_load(self.assertIs, NEI.y, protocol=(4, 4))
1016+
test_pickle_dump_load(self.assertIs, NEI)
10151017

10161018
def test_subclasses_with_reduce(self):
10171019
class NamedInt(int):
@@ -1068,6 +1070,7 @@ class NEI(NamedInt, Enum):
10681070
test_pickle_dump_load(self.assertEqual, NI5, 5)
10691071
self.assertEqual(NEI.y.value, 2)
10701072
test_pickle_dump_load(self.assertIs, NEI.y)
1073+
test_pickle_dump_load(self.assertIs, NEI)
10711074

10721075
def test_subclasses_with_reduce_ex(self):
10731076
class NamedInt(int):
@@ -1124,8 +1127,9 @@ class NEI(NamedInt, Enum):
11241127
test_pickle_dump_load(self.assertEqual, NI5, 5)
11251128
self.assertEqual(NEI.y.value, 2)
11261129
test_pickle_dump_load(self.assertIs, NEI.y)
1130+
test_pickle_dump_load(self.assertIs, NEI)
11271131

1128-
def test_subclasses_without_getnewargs(self):
1132+
def test_subclasses_without_direct_pickle_support(self):
11291133
class NamedInt(int):
11301134
__qualname__ = 'NamedInt'
11311135
def __new__(cls, *args):
@@ -1178,6 +1182,61 @@ class NEI(NamedInt, Enum):
11781182
test_pickle_exception(self.assertRaises, TypeError, NEI.x)
11791183
test_pickle_exception(self.assertRaises, PicklingError, NEI)
11801184

1185+
def test_subclasses_without_direct_pickle_support_using_name(self):
1186+
class NamedInt(int):
1187+
__qualname__ = 'NamedInt'
1188+
def __new__(cls, *args):
1189+
_args = args
1190+
name, *args = args
1191+
if len(args) == 0:
1192+
raise TypeError("name and value must be specified")
1193+
self = int.__new__(cls, *args)
1194+
self._intname = name
1195+
self._args = _args
1196+
return self
1197+
@property
1198+
def __name__(self):
1199+
return self._intname
1200+
def __repr__(self):
1201+
# repr() is updated to include the name and type info
1202+
return "{}({!r}, {})".format(type(self).__name__,
1203+
self.__name__,
1204+
int.__repr__(self))
1205+
def __str__(self):
1206+
# str() is unchanged, even if it relies on the repr() fallback
1207+
base = int
1208+
base_str = base.__str__
1209+
if base_str.__objclass__ is object:
1210+
return base.__repr__(self)
1211+
return base_str(self)
1212+
# for simplicity, we only define one operator that
1213+
# propagates expressions
1214+
def __add__(self, other):
1215+
temp = int(self) + int( other)
1216+
if isinstance(self, NamedInt) and isinstance(other, NamedInt):
1217+
return NamedInt(
1218+
'({0} + {1})'.format(self.__name__, other.__name__),
1219+
temp )
1220+
else:
1221+
return temp
1222+
1223+
class NEI(NamedInt, Enum):
1224+
__qualname__ = 'NEI'
1225+
x = ('the-x', 1)
1226+
y = ('the-y', 2)
1227+
def __reduce_ex__(self, proto):
1228+
return getattr, (self.__class__, self._name_)
1229+
1230+
self.assertIs(NEI.__new__, Enum.__new__)
1231+
self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)")
1232+
globals()['NamedInt'] = NamedInt
1233+
globals()['NEI'] = NEI
1234+
NI5 = NamedInt('test', 5)
1235+
self.assertEqual(NI5, 5)
1236+
self.assertEqual(NEI.y.value, 2)
1237+
test_pickle_dump_load(self.assertIs, NEI.y)
1238+
test_pickle_dump_load(self.assertIs, NEI)
1239+
11811240
def test_tuple_subclass(self):
11821241
class SomeTuple(tuple, Enum):
11831242
__qualname__ = 'SomeTuple' # needed for pickle protocol 4

0 commit comments

Comments
 (0)