问题
I'm having trouble working with an Enum where some attributes have the same value. I think Enums are so new to python that I can't find any other reference to this issue. In any case, let's say I have the following
class CardNumber(Enum):
ACE = 11
TWO = 2
THREE = 3
FOUR = 4
FIVE = 5
SIX = 6
SEVEN = 7
EIGHT = 8
NINE = 9
TEN = 10
JACK = 10
QUEEN = 10
KING = 10
Clearly these are the card numbers and their corresponding values in black jack. The ten through king have the same value. But if I do something like print(CardNumber.QUEEN)
, I get back <CardNumber.TEN: 10>
. What's more, if I iterate over these, it simply iterates over unique values.
>>> for elem in CardNumber:
... print(elem)
CardNumber.ACE
CardNumber.TWO
CardNumber.THREE
CardNumber.FOUR
CardNumber.FIVE
CardNumber.SIX
CardNumber.SEVEN
CardNumber.EIGHT
CardNumber.NINE
CardNumber.TEN
How can I get around this issue? I want CardNumber.QUEEN and CardNumber.TEN to be unique, and both appear in any iteration. The only thing I could think of was to give each attribute a second value which would act as a distinct id, but that seems unpythonic.
回答1:
Yes, labels with duplicate values are turned into aliases for the first such label.
You can enumerate over the __members__
attribute, it is an ordered dictionary with the aliases included:
>>> for name, value in CardNumber.__members__.items():
... print(name, value)
...
ACE CardNumber.ACE
TWO CardNumber.TWO
THREE CardNumber.THREE
FOUR CardNumber.FOUR
FIVE CardNumber.FIVE
SIX CardNumber.SIX
SEVEN CardNumber.SEVEN
EIGHT CardNumber.EIGHT
NINE CardNumber.NINE
TEN CardNumber.TEN
JACK CardNumber.TEN
QUEEN CardNumber.TEN
KING CardNumber.TEN
However, if you must have label-and-value pairs that are unique (and not aliases), then enum.Enum
is the wrong approach here; it doesn't match the usecases for a card game.
In that case it'll be better to use a dictionary (consider using collections.OrderedDict()
if order is important too).
回答2:
Update
Using aenum1 you have a couple choices:
use
NamedConstant
instead: does not provide any of theEnum
extras (iterating, lookups, etc.) [see: original answer below]use
NoAlias
: has all the normalEnum
behavior except every member is unique and by-value lookups are not available
An example of NoAlias
:
from aenum import Enum, NoAlias
class CardNumber(Enum):
_order_ = 'EIGHT NINE TEN JACK QUEEN KING ACE' # only needed for Python 2.x
_settings_ = NoAlias
EIGHT = 8
NINE = 9
TEN = 10
JACK = 10
QUEEN = 10
KING = 10
ACE = 11
and in use:
>>> list(CardNumber)
[<CardNumber.EIGHT: 8>, <CardNumber.NINE: 9>, <CardNumber.TEN: 10>, <CardNumber.JACK: 10>, <CardNumber.QUEEN: 10>, <CardNumber.KING: 10>, <CardNumber.ACE: 11>]
>>> CardNumber.QUEEN == CardNumber.KING
False
>>> CardNumber.QUEEN is CardNumber.KING
False
>>> CardNumber.QUEEN.value == CardNumber.KING.value
True
>>> CardNumber(8)
Traceback (most recent call last):
...
TypeError: NoAlias enumerations cannot be looked up by value
Original Answer
If you want named constants and don't care about the other features of Enum
s, you can use the NamedConstant
class from the aenum library:
from aenum import NamedConstant
class CardNumber(NamedConstant):
ACE = 11
TWO = 2
THREE = 3
FOUR = 4
FIVE = 5
SIX = 6
SEVEN = 7
EIGHT = 8
NINE = 9
TEN = 10
JACK = 10
QUEEN = 10
KING = 10
Duplicate values are still distinct:
>>> CardNumber.TEN is CardNumber.JACK
False
>>> CardNumber.TEN == CardNumber.JACK
True
>>> CardNumber.TEN == 10
True
1 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.
回答3:
Thanks for the answers here but for those who use enum.auto()
here is an extended answer for having alias or duplicate enum values
import enum
PREDICT_ALIAS = enum.auto()
class Mode(enum.Enum):
FIT = enum.auto()
EVALUATE = enum.auto()
PREDICT = PREDICT_ALIAS
INFER = PREDICT_ALIAS # alias mode
print(Mode.PREDICT is Mode.INFER) # return True :)
回答4:
For those still not considering using aenum or OrderedDict, one can still hack around:
import enum
class AliasedEnum(enum.Enum):
def __init_subclass__(cls, *kargs, **kwargs):
import inspect
# unfortunately, there's no cleaner ways to retrieve original members
for stack in reversed(inspect.stack()):
frame_locals = stack[0].f_locals
enum_members = frame_locals.get('enum_members')
if enum_members is None:
try:
enum_members = frame_locals['classdict']._member_names
except (KeyError, AttributeError):
continue
break
else:
raise RuntimeError('Unable to checkout AliasedEnum members!')
# patch subclass __getattribute__ to evade deduplication checks
cls._shield_members = list(enum_members)
cls._shield_getattr = cls.__getattribute__
def patch(self, key):
if key.startswith('_shield_'):
return object.__getattribute__(self, key)
if key.startswith('_value'):
if not hasattr(self, '_name_'):
self._shield_counter = 0
elif self._shield_counter < self._shield_members.index(self._name_):
self._shield_counter += 1
class unequal:
pass
return unequal
return self._shield_getattr(key)
cls.__getattribute__ = patch
class Fck(AliasedEnum):
A = 0
B = 0
C = 0
This rely on the fact that standard enum.EnumMeta.__new__
deduplicates with an O(n²)
algorithm to handle unhashable values and should be considered bad practice. It stills achieve the desired effect:
$ python -i aliased_enum.py
>>> Fck.A
<Fck.A: 0>
>>> Fck.B
<Fck.B: 0>
>>> Fck.C
<Fck.C: 0>
Tested with Python3.7.4
and should be portable across cpython standard library.
回答5:
A simple way to do this is to put the value inside an object:
class Value:
def __init__(self, value:Any):
self.value = value
class MyEnum(Enum):
item1 = Value(0)
item2 = Value(0)
item3 = Value(1)
@property
def val(self):
return self.value.value
assert MyEnum.item1.val == 0
assert MyEnum.item2.val == 0
assert MyEnum.item3.val == 1
By the way, do not use the @dataclass decorator for the Value class. Dataclasses compare the values of its attributes to check if objects are equal, meaning that it would behave exactly like the default Enum!
来源:https://stackoverflow.com/questions/31537316/python-enums-with-duplicate-values