enum – Support for enumerations
An enumeration is a set of symbolic names (members) bound to unique, constant values. Within an enumeration, the members can be compared by identity, and the enumeration itself can be iterated over.
官方介紹文件:8.13. enum — Support for enumerations
enum
是 Python 裏用來建立枚舉形態的標準函式庫。enum
算是比較新的標準函式庫,學習 enum
可以讓你輕鬆建立枚舉,改寫以前單獨使用 const variable 的狀況。
01. Quickstart Tutorial
從今天開始,讓我們把這樣枚舉的 Code:
1 2 3 4 5 6 7 8 9 10 11 |
# -*- coding: utf-8 -*- # Input Type TRACK = ‘track’ ALBUM = ‘album’ ARTIST = ‘atrist’ PLAYLIST = ‘playlist’ # Response Type OK = ‘ok’ NO_RESULT = ‘no_result’ |
使用 enum 來改寫:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
# -*- coding: utf-8 -*- from enum import Enum class InputType(Enum): TRACK = ‘track’ ALBUM = ‘album’ ARTIST = ‘atrist’ PLAYLIST = ‘playlist’ class ResponseType(Enum): OK = ‘ok’ NO_RESULT = ‘no_result’ |
之後就可以改用 enum 來比對型態:
1 2 3 4 5 6 |
>>> InputType.ARTIST == InputType.ALBUM False >>> InputType.ARTIST == InputType.ARTIST True >>> ResponseType.OK == ResponseType.NO_RESULT False |
透過 enum member 可以取得 name 或是 value:
1 2 3 4 |
>>> InputType.PLAYLIST.name ‘PLAYLIST’ >>> InputType.PLAYLIST.value ‘playlist’ |
透過 enum class 可以取得 enum member:
1 2 3 4 |
>>> InputType(‘playlist’) <InputType.PLAYLIST: ‘playlist’> >>> InputType[‘PLAYLIST’] <InputType.PLAYLIST: ‘playlist’> |
不同的 enum class 雖然有相同的 enum value,但比較結果是不相同的
1 2 3 4 5 6 7 8 9 10 11 |
>>> import enum >>> class Shape(enum.Enum) ... SQUARE = 1 ... CIRCLE = 2 ... >>> class Response(enum.Enum): ... OK = 1 ... BAD = 2 ... >>> Shape.SQUARE == Response.OK False |
“可是我有 legacy code 啊,改成使用 enum 就壞掉了”
沒關係,透過 globals()
就可以 hack 過去了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
>>> globals() {‘Enum’: <enum ‘Enum’>, ‘InputType’: <enum ‘InputType’>, ‘ResponseType’: <enum ‘ResponseType’>, ‘__annotations__’: {}, ‘__builtins__’: <module ‘builtins’ (built–in)>, ‘__doc__’: None, ‘__loader__’: <class ‘_frozen_importlib.BuiltinImporter’>, ‘__name__’: ‘__main__’, ‘__package__’: None, ‘__spec__’: None, ‘pprint’: <module ‘pprint’ from ‘/home/grd/Python/cpython/Lib/pprint.py’>} >>> globals().update(InputType.__members__) >>> globals().update(ResponseType.__members__) >>> globals() {‘ALBUM’: <InputType.ALBUM: ‘album’>, ‘ARTIST’: <InputType.ARTIST: ‘atrist’>, ‘Enum’: <enum ‘Enum’>, ‘InputType’: <enum ‘InputType’>, ‘NO_RESULT’: <ResponseType.NO_RESULT: ‘no_result’>, ‘OK’: <ResponseType.OK: ‘ok’>, ‘PLAYLIST’: <InputType.PLAYLIST: ‘playlist’>, ‘ResponseType’: <enum ‘ResponseType’>, ‘TRACK’: <InputType.TRACK: ‘track’>, ‘__annotations__’: {}, ‘__builtins__’: <module ‘builtins’ (built–in)>, ‘__doc__’: None, ‘__loader__’: <class ‘_frozen_importlib.BuiltinImporter’>, ‘__name__’: ‘__main__’, ‘__package__’: None, ‘__spec__’: None, ‘pprint’: <module ‘pprint’ from ‘/home/grd/Python/cpython/Lib/pprint.py’>} >>> ARTIST <InputType.ARTIST: ‘atrist’> >>> ALBUM <InputType.ALBUM: ‘album’> >>> OK <ResponseType.OK: ‘ok’> >>> |
其實應該要透過 globals() + IntEnum
才能直接將 enum member 當 int 用:
1 2 3 4 5 6 7 8 9 10 11 |
>>> class Animal(IntEnum): ... DOG = 1 ... CAT = 2 ... PANDA = 3 ... >>> globals().update(Animal.__members__) >>> DOG <Animal.DOG: 1> >>> DOG == 1 True >>> |
真是可喜可賀,可喜可賀。透過改寫枚舉方式,我們邁向現代化 Python 更進一步了。
02. HOW-TO Guides
使用 IntEnum 來簡化整數枚舉比對
透過 IntEnum
我們可以讓整數枚舉的比對變得更輕鬆一點:
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> import enum >>> class Shape(enum.IntEnum): ... SQUARE = 1 ... CIRCLE = 2 ... TRIANGLE = 3 ... >>> Shape.SQUARE == 1 True >>> Shape.CIRCLE == 2 True >>> Shape.TRIANGLE == 1 False |
此時不同 enum class 的比較會是相等的:
1 2 3 4 5 6 7 8 9 10 11 |
>>> import enum >>> class Mac(enum.IntEnum): ... AIR = 6 ... PRO = 7 ... >>> class Android(enum.IntEnum): ... MARSHMALLOW = 6 ... NOUGAT = 7 ... >>> Mac.AIR == Android.MARSHMALLOW True |
透過繼承來組合出不同的 default enum (e.g. StrEnum)
我們可以透過繼承來達到前面 IntEnum 的效果,例如說要製作 StrEnum:
1 2 3 4 5 6 7 8 9 10 11 12 |
>>> import enum >>> class StrEnum(str, enum.Enum): ... pass ... >>> class Browser(StrEnum): ... FIREFOX = ‘firefox’ ... CHROME = ‘chrome’ ... >>> Browser.FIREFOX == ‘firefox’ True >>> Browser.CHROME == ‘chromium’ False |
透過 IntFlag 來進行 bitwise 操作:
如果是 IntFlag
,執行 bitwise 操作 (&, |, ^, ~),回傳結果還會是 IntFlag 成員。如果是其他操作則會回傳 int
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
>>> class Permission(enum.IntFlag): ... R = 4 ... W = 2 ... X = 1 ... >>> Permission.R | Permission.W <Permission.R|W: 6> >>> Permission.R | Permission.X <Permission.R|X: 5> >>> Permission.R + Permission.X 5 >>> RW = Permission.R | Permission.W >>> Permission.R in RW True |
快速的創造 enum class
1 2 3 4 5 6 7 8 9 10 11 |
>>> import enum >>> Animal = enum.Enum(‘Animal’, ‘CAT DOG PANDA’) >>> Animal <enum ‘Animal’> >>> Animal.CAT <Animal.CAT: 1> >>> Animal.DOG <Animal.DOG: 2> >>> Animal.PANDA <Animal.PANDA: 3> >>> |
03. Discussions
PEP 435 – Status of discussions
枚舉型態的想法並不是第一次出現在 Python 討論之中,在 2005 年 PEP 354 有嘗試過一次而被拒絕。近期則在 Python-ideas 開啟了一組新的討論。許多新的點子在一連串的討論中被提出。經過一長串的討論後,Guido 提議將 flufl.enum
加入標準函式庫。在 Python 2013 language summit 之中這個議題被深入的討論。討論中許多的開發者希望 enum 能夠繼承 int,讓我們可以取代標準函式庫中許多的整數枚舉而不必影響到 backword compatibility。之後的討論讓開發者們提議加入 IntNum
這個特殊型態的 Enum
。
Enum
以及 IntEnum
討論的關鍵點在於,這樣與整數的比較在語意上是否具有意義。對於大多數的枚舉,拒絕與整數比較是一個 feature。可以與整數比較的 enums 會讓其可以與其他不相關的型態比較,這在多數的狀況下是不希望發生的。不過某些情況下,例如說 socket.AF_INET
,透過 IntEum
可以直接替換掉。
之後在 2013 年4月底的討論,enumeration members 的型態需要與 enum 相同: type(Color.red) == Color
。Guido 在這個議題上做出了決定,另外也對 enum 不能 subclass (除非 subclass 沒有定義新的 enumration members) 這件事情做出了決定。
這個 PEP 在 20130510 被 Guido 所接受。
Enum 實作? Enum 的 metaclass “EnumMeta”?
Enum 在定義與實作上有著諸多的限制以及創意。限制在於多處,例如不能夠使用多個 mixin_type、mixin_type 繼承時位置有限制需要在第一個、enum member 名稱不能使用 mro、創立後不能夠被繼承、如果要繼承則不能有 enum member 等等。創意在於透過 mixin_type,enum member 能夠直接比較相對應的型態、enum member 的值能夠是 object
、 None
同時會自動處理對應數值等。
這些如同魔術般的創意與限制,在 Python 如果要實作,就必定會要談到 metaclass。在這邊僅提點基本的概念:透過 metaclass EnumMeta
,當創立繼承 enum.Enum
的 enum class (e.g. Animal(enum.Enum)
),就會觸發EnumMeta.__new__
檢查以及設立這些規則與結果。之後使用 Animal
建立實例的時候就可以使用這些功能。
請參考程式碼:EnumMeta、Enum ,以及官方文件:Datamodel – metaclass。
How metaclass work?
這根本可以開專文了。請先參考 Stackoverflow 這篇文章。
Why using Enum?
有關為什麼要使用 Enum,以及 Enum 相比於其他方式的好處,請參考 stackoverflow – Python, what’s the Enum type good for? [duplicate].
04. References
- 3. Data model – metaclass
- 8.13. enum — Support for enumerations
- PEP 435 – Adding an Enum type to the Python standard libarary
Leave a Reply