5

I don't understand why this Enum doesn't have all the members I defined, when I assign a dict as each member's value:

from enum import Enum

class Token(Enum):
    facebook = {
    'access_period': 0,
    'plan_name': ''}

    instagram = {
    'access_period': 0,
    'plan_name': ''}

    twitter = {
    'access_period': 0,
    'plan_name': ''}

if __name__ == "__main__":
    print(list(Token))

The output is:

[<Token.twitter: {'plan_name': '', 'access_period': 0}>]

… but I expected something like:

[<Token.facebook:  {'plan_name': '', 'access_period': 0}>,
 <Token.instagram: {'plan_name': '', 'access_period': 0}>,
 <Token.twitter:   {'plan_name': '', 'access_period': 0}>]

Why aren't all the members shown?

6
  • It's because they all have the same value. That's not how enums are supposed to work. Commented Apr 20, 2017 at 14:51
  • I'm not sure why this question has received a down-vote. The writing could be improved -- yes. However the question shows a not necessarily intuitive behavior of the Enum module. Commented Apr 20, 2017 at 15:07
  • @Fartash you might want to double-check your spelling and rewrite the question title. Commented Apr 20, 2017 at 15:10
  • @MichaelHoff: Agreed. The question itself is well worded, shows the code to reproduce the problem, and describes the desired outcome. I wish all questions were this good! Commented Apr 20, 2017 at 15:14
  • Any reason you need Enum? Commented Apr 20, 2017 at 20:55

2 Answers 2

5
+250

Enum enforces unique values for the members. Member definitions with the same value as other definitions will be treated as aliases.

Demonstration:

Token.__members__
# OrderedDict([('twitter',
#               <Token.twitter: {'plan_name': '', 'access_period': 0}>),
#              ('facebook',
#               <Token.twitter: {'plan_name': '', 'access_period': 0}>),
#              ('instagram',
#               <Token.twitter: {'plan_name': '', 'access_period': 0}>)])

assert Token.instagram == Token.twitter

The defined names do all exist, however they are all mapped to the same member.

Have a look at the source code if you are interested:

# [...]
# If another member with the same value was already defined, the
# new member becomes an alias to the existing one.
for name, canonical_member in enum_class._member_map_.items():
    if canonical_member._value_ == enum_member._value_:
        enum_member = canonical_member
        break
else:
    # Aliases don't appear in member names (only in __members__).
    enum_class._member_names_.append(member_name)
# performance boost for any member that would not shadow
# a DynamicClassAttribute
if member_name not in base_attributes:
    setattr(enum_class, member_name, enum_member)
# now add to _member_map_
enum_class._member_map_[member_name] = enum_member
try:
    # This may fail if value is not hashable. We can't add the value
    # to the map, and by-value lookups for this value will be
    # linear.
    enum_class._value2member_map_[value] = enum_member
except TypeError:
    pass
# [...]

Further, it seems to me that you want to exploit the Enum class to modify the value (the dictionary) during run-time. This is strongly discouraged and also very unintuitive for other people reading/using your code. An enum is expected to be made of constants.

Sign up to request clarification or add additional context in comments.

3 Comments

The line "The members do exist" could be worded more clearly, maybe "The different names exist, but they all map to the same member" ?
Fair point. I was not too sure about the correct terminology. As len(Token.__members__) == 3 one could understand that there are three "members". Answer updated.
Hah, I just had to make the same change to my answer! :/
4

As @MichaelHoff noted, the behavior of Enum is to consider names with the same values to be aliases1.

You can get around this by using the Advanced Enum2 library:

from aenum import Enum, NoAlias

class Token(Enum):
    _settings_ = NoAlias
    facebook = {
        'access_period': 0,
        'plan_name': '',
        }

    instagram = {
        'access_period': 0,
        'plan_name': '',
        }

    twitter = {
        'access_period': 0,
        'plan_name': '',
        }

if __name__ == "__main__":
    print list(Token)

Output is now:

[
  <Token.twitter: {'plan_name': '', 'access_period': 0}>,
  <Token.facebook: {'plan_name': '', 'access_period': 0}>,
  <Token.instagram: {'plan_name': '', 'access_period': 0}>,
  ]

To reinforce what Michael said: Enum members are meant to be constants -- you shouldn't use non-constant values unless you really know what you are doing.


A better example of using 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

1 See this answer for the standard Enum usage.

2 Disclosure: I am the author of the Python stdlib Enum, the enum34 backport, and the Advanced Enumeration (aenum) library.

6 Comments

Is there a way to achieve the NoAlias behavior for the stdlib?
@MichaelHoff: Not easily, no. It falls into the category of "you can if you really want to, but nobody is going to enjoy reading that code!" ;)
How dislikable is def __init__(self, *args): self._value_ = (self.name,) + args? ;)
@MichaelHoff: That will not work with mixin types, such as int, and would also make by-value lookups fail (instead of CardNumber(8) it would be CardNumber(('EIGHT', 8)).
Surprisingly it does work* with IntEnum but yes, the lookup via constructor will fail. (*at least the use cases I tested have been just fine)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.