Starting with Python 3.4, which will be released in March this year, enums will be added. (Documentation)
A backport has been developed that can be used from Python 2.4 and can be installed with pip install enum34
.
Until now, for example, constants have been defined in this way.
class Spam:
FOO = 1
BAR = 2
BAZ = 3
However, this method has the following problems.
There were ad hoc solutions and libraries, but I want this much standard library. Now that it's in the standard library, let's use it.
As with namedtuple
, if you pass a model name first, then a list of names, or a space-separated string, the values will be assigned in order from 1.
The resulting type can access the element in three ways: attribute, call, and subscript.
>>> import enum
>>> Foo = enum.Enum("Foo", "foo bar baz")
>>> Foo.foo
<Foo.foo: 1>
>>> Foo(1)
<Foo.foo: 1>
>>> Foo["foo"]
<Foo.foo: 1>
>>> isinstance(Foo.foo, Foo)
True
The element has attributes named and value.
>>> Foo.foo.value
1
>>> Foo.foo.name
'foo'
The element itself is distinct from its value. Since all elements are singletons that are created at the same time as the class is created, you can compare them with ʻisinstead of
==`.
>>> Foo.foo == 1
False
>>> Foo.foo is Foo(1) is Foo["foo"]
True
>>> Foo.foo is Foo(1)
True
You can also iterate.
>>> for m in Foo:
... print(m)
...
Foo.foo
Foo.bar
Foo.baz
By defining an inherited class instead of calling ʻEnum`, you can assign any value to an element instead of a sequential number from 1.
>>> class Bar(enum.Enum):
... foo = 'x'
... bar = 'y'
... baz = 'z'
...
>>> Bar.foo
<Bar.foo: 'x'>
In Python 2, there is (basically) no way to take advantage of the order in which the elements of a class are defined.
If you want to maintain the definition order when enumerating with the for statement, define the attribute __order__
. This is an enum34 extension for Python 2 support, and the Python 3.4 enum module saves the definition order without any action.
>>> class Bar(enum.Enum):
... __order__ = 'foo bar baz'
... foo = 'x'
... bar = 'y'
... baz = 'z'
...
>>> list(Bar)
[<Bar.foo: 'x'>, <Bar.bar: 'y'>, <Bar.baz: 'z'>]
Sometimes it's convenient for an element to act directly as an integer. In that case you can use IntEnum.
>>> class Foo(enum.IntEnum):
... foo = 1
... bar = 3
... baz = 5
...
>>> Foo.foo == 1
True
>>> Foo.foo + 2
3
>>> Foo.foo < Foo.bar
True
In fact, this is the only definition of IntEnum.
class IntEnum(int, Enum):
"""Enum where members are also (and must be) ints"""
In this way, you can inherit the behavior you want the element to have.
>>> class Bar(str, enum.Enum):
... foo = 'x'
... bar = 'y'
...
>>> Bar.foo + Bar.bar
'xy'
While this is useful, dynamically typed Python runs the risk of confusing whether a variable has an Enum element or a pure value type. Let's use it with moderation.
Even with IntEnum, I'm not sure if there is an Enum value that corresponds to that integer value using ʻin directly between the integer value and the Enum type. ʻIn
can only be used on its Enum type element.
>>> class Foo(enum.IntEnum):
... foo = 3
... bar = 7
...
>>> 3 in Foo
False
>>> Foo.foo in Foo
True
You can do anything by inheriting EnumMeta and customizing it, or hitting the private API directly, but don't behave like that, just call it normally and check for exceptions.
If the call does not find a value, ValueError
is thrown.
You can use subscripts and KeyError
in the same way when subtracting elements from a name.
>>> Foo(2)
ValueError: 2 is not a valid Foo
>>> Foo['xyzzy']
KeyError: 'xyzzy'
However, as a public API, a dictionary with the name as the key and the element as the value is prepared with the name __members__
, so when looking up by name, you can also use ʻinor
.get ()` or the usual dict operation. You can use it.
>>> Foo.__members__
{'foo': <Foo.foo: 3>, 'bar': <Foo.bar: 7>}
>>> Foo.__members__['foo']
<Foo.foo: 3>
>>> 'xyzzy' in Foo.__members__
False
Recommended Posts