Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 640
Fartash Avatar asked Apr 20 '17 14:04

Fartash


People also ask

Can enums have the same value python?

By definition, the enumeration member values are unique. However, you can create different member names with the same values.

How do you check if an enum contains a value python?

To check if a value exists in an enum in Python: Use a list comprehension to get a list of all of the enum's values. Use the in operator to check if the value is present in the list. The in operator will return True if the value is in the list.

Is enum Pythonic?

Enum is a class in python for creating enumerations, which are a set of symbolic names (members) bound to unique, constant values. The members of an enumeration can be compared by these symbolic anmes, and the enumeration itself can be iterated over. An enum has the following characteristics.

What is enum Auto ()?

Syntax : enum.auto() Automatically assign the integer value to the values of enum class attributes. Example #1 : In this example we can see that by using enum. auto() method, we are able to assign the numerical values automatically to the class attributes by using this method.


2 Answers

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.

like image 65
Michael Hoff Avatar answered Oct 18 '22 18:10

Michael Hoff


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.

like image 28
Ethan Furman Avatar answered Oct 18 '22 19:10

Ethan Furman