Python Enums with duplicate values

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).


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!


Update

Using aenum1 you have a couple choices:

  • use NamedConstant instead: does not provide any of the Enum extras (iterating, lookups, etc.) [see: original answer below]

  • use NoAlias: has all the normal Enum 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 Enums, 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.

Tags:

Python

Enums