Enum HOWTO¶
Enum
是一組繫結到唯一值的符號名稱。它們類似於全域性變數,但它們提供了更有用的 repr()
,分組,型別安全和一些其他功能。
當您有一個變數可以採用有限的選擇值之一時,它們最有用。 例如,一週中的幾天
>>> from enum import Enum
>>> class Weekday(Enum):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 3
... THURSDAY = 4
... FRIDAY = 5
... SATURDAY = 6
... SUNDAY = 7
或者可能是 RGB 原色
>>> from enum import Enum
>>> class Color(Enum):
... RED = 1
... GREEN = 2
... BLUE = 3
如您所見,建立一個 Enum
與編寫一個繼承自 Enum
本身的類一樣簡單。
注意
列舉成員的大小寫
由於列舉用於表示常量,並且為了避免混入類方法/屬性和列舉名稱之間的名稱衝突問題,我們強烈建議對成員使用 UPPER_CASE 名稱,並且將在我們的示例中使用該樣式。
根據列舉的性質,成員的值可能重要也可能不重要,但無論如何,該值都可以用於獲取相應的成員
>>> Weekday(3)
<Weekday.WEDNESDAY: 3>
如您所見,成員的 repr()
顯示列舉名稱、成員名稱和值。 成員的 str()
僅顯示列舉名稱和成員名稱
>>> print(Weekday.THURSDAY)
Weekday.THURSDAY
列舉成員的型別是它所屬的列舉
>>> type(Weekday.MONDAY)
<enum 'Weekday'>
>>> isinstance(Weekday.FRIDAY, Weekday)
True
列舉成員具有一個屬性,該屬性僅包含其 name
>>> print(Weekday.TUESDAY.name)
TUESDAY
同樣,它們也有一個屬性用於它們的 value
>>> Weekday.WEDNESDAY.value
3
與許多僅將列舉視為名稱/值對的語言不同,Python 列舉可以新增行為。 例如,datetime.date
有兩個返回工作日的方法:weekday()
和 isoweekday()
。 區別在於其中一個從 0-6 計數,另一個從 1-7 計數。 我們無需自己跟蹤它,而是可以向 Weekday
列舉新增一個方法,以從 date
例項中提取日期並返回匹配的列舉成員
@classmethod
def from_date(cls, date):
return cls(date.isoweekday())
完整的 Weekday
列舉現在看起來像這樣
>>> class Weekday(Enum):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 3
... THURSDAY = 4
... FRIDAY = 5
... SATURDAY = 6
... SUNDAY = 7
... #
... @classmethod
... def from_date(cls, date):
... return cls(date.isoweekday())
現在我們可以找出今天是什麼日子了! 觀察
>>> from datetime import date
>>> Weekday.from_date(date.today())
<Weekday.TUESDAY: 2>
當然,如果您在其他日子閱讀此內容,您會看到那一天。
如果我們的變數只需要一天,則此 Weekday
列舉非常有用,但是如果我們需要幾天呢? 也許我們正在編寫一個函式來繪製一週中的家務,並且不想使用 list
– 我們可以使用另一種型別的 Enum
>>> from enum import Flag
>>> class Weekday(Flag):
... MONDAY = 1
... TUESDAY = 2
... WEDNESDAY = 4
... THURSDAY = 8
... FRIDAY = 16
... SATURDAY = 32
... SUNDAY = 64
我們更改了兩件事:我們繼承自 Flag
,並且值都是 2 的冪。
就像上面的原始 Weekday
列舉一樣,我們可以進行單項選擇
>>> first_week_day = Weekday.MONDAY
>>> first_week_day
<Weekday.MONDAY: 1>
但是 Flag
還允許我們將多個成員組合到一個變數中
>>> weekend = Weekday.SATURDAY | Weekday.SUNDAY
>>> weekend
<Weekday.SATURDAY|SUNDAY: 96>
你甚至可以迭代一個 Flag
變數
>>> for day in weekend:
... print(day)
Weekday.SATURDAY
Weekday.SUNDAY
好吧,讓我們設定一些家務活
>>> chores_for_ethan = {
... 'feed the cat': Weekday.MONDAY | Weekday.WEDNESDAY | Weekday.FRIDAY,
... 'do the dishes': Weekday.TUESDAY | Weekday.THURSDAY,
... 'answer SO questions': Weekday.SATURDAY,
... }
以及一個顯示給定日期家務的函式
>>> def show_chores(chores, day):
... for chore, days in chores.items():
... if day in days:
... print(chore)
...
>>> show_chores(chores_for_ethan, Weekday.SATURDAY)
answer SO questions
在成員的實際值無關緊要的情況下,您可以節省一些工作併為值使用 auto()
>>> from enum import auto
>>> class Weekday(Flag):
... MONDAY = auto()
... TUESDAY = auto()
... WEDNESDAY = auto()
... THURSDAY = auto()
... FRIDAY = auto()
... SATURDAY = auto()
... SUNDAY = auto()
... WEEKEND = SATURDAY | SUNDAY
以程式設計方式訪問列舉成員及其屬性¶
有時以程式設計方式訪問列舉中的成員很有用(即 Color.RED
不起作用的情況,因為在編寫程式時確切的顏色是未知的)。 Enum
允許這種訪問
>>> Color(1)
<Color.RED: 1>
>>> Color(3)
<Color.BLUE: 3>
如果要按名稱訪問列舉成員,請使用專案訪問
>>> Color['RED']
<Color.RED: 1>
>>> Color['GREEN']
<Color.GREEN: 2>
如果你有一個列舉成員並且需要它的 name
或 value
>>> member = Color.RED
>>> member.name
'RED'
>>> member.value
1
複製列舉成員和值¶
具有兩個名稱相同的列舉成員是無效的
>>> class Shape(Enum):
... SQUARE = 2
... SQUARE = 3
...
Traceback (most recent call last):
...
TypeError: 'SQUARE' already defined as 2
但是,一個列舉成員可以有其他與之關聯的名稱。給定兩個具有相同值的條目 A
和 B
(並且首先定義了 A
),B
是成員 A
的別名。按值查詢 A
的值將返回成員 A
。按名稱查詢 A
將返回成員 A
。 按名稱查詢 B
也將返回成員 A
>>> class Shape(Enum):
... SQUARE = 2
... DIAMOND = 1
... CIRCLE = 3
... ALIAS_FOR_SQUARE = 2
...
>>> Shape.SQUARE
<Shape.SQUARE: 2>
>>> Shape.ALIAS_FOR_SQUARE
<Shape.SQUARE: 2>
>>> Shape(2)
<Shape.SQUARE: 2>
注意
嘗試建立與已定義的屬性(另一個成員、方法等)同名的成員,或嘗試建立與成員同名的屬性是不允許的。
確保列舉值唯一¶
預設情況下,列舉允許使用多個名稱作為同一值的別名。 如果不需要此行為,可以使用 unique()
裝飾器
>>> from enum import Enum, unique
>>> @unique
... class Mistake(Enum):
... ONE = 1
... TWO = 2
... THREE = 3
... FOUR = 3
...
Traceback (most recent call last):
...
ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE
使用自動值¶
如果確切的值不重要,您可以使用 auto
>>> from enum import Enum, auto
>>> class Color(Enum):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> [member.value for member in Color]
[1, 2, 3]
這些值由 _generate_next_value_()
選擇,該方法可以被覆蓋
>>> class AutoName(Enum):
... @staticmethod
... def _generate_next_value_(name, start, count, last_values):
... return name
...
>>> class Ordinal(AutoName):
... NORTH = auto()
... SOUTH = auto()
... EAST = auto()
... WEST = auto()
...
>>> [member.value for member in Ordinal]
['NORTH', 'SOUTH', 'EAST', 'WEST']
注意
_generate_next_value_()
方法必須在任何成員之前定義。
迭代¶
迭代列舉的成員不會提供別名
>>> list(Shape)
[<Shape.SQUARE: 2>, <Shape.DIAMOND: 1>, <Shape.CIRCLE: 3>]
>>> list(Weekday)
[<Weekday.MONDAY: 1>, <Weekday.TUESDAY: 2>, <Weekday.WEDNESDAY: 4>, <Weekday.THURSDAY: 8>, <Weekday.FRIDAY: 16>, <Weekday.SATURDAY: 32>, <Weekday.SUNDAY: 64>]
請注意,別名 Shape.ALIAS_FOR_SQUARE
和 Weekday.WEEKEND
未顯示。
特殊屬性 __members__
是名稱到成員的只讀有序對映。 它包括列舉中定義的所有名稱,包括別名
>>> for name, member in Shape.__members__.items():
... name, member
...
('SQUARE', <Shape.SQUARE: 2>)
('DIAMOND', <Shape.DIAMOND: 1>)
('CIRCLE', <Shape.CIRCLE: 3>)
('ALIAS_FOR_SQUARE', <Shape.SQUARE: 2>)
__members__
屬性可用於對列舉成員進行詳細的程式設計訪問。 例如,查詢所有別名
>>> [name for name, member in Shape.__members__.items() if member.name != name]
['ALIAS_FOR_SQUARE']
注意
標誌的別名包括設定了多個標誌的值,例如 3
,以及沒有設定任何標誌的值,即 0
。
比較¶
列舉成員按標識進行比較
>>> Color.RED is Color.RED
True
>>> Color.RED is Color.BLUE
False
>>> Color.RED is not Color.BLUE
True
不支援列舉值之間的有序比較。 列舉成員不是整數(但請參見下面的 IntEnum)
>>> Color.RED < Color.BLUE
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: '<' not supported between instances of 'Color' and 'Color'
但是定義了相等比較
>>> Color.BLUE == Color.RED
False
>>> Color.BLUE != Color.RED
True
>>> Color.BLUE == Color.BLUE
True
與非列舉值的比較將始終比較為不相等(再次強調,IntEnum
的設計初衷就是行為不同,請見下文)
>>> Color.BLUE == 2
False
警告
模組可以被重新載入——如果重新載入的模組包含列舉,它們將被重新建立,並且新的成員可能與原始成員不完全相同/相等。
列舉的允許成員和屬性¶
上面的大多數示例都使用整數作為列舉值。使用整數簡短方便(並且由函式式 API 提供預設值),但並非強制要求。在絕大多數用例中,人們並不關心列舉的實際值是什麼。但是如果值是重要的,列舉可以有任意值。
列舉是 Python 類,並且可以像往常一樣擁有方法和特殊方法。如果我們有以下列舉
>>> class Mood(Enum):
... FUNKY = 1
... HAPPY = 3
...
... def describe(self):
... # self is the member here
... return self.name, self.value
...
... def __str__(self):
... return 'my custom str! {0}'.format(self.value)
...
... @classmethod
... def favorite_mood(cls):
... # cls here is the enumeration
... return cls.HAPPY
...
那麼
>>> Mood.favorite_mood()
<Mood.HAPPY: 3>
>>> Mood.HAPPY.describe()
('HAPPY', 3)
>>> str(Mood.FUNKY)
'my custom str! 1'
允許的規則如下:以單個下劃線開頭和結尾的名稱由列舉保留,不能使用;列舉中定義的所有其他屬性將成為此列舉的成員,特殊方法(__str__()
、__add__()
等)、描述符(方法也是描述符)以及 _ignore_
中列出的變數名除外。
注意:如果你的列舉定義了 __new__()
和/或 __init__()
,則傳遞給列舉成員的任何值都將傳遞給這些方法。有關示例,請參見 Planet。
注意
如果定義了 __new__()
方法,則在建立列舉成員期間使用它;然後它將被 Enum 的 __new__()
替換,該方法在類建立後用於查詢現有成員。有關更多詳細資訊,請參見 何時使用 __new__() 與 __init__()。
受限的列舉子類化¶
新的 Enum
類必須有一個基礎列舉類,最多一個具體的資料型別,以及根據需要多個基於 object
的 mixin 類。這些基類的順序是
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
此外,僅當列舉未定義任何成員時,才允許對列舉進行子類化。因此,這是禁止的
>>> class MoreColor(Color):
... PINK = 17
...
Traceback (most recent call last):
...
TypeError: <enum 'MoreColor'> cannot extend <enum 'Color'>
但是這是允許的
>>> class Foo(Enum):
... def some_behavior(self):
... pass
...
>>> class Bar(Foo):
... HAPPY = 1
... SAD = 2
...
允許對定義成員的列舉進行子類化會導致違反型別和例項的一些重要不變數。另一方面,允許在一組列舉之間共享一些通用行為是有意義的。(有關示例,請參見 OrderedEnum。)
資料類支援¶
當從 dataclass
繼承時,__repr__()
省略了繼承類的名稱。例如
>>> from dataclasses import dataclass, field
>>> @dataclass
... class CreatureDataMixin:
... size: str
... legs: int
... tail: bool = field(repr=False, default=True)
...
>>> class Creature(CreatureDataMixin, Enum):
... BEETLE = 'small', 6
... DOG = 'medium', 4
...
>>> Creature.DOG
<Creature.DOG: size='medium', legs=4>
使用 dataclass()
引數 repr=False
以使用標準的 repr()
。
在 3.12 版本中更改:只在值區域顯示資料類欄位,而不是資料類的名稱。
注意
不支援將 dataclass()
裝飾器新增到 Enum
及其子類。它不會引發任何錯誤,但會在執行時產生非常奇怪的結果,例如成員彼此相等
>>> @dataclass # don't do this: it does not make any sense
... class Color(Enum):
... RED = 1
... BLUE = 2
...
>>> Color.RED is Color.BLUE
False
>>> Color.RED == Color.BLUE # problem is here: they should not be equal
True
序列化¶
列舉可以被序列化和反序列化
>>> from test.test_enum import Fruit
>>> from pickle import dumps, loads
>>> Fruit.TOMATO is loads(dumps(Fruit.TOMATO))
True
通常的序列化限制適用:可序列化的列舉必須在模組的頂層定義,因為反序列化需要它們可以從該模組匯入。
注意
使用 pickle 協議版本 4,可以輕鬆地序列化巢狀在其他類中的列舉。
可以透過在列舉類中定義 __reduce_ex__()
來修改列舉成員的序列化/反序列化方式。預設方法是按值,但是具有複雜值的列舉可能希望使用按名稱
>>> import enum
>>> class MyEnum(enum.Enum):
... __reduce_ex__ = enum.pickle_by_enum_name
注意
不建議對標誌使用按名稱,因為未命名的別名將不會被反序列化。
函式式 API¶
Enum
類是可呼叫的,提供以下函式式 API
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG')
>>> Animal
<enum 'Animal'>
>>> Animal.ANT
<Animal.ANT: 1>
>>> list(Animal)
[<Animal.ANT: 1>, <Animal.BEE: 2>, <Animal.CAT: 3>, <Animal.DOG: 4>]
此 API 的語義類似於 namedtuple
。Enum
呼叫的第一個引數是列舉的名稱。
第二個引數是列舉成員名稱的源。它可以是由空格分隔的名稱字串,名稱序列,帶有鍵/值對的 2 元組序列,或名稱到值的對映(例如字典)。最後兩個選項允許為列舉分配任意值;其他選項會自動分配從 1 開始遞增的整數(使用 start
引數來指定不同的起始值)。返回從 Enum
派生的新類。換句話說,上面對 Animal
的賦值等同於
>>> class Animal(Enum):
... ANT = 1
... BEE = 2
... CAT = 3
... DOG = 4
...
預設使用 1
而不是 0
作為起始數字的原因是 0
在布林意義上是 False
,但是預設情況下,列舉成員都評估為 True
。
使用函式式 API 建立的列舉的序列化可能很棘手,因為使用幀棧實現細節來嘗試確定列舉是在哪個模組中建立的(例如,如果您在單獨的模組中使用實用程式函式,它將失敗,並且可能在 IronPython 或 Jython 上也不起作用)。解決方案是顯式指定模組名稱,如下所示
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', module=__name__)
警告
如果未提供 module
並且 Enum 無法確定它是什麼,則新的 Enum 成員將無法反序列化;為了使錯誤更接近源頭,將停用序列化。
新的 pickle 協議 4 也在某些情況下依賴於 __qualname__
設定為 pickle 將能夠找到該類的位置。例如,如果該類在全域性範圍內的類 SomeData 中可用
>>> Animal = Enum('Animal', 'ANT BEE CAT DOG', qualname='SomeData.Animal')
完整的簽名是
Enum(
value='NewEnumName',
names=<...>,
*,
module='...',
qualname='...',
type=<mixed-in class>,
start=1,
)
value:新列舉類將記錄為其名稱的內容。
names:列舉成員。這可以是以空格或逗號分隔的字串(值將從 1 開始,除非另有指定)
'RED GREEN BLUE' | 'RED,GREEN,BLUE' | 'RED, GREEN, BLUE'
或名稱的迭代器
['RED', 'GREEN', 'BLUE']
或(名稱,值)對的迭代器
[('CYAN', 4), ('MAGENTA', 5), ('YELLOW', 6)]
或對映
{'CHARTREUSE': 7, 'SEA_GREEN': 11, 'ROSEMARY': 42}
module:新列舉類可以在其中找到的模組名稱。
qualname:新列舉類可以在模組中的哪個位置找到。
type:要混入新列舉類的型別。
start:如果僅傳遞名稱,則從哪個數字開始計數。
在 3.5 版本中更改:添加了 start 引數。
派生列舉¶
IntEnum¶
提供的 Enum
的第一個變體也是 int
的子類。IntEnum
的成員可以與整數進行比較;透過擴充套件,不同型別的整數列舉也可以相互比較
>>> from enum import IntEnum
>>> class Shape(IntEnum):
... CIRCLE = 1
... SQUARE = 2
...
>>> class Request(IntEnum):
... POST = 1
... GET = 2
...
>>> Shape == 1
False
>>> Shape.CIRCLE == 1
True
>>> Shape.CIRCLE == Request.POST
True
但是,它們仍然不能與標準的 Enum
列舉進行比較
>>> class Shape(IntEnum):
... CIRCLE = 1
... SQUARE = 2
...
>>> class Color(Enum):
... RED = 1
... GREEN = 2
...
>>> Shape.CIRCLE == Color.RED
False
IntEnum
的值在其他方面也表現得像整數一樣。
>>> int(Shape.CIRCLE)
1
>>> ['a', 'b', 'c'][Shape.CIRCLE]
'b'
>>> [i for i in range(Shape.SQUARE)]
[0, 1]
StrEnum¶
提供的 Enum
的第二種變體也是 str
的子類。StrEnum
的成員可以與字串進行比較;由此,不同型別的字串列舉也可以相互比較。
3.11 版本新增。
IntFlag¶
提供的 Enum
的下一種變體 IntFlag
,也是基於 int
的。不同之處在於,IntFlag
成員可以使用位運算子(&、|、^、~)進行組合,並且如果可能,結果仍然是 IntFlag
成員。與 IntEnum
一樣,IntFlag
成員也是整數,可以在任何使用 int
的地方使用。
注意
除了位運算之外,對 IntFlag
成員的任何操作都會丟失 IntFlag
成員資格。
導致無效 IntFlag
值的位運算將丟失 IntFlag
成員資格。有關詳細資訊,請參閱 FlagBoundary
。
3.6 版本新增。
3.11 版本更改。
IntFlag
類的示例
>>> from enum import IntFlag
>>> class Perm(IntFlag):
... R = 4
... W = 2
... X = 1
...
>>> Perm.R | Perm.W
<Perm.R|W: 6>
>>> Perm.R + Perm.W
6
>>> RW = Perm.R | Perm.W
>>> Perm.R in RW
True
也可以命名組合
>>> class Perm(IntFlag):
... R = 4
... W = 2
... X = 1
... RWX = 7
...
>>> Perm.RWX
<Perm.RWX: 7>
>>> ~Perm.RWX
<Perm: 0>
>>> Perm(7)
<Perm.RWX: 7>
注意
命名的組合被視為別名。別名在迭代期間不會顯示,但可以從按值查詢返回。
3.11 版本更改。
IntFlag
和 Enum
之間的另一個重要區別是,如果沒有設定標誌(值為 0),則其布林值評估為 False
。
>>> Perm.R & Perm.X
<Perm: 0>
>>> bool(Perm.R & Perm.X)
False
因為 IntFlag
成員也是 int
的子類,它們可以與它們組合(但可能會丟失 IntFlag
成員資格)
>>> Perm.X | 4
<Perm.R|X: 5>
>>> Perm.X + 8
9
也可以迭代 IntFlag
成員
>>> list(RW)
[<Perm.R: 4>, <Perm.W: 2>]
3.11 版本新增。
Flag¶
最後一種變體是 Flag
。與 IntFlag
類似,Flag
成員可以使用位運算子(&、|、^、~)進行組合。與 IntFlag
不同,它們不能與任何其他 Flag
列舉進行組合或比較,也不能與 int
進行組合或比較。雖然可以直接指定值,但建議使用 auto
作為值,並讓 Flag
選擇適當的值。
3.6 版本新增。
與 IntFlag
一樣,如果 Flag
成員的組合導致未設定任何標誌,則布林值評估為 False
。
>>> from enum import Flag, auto
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.RED & Color.GREEN
<Color: 0>
>>> bool(Color.RED & Color.GREEN)
False
單個標誌應具有 2 的冪的值(1、2、4、8,…),而標誌的組合則不會
>>> class Color(Flag):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
... WHITE = RED | BLUE | GREEN
...
>>> Color.WHITE
<Color.WHITE: 7>
為“未設定任何標誌”的情況命名不會更改其布林值
>>> class Color(Flag):
... BLACK = 0
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.BLACK
<Color.BLACK: 0>
>>> bool(Color.BLACK)
False
也可以迭代 Flag
成員
>>> purple = Color.RED | Color.BLUE
>>> list(purple)
[<Color.RED: 1>, <Color.BLUE: 2>]
3.11 版本新增。
其他¶
雖然 IntEnum
是 enum
模組的一部分,但獨立實現它非常簡單
class IntEnum(int, ReprEnum): # or Enum instead of ReprEnum
pass
這演示瞭如何定義類似的派生列舉;例如,一個混合了 float
而不是 int
的 FloatEnum
。
一些規則
混合型別必須是可子類化的。例如,
bool
和range
是不可子類化的,如果在 Enum 建立期間用作混合型別,則會引發錯誤。雖然
Enum
可以具有任何型別的成員,但一旦您混合新增其他型別,則所有成員都必須具有該型別的值,例如,上面的int
。此限制不適用於僅新增方法而不指定其他型別的混合。當混合新增另一種資料型別時,
value
屬性與列舉成員本身不同,儘管它等效並且將比較相等。%-樣式格式化:
%s
和%r
分別呼叫Enum
類的__str__()
和__repr__()
方法;其他程式碼(例如,%i
或%h
用於 IntEnum)將列舉成員視為其混合型別的成員。格式化字串字面值,
str.format()
,以及format()
將使用列舉的__str__()
方法。
何時使用 __new__()
vs. __init__()
¶
只要你想自定義 Enum
成員的實際值,就必須使用 __new__()
。任何其他的修改都可以放在 __new__()
或 __init__()
中,但最好使用 __init__()
。
例如,如果你想傳遞多個項給建構函式,但只想要其中一個作為值
>>> class Coordinate(bytes, Enum):
... """
... Coordinate with binary codes that can be indexed by the int code.
... """
... def __new__(cls, value, label, unit):
... obj = bytes.__new__(cls, [value])
... obj._value_ = value
... obj.label = label
... obj.unit = unit
... return obj
... PX = (0, 'P.X', 'km')
... PY = (1, 'P.Y', 'km')
... VX = (2, 'V.X', 'km/s')
... VY = (3, 'V.Y', 'km/s')
...
>>> print(Coordinate['PY'])
Coordinate.PY
>>> print(Coordinate(3))
Coordinate.VY
警告
不要 呼叫 super().__new__()
,因為找到的只是用於查詢的 __new__
;相反,直接使用資料型別。
更精細的點¶
支援的 __dunder__
名稱¶
__members__
是一個只讀的有序對映,包含 member_name
:member
項。它僅在類上可用。
__new__()
,如果指定了,必須建立並返回列舉成員;最好也適當設定成員的 _value_
。一旦所有成員被建立,它就不再使用。
支援的 _sunder_
名稱¶
_name_
– 成員的名稱_value_
– 成員的值;可以在__new__
中設定_missing_()
– 當找不到值時使用的查詢函式;可以被覆蓋_generate_next_value_()
– 用於獲取列舉成員的適當值;可以被覆蓋_add_alias_()
– 新增一個新名稱作為現有成員的別名。_add_value_alias_()
– 新增一個新值作為現有成員的別名。有關示例,請參見 MultiValueEnum。在 3.13 版本中更改:之前的版本會使用最後看到的值而不是最高值。
3.6 版本新增:_missing_
, _order_
, _generate_next_value_
3.7 版本新增:_ignore_
3.13 版本新增:_add_alias_
, _add_value_alias_
為了幫助保持 Python 2/Python 3 程式碼同步,可以提供 _order_
屬性。它將與列舉的實際順序進行檢查,如果兩者不匹配,則會引發錯誤。
>>> class Color(Enum):
... _order_ = 'RED GREEN BLUE'
... RED = 1
... BLUE = 3
... GREEN = 2
...
Traceback (most recent call last):
...
TypeError: member order does not match _order_:
['RED', 'BLUE', 'GREEN']
['RED', 'GREEN', 'BLUE']
注意
在 Python 2 程式碼中,_order_
屬性是必要的,因為在可以記錄之前,定義順序會丟失。
_私有__名稱¶
私有名稱 不會轉換為列舉成員,但仍然是普通屬性。
3.11 版本更改。
Enum
成員型別¶
列舉成員是其列舉類的例項,通常透過 EnumClass.member
訪問。在某些情況下,例如編寫自定義列舉行為時,能夠直接從一個成員訪問另一個成員很有用,並且是支援的;但是,為了避免成員名稱與混合類的屬性/方法之間發生名稱衝突,強烈建議使用大寫名稱。
在 3.5 版本中更改。
建立與其他資料型別混合的成員¶
當使用 Enum
對其他資料型別(例如 int
或 str
)進行子類化時,=
之後的所有值都會傳遞給該資料型別的建構函式。例如
>>> class MyEnum(IntEnum): # help(int) -> int(x, base=10) -> integer
... example = '11', 16 # so x='11' and base=16
...
>>> MyEnum.example.value # and hex(11) is...
17
Enum
類和成員的布林值¶
與非 Enum
型別(例如 int
,str
等)混合的列舉類會根據混合型別的規則進行評估;否則,所有成員的評估結果均為 True
。要使你自己的列舉的布林評估依賴於成員的值,請將以下內容新增到你的類中
def __bool__(self):
return bool(self.value)
具有方法的 Enum
類¶
如果你給你的列舉子類新增額外的方法,如下面的 Planet 類,這些方法將顯示在成員的 dir()
中,而不是類的 dir()
中
>>> dir(Planet)
['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', 'VENUS', '__class__', '__doc__', '__members__', '__module__']
>>> dir(Planet.EARTH)
['__class__', '__doc__', '__module__', 'mass', 'name', 'radius', 'surface_gravity', 'value']
組合 Flag
的成員¶
迭代 Flag
成員的組合只會返回由單個位組成的成員
>>> class Color(Flag):
... RED = auto()
... GREEN = auto()
... BLUE = auto()
... MAGENTA = RED | BLUE
... YELLOW = RED | GREEN
... CYAN = GREEN | BLUE
...
>>> Color(3) # named combination
<Color.YELLOW: 3>
>>> Color(7) # not named combination
<Color.RED|GREEN|BLUE: 7>
Flag
和 IntFlag
的細節¶
對我們的示例使用以下程式碼片段
>>> class Color(IntFlag):
... BLACK = 0
... RED = 1
... GREEN = 2
... BLUE = 4
... PURPLE = RED | BLUE
... WHITE = RED | GREEN | BLUE
...
以下情況屬實:
單位元標誌是規範的
多位元和零位元標誌是別名
迭代期間只返回規範標誌
>>> list(Color.WHITE) [<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
對標誌或標誌集取反會返回一個新的標誌/標誌集,其對應的正整數值
>>> Color.BLUE <Color.BLUE: 4> >>> ~Color.BLUE <Color.RED|GREEN: 3>
偽標誌的名稱由其成員的名稱構成
>>> (Color.RED | Color.GREEN).name 'RED|GREEN' >>> class Perm(IntFlag): ... R = 4 ... W = 2 ... X = 1 ... >>> (Perm.R & Perm.W).name is None # effectively Perm(0) True
多位元標誌(又稱別名)可以從操作中返回
>>> Color.RED | Color.BLUE <Color.PURPLE: 5> >>> Color(7) # or Color(-1) <Color.WHITE: 7> >>> Color(0) <Color.BLACK: 0>
成員資格/包含檢查:零值標誌始終被視為包含在內
>>> Color.BLACK in Color.WHITE True
否則,只有當一個標誌的所有位都包含在另一個標誌中時,才會返回 True
>>> Color.PURPLE in Color.WHITE True >>> Color.GREEN in Color.PURPLE False
存在一種新的邊界機制,用於控制如何處理超出範圍/無效的位:STRICT
,CONFORM
,EJECT
和 KEEP
STRICT –> 當遇到無效值時引發異常
CONFORM –> 丟棄任何無效位
EJECT –> 失去 Flag 狀態,並變為具有給定值的普通 int
KEEP –> 保留額外的位
保留 Flag 狀態和額外的位
額外的位不會在迭代中顯示
額外的位會在 repr() 和 str() 中顯示
Flag 的預設值為 STRICT
,IntFlag
的預設值為 EJECT
,而 _convert_
的預設值為 KEEP
(有關何時需要 KEEP
的示例,請參見 ssl.Options
)。
列舉和標誌有什麼不同?¶
列舉具有自定義元類,該元類會影響派生的 Enum
類及其例項(成員)的許多方面。
列舉類¶
EnumType
元類負責提供 __contains__()
、__dir__()
、__iter__()
和其他方法,這些方法允許人們對 Enum
類執行在典型類上會失敗的操作,例如 list(Color)
或 some_enum_var in Color
。EnumType
負責確保最終 Enum
類上的各種其他方法是正確的(例如 __new__()
、__getnewargs__()
、__str__()
和 __repr__()
)。
標誌類¶
標誌具有更廣泛的別名檢視:要成為規範的,標誌的值必須是 2 的冪值,而不是重複的名稱。因此,除了 Enum
的別名定義之外,沒有值的標誌(又名 0
)或具有多個 2 的冪值的標誌(例如 3
)被認為是別名。
列舉成員(又名例項)¶
關於列舉成員最有趣的事情是它們是單例。EnumType
在建立列舉類本身時會建立所有成員,然後設定一個自定義的 __new__()
,以確保不會例項化任何新的成員,而是僅返回現有成員例項。
標誌成員¶
標誌成員可以像 Flag
類一樣進行迭代,並且只會返回規範成員。例如
>>> list(Color)
[<Color.RED: 1>, <Color.GREEN: 2>, <Color.BLUE: 4>]
(請注意,BLACK
、PURPLE
和 WHITE
不會顯示。)
反轉標誌成員會返回相應的正值,而不是負值 — 例如
>>> ~Color.RED
<Color.GREEN|BLUE: 6>
標誌成員的長度與其包含的 2 的冪值的數量相對應。例如
>>> len(Color.PURPLE)
2
列舉 Cookbook¶
雖然 Enum
、IntEnum
、StrEnum
、Flag
和 IntFlag
預計將涵蓋大多數用例,但它們無法涵蓋所有用例。以下是一些不同型別的列舉的示例,可以直接使用,也可以用作建立自己的列舉的示例。
省略值¶
在許多用例中,人們並不關心列舉的實際值是什麼。有幾種方法可以定義這種型別的簡單列舉
使用這些方法中的任何一種都向使用者表明這些值並不重要,並且還可以新增、刪除或重新排序成員,而無需重新編號其餘成員。
使用 auto
¶
使用 auto
看起來像
>>> class Color(Enum):
... RED = auto()
... BLUE = auto()
... GREEN = auto()
...
>>> Color.GREEN
<Color.GREEN: 3>
使用 object
¶
使用 object
看起來像
>>> class Color(Enum):
... RED = object()
... GREEN = object()
... BLUE = object()
...
>>> Color.GREEN
<Color.GREEN: <object object at 0x...>>
這也是一個很好的示例,說明為什麼您可能想要編寫自己的 __repr__()
>>> class Color(Enum):
... RED = object()
... GREEN = object()
... BLUE = object()
... def __repr__(self):
... return "<%s.%s>" % (self.__class__.__name__, self._name_)
...
>>> Color.GREEN
<Color.GREEN>
使用描述性字串¶
使用字串作為值看起來像
>>> class Color(Enum):
... RED = 'stop'
... GREEN = 'go'
... BLUE = 'too fast!'
...
>>> Color.GREEN
<Color.GREEN: 'go'>
使用自定義的 __new__()
¶
使用自動編號的 __new__()
看起來像
>>> class AutoNumber(Enum):
... def __new__(cls):
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
>>> class Color(AutoNumber):
... RED = ()
... GREEN = ()
... BLUE = ()
...
>>> Color.GREEN
<Color.GREEN: 2>
要建立一個更通用的 AutoNumber
,請將 *args
新增到簽名中
>>> class AutoNumber(Enum):
... def __new__(cls, *args): # this is the only change from above
... value = len(cls.__members__) + 1
... obj = object.__new__(cls)
... obj._value_ = value
... return obj
...
然後,當您從 AutoNumber
繼承時,您可以編寫自己的 __init__
來處理任何額外的引數
>>> class Swatch(AutoNumber):
... def __init__(self, pantone='unknown'):
... self.pantone = pantone
... AUBURN = '3497'
... SEA_GREEN = '1246'
... BLEACHED_CORAL = () # New color, no Pantone code yet!
...
>>> Swatch.SEA_GREEN
<Swatch.SEA_GREEN: 2>
>>> Swatch.SEA_GREEN.pantone
'1246'
>>> Swatch.BLEACHED_CORAL.pantone
'unknown'
警告
不要 呼叫 super().__new__()
,因為找到的是隻用於查詢的 __new__
;相反,直接使用資料型別——例如:
obj = int.__new__(cls, value)
OrderedEnum¶
一個不基於 IntEnum
的有序列舉,因此保持了正常的 Enum
不變數(例如,不能與其他列舉進行比較)
>>> class OrderedEnum(Enum):
... def __ge__(self, other):
... if self.__class__ is other.__class__:
... return self.value >= other.value
... return NotImplemented
... def __gt__(self, other):
... if self.__class__ is other.__class__:
... return self.value > other.value
... return NotImplemented
... def __le__(self, other):
... if self.__class__ is other.__class__:
... return self.value <= other.value
... return NotImplemented
... def __lt__(self, other):
... if self.__class__ is other.__class__:
... return self.value < other.value
... return NotImplemented
...
>>> class Grade(OrderedEnum):
... A = 5
... B = 4
... C = 3
... D = 2
... F = 1
...
>>> Grade.C < Grade.A
True
DuplicateFreeEnum¶
如果找到重複的成員值,則引發錯誤,而不是建立別名
>>> class DuplicateFreeEnum(Enum):
... def __init__(self, *args):
... cls = self.__class__
... if any(self.value == e.value for e in cls):
... a = self.name
... e = cls(self.value).name
... raise ValueError(
... "aliases not allowed in DuplicateFreeEnum: %r --> %r"
... % (a, e))
...
>>> class Color(DuplicateFreeEnum):
... RED = 1
... GREEN = 2
... BLUE = 3
... GRENE = 2
...
Traceback (most recent call last):
...
ValueError: aliases not allowed in DuplicateFreeEnum: 'GRENE' --> 'GREEN'
注意
這是一個有用的示例,用於子類化 Enum 以新增或更改其他行為,以及禁止別名。如果唯一需要的更改是禁止別名,則可以使用 unique()
裝飾器代替。
MultiValueEnum¶
支援每個成員具有多個值
>>> class MultiValueEnum(Enum):
... def __new__(cls, value, *values):
... self = object.__new__(cls)
... self._value_ = value
... for v in values:
... self._add_value_alias_(v)
... return self
...
>>> class DType(MultiValueEnum):
... float32 = 'f', 8
... double64 = 'd', 9
...
>>> DType('f')
<DType.float32: 'f'>
>>> DType(9)
<DType.double64: 'd'>
Planet¶
如果定義了 __new__()
或 __init__()
,列舉成員的值將被傳遞給這些方法
>>> class Planet(Enum):
... MERCURY = (3.303e+23, 2.4397e6)
... VENUS = (4.869e+24, 6.0518e6)
... EARTH = (5.976e+24, 6.37814e6)
... MARS = (6.421e+23, 3.3972e6)
... JUPITER = (1.9e+27, 7.1492e7)
... SATURN = (5.688e+26, 6.0268e7)
... URANUS = (8.686e+25, 2.5559e7)
... NEPTUNE = (1.024e+26, 2.4746e7)
... def __init__(self, mass, radius):
... self.mass = mass # in kilograms
... self.radius = radius # in meters
... @property
... def surface_gravity(self):
... # universal gravitational constant (m3 kg-1 s-2)
... G = 6.67300E-11
... return G * self.mass / (self.radius * self.radius)
...
>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129
TimePeriod¶
一個展示 _ignore_
屬性用法的示例
>>> from datetime import timedelta
>>> class Period(timedelta, Enum):
... "different lengths of time"
... _ignore_ = 'Period i'
... Period = vars()
... for i in range(367):
... Period['day_%d' % i] = i
...
>>> list(Period)[:2]
[<Period.day_0: datetime.timedelta(0)>, <Period.day_1: datetime.timedelta(days=1)>]
>>> list(Period)[-2:]
[<Period.day_365: datetime.timedelta(days=365)>, <Period.day_366: datetime.timedelta(days=366)>]
子類化 EnumType¶
雖然大多數列舉需求可以透過自定義 Enum
子類(使用類裝飾器或自定義函式)來滿足,但可以子類化 EnumType
來提供不同的 Enum 體驗。