使用 enum 列舉

April 29, 2022

如果想要列舉值,雖然可以透過 dict 或者是類別來定義,不過並不方便。

沒有 enum 之前

例如使用 dict 的情況:

>>> Action = {
...     'stop' : 1,
...     'right': 2,
...     'left' : 3,
...     'up'   : 4,
...     'down' : 5
... }
>>> Action['stop']
1
>>> Action['down']
5
>>> 

或者是使用類別定義的方式:

>>> class Action:
...     stop  = 1
...     right = 2
...     left  = 3
...     up    = 4
...     down  = 5
...
>>> Action.right
2
>>> Action.left
3
>>> 

基本上這兩種方式是可以解決問題,不過問題在於,無法檢查列舉值是否重複,可以透過 Action['up'] = 5 或者是 Action.up = 5 這樣的方式來修改列舉值,如果透過類別方式來定義,Action 類別本身還能夠實例化,這些都是使用上的一些困擾。

使用 enum 模組

從 Python 3.4 開始新增了 enum 模組,其中提供了 EnumIntEnum 等類別,可以用來繼承以便定義列舉。繼承 Enum 的話,列舉值可以是各種型態,不過建議使用狀態不可變的值(例如字串),繼承 IntEnum 的話,列舉值就只能是整數。例如:

>>> from enum import IntEnum
>>> class Action(IntEnum):
...     stop  = 1
...     right = 2
...     left  = 3
...     up    = 4
...     down  = 5
...
>>> Action.left
<Action.left: 3>
>>> Action()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __call__() missing 1 required positional argument: 'value'
>>> Action.left = 5
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Winware\Python39\lib\enum.py", line 389, in __setattr__
    raise AttributeError('Cannot reassign members.')
AttributeError: Cannot reassign members.
>>> 

可以看到,你無法使用 Action() 來建立一個物件,也無法重新指定列舉值,實際上,Action() 是用來指定列舉值,然後傳回列舉物件,列舉物件上具有 namevalue ,可用來取得列舉名稱與列舉值,也可以使用 [] 指定列舉名稱來取得列舉物件。例如:

>>> Action(3)
<Action.left: 3>
>>> enum_member = Action(3)
>>> enum_member.name
'left'
>>> enum_member.value
3
>>> Action['left']
<Action.left: 3>
>>> 

繼承了 EnumIntEnum 而定義的類別,可以使用 for in 來迭代:

>>> for member in Action:
...     print(member.name, '\t: ', member.value)
...
stop    :  1
right   :  2
left    :  3
up      :  4
down    :  5
>>> 

繼承 EnumIntEnum 類別定義列舉時,列舉名稱不得重複,然而,列舉值可以重複。例如:

>>> class Action(IntEnum):
...     stop = 1
...     stop = 2
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in Action
  File "C:\Winware\Python39\lib\enum.py", line 99, in __setitem__
    raise TypeError('Attempted to reuse key: %r' % key)
TypeError: Attempted to reuse key: 'stop'
>>> class Action(IntEnum):
...     stop = 1
...     left = 1
...
>>> Action(1)
<Action.stop: 1>
>>> Action['left']
<Action.stop: 1>
>>>

如果列舉名稱不同然而值相同,那麼後者會是前者的別名,因此就上例來說,無論使用 Action(1)Action['left'],一律傳回 <Action.stop: 1>

如果想在列舉時值不得重複,可以在類別上加註 enum 模組的 @unique,這麼一來若列舉時有重複的值,就會引發 ValueError。例如:

>>> from enum import IntEnum, unique
>>> @unique
... class Action(IntEnum):
...     stop  = 1
...     right = 2
...     left  = 3
...     up    = 4
...     down  = 4
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
  File "C:\Winware\Python39\lib\enum.py", line 884, in unique
    raise ValueError('duplicate values found in %r: %s' %
ValueError: duplicate values found in <enum 'Action'>: down -> up
>>>

如果不在乎值,只是單純想列舉名稱,也可以採用呼叫的方式來建立列舉,例如:

>>> Action = IntEnum('Action', ('stop', 'right', 'left', 'up', 'down'))
>>> Action
<enum 'Action'>
>>> Action.stop
<Action.stop: 1>
>>> Action.right
<Action.right: 2>
>>> list(Action)
[<Action.stop: 1>, <Action.right: 2>, <Action.left: 3>, <Action.up: 4>, <Action.down: 5>]

enum 模組的官方說明文件,還有一些關於列舉的相關說明,有興趣的話可以進一步參考。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter