Monday, May 1, 2017

Python Enum shows weird behavior when using same dictionary for member values

Leave a Comment

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?

2 Answers

Answers 1

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.

Answers 2

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.

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment