前情提示: 測(cè)試代碼中腥沽,右尖括號(hào)(>
)表示命令行中輸入的命令谦炒; 單獨(dú)一行并以井字符(#
)開頭的為輸出內(nèi)容; 庫(kù)的導(dǎo)入僅在本文的第一個(gè)測(cè)試代碼中展現(xiàn)藤为,其他代碼塊均省略庫(kù)的導(dǎo)入代碼。
-
系統(tǒng)類型:
Windows 10
-
python 版本:
Python 3.9.0
枚舉是一組符號(hào)名稱(枚舉成員)的集合夺刑,枚舉成員應(yīng)該是唯一的缅疟、不可變的。
enum
模塊將分為三個(gè)部分解析遍愿,第一部分主要介紹枚舉的特性和 Enum
類存淫,第二部分將介紹 Enum
類的三個(gè)變種類及自定義類,第三部分將更深入的了解枚舉沼填。
傳送門
創(chuàng)建一個(gè) Enum
枚舉是使用 class
語法來創(chuàng)建的桅咆,這使得它們易于讀寫。但還是有一種以使用函數(shù)形式的創(chuàng)建方式坞笙,這個(gè)留到下文再講岩饼,現(xiàn)在主要來說明一下使用 class
語法的創(chuàng)建方式荚虚。
import enum
class Test(enum.Enum):
A = 1
B = 'b'
上面的代碼創(chuàng)建了一個(gè)枚舉,class Test
是一個(gè)枚舉
籍茧,其中 Test
就是枚舉對(duì)象的名稱版述。
A = 1
、B = 'b'
這些等號(hào)兩邊的整體被稱為 枚舉成員硕糊,等號(hào)左邊的被稱為枚舉成員的名稱院水,枚舉通常是用來表示常量的,所以枚舉成員的名稱一般使用大寫形式简十。等號(hào)右邊的被稱為枚舉成員的值,枚舉成員的值可以是任意類型撬腾,如 int
螟蝙、str
等等,只需要注意枚舉成員的值在 枚舉對(duì)象中是唯一的民傻。
雖然創(chuàng)建枚舉時(shí)使用了 class
語法胰默,但是枚舉與普通的 python
類不同,可以打印一些信息來進(jìn)行對(duì)比:
'''枚舉'''
class Test(enum.Enum):
A = 1
print(Test) # 打印枚舉
# <enum 'Test'>
print(Test.A) # 打印枚舉成員本身
# Test.A
print(repr(Test.A)) # 打印枚舉成員在解釋器中的形式
# <Test.A: 1>
print(type(Test.A)) # 打印枚舉成員類型
# <enum 'Test'>
print(isinstance(Test.A, Test)) # 判斷枚舉成員類型是否有枚舉類型一致
# True
'''普通的 python 類'''
class Common:
A = 1
print(Common) # 打印類
# <class '__main__.Common'>
print(Common.A) # 打印類屬性的值
# 1
print(repr(Common.A)) # 打印類屬性的值在解釋器中的形式
# 1
print(type(Common.A)) # 打印類屬性的值的類型
# <class 'int'>
print(isinstance(Common.A, Test)) # 判斷類屬性的值的類型是否有類的類型一致
# False
首先漓踢,枚舉創(chuàng)建出的對(duì)象自成一種類型牵署,類型名就是枚舉的名稱(類名)。枚舉的枚舉成員(類的屬性)被創(chuàng)建成了一個(gè)對(duì)象喧半,并且類型就是所屬枚舉的類型奴迅。
枚舉可以按照定義順序進(jìn)行迭代,并且枚舉成員是不可變的挺据,是可哈希的取具,因此字典中的鍵可以使用枚舉成員。
class Test(enum.Enum):
A = 1
test_dict = {}
for i in Test:
test_dict[i] = 'value.' + str(i)
print(test_dict)
# {<Test.A: 1>: 'value.Test.A', <Test.B: 'b'>: 'value.Test.B', <Test.C: 'c'>: 'value.Test.C'}
print(test_dict[Test.A])
# value.Test.A
那么怎么獲取枚舉成員呢扁耐,在枚舉中有很多種訪問方式:
class Test(enum.Enum):
A = 1
print(Test(1)) # 程序化訪問
# Test.A
print(Test['A']) # 條目訪問
# Test.A
print(Test.A.value) # 枚舉成員的 value 屬性
# 1
print(Test.A.name) # 枚舉成員的 name 屬性
# A
枚舉中不允許有相同的枚舉成員暇检,但是允許枚舉成員有同樣的值,若查找的值有多個(gè)枚舉成員婉称,那么會(huì)按定義順序返回第一個(gè)块仆。
'''枚舉成員有相同的名稱'''
class Test(enum.Enum):
A = 1
A = 2
# TypeError: Attempted to reuse key: 'A'
'''枚舉成員有相同的值'''
class Test(enum.Enum):
A = 1
B = 1
print(Test(1))
# Test.A
enum
模塊中定義了一個(gè)裝飾器 -- @enum.unique
,可以強(qiáng)制規(guī)定枚舉成員的值也必須是唯一的王暗。
@enum.unique
class Test(enum.Enum):
A = 1
B = 1
# ValueError: duplicate values found in <enum 'Test'>: B -> A
若枚舉成員的值不重要或者其他不需要一一指定枚舉成員的場(chǎng)景下悔据,可以使用 enum.auto()
方法來替代輸入的值。
class Test(enum.Enum):
A = enum.auto()
B = enum.auto()
C = 1
print(repr(Test.A))
# <Test.A: 1>
print(repr(Test.B))
# <Test.B: 2>
print(repr(Test.C))
# <Test.A: 1>
print(list(Test))
# [<Test.A: 1>, <Test.B: 2>]
當(dāng)使用 enum.auto()
來定義枚舉成員的值時(shí)瘫筐,再次使用 int
類型的值時(shí)蜜暑,最后得到的結(jié)果可能會(huì)和預(yù)想的不太一樣。
enum.auto()
函數(shù)的返回值是由 _generate_next_value_()
函數(shù)決定的策肝,默認(rèn)情況下肛捍,此函數(shù)是根據(jù)最后一個(gè) int
類型的枚舉成員的值增加 1
隐绵。此函數(shù)是可以重載的,重載時(shí)拙毫,方法定義必須在任何成員之前依许。
'''先定義一個(gè)值為 int 類型的枚舉成員, 再使用 auto() 函數(shù)'''
class Test(enum.Enum):
A = 2
B = enum.auto()
C = enum.auto()
print(list(Test))
# [<Test.A: 2>, <Test.B: 3>, <Test.C: 4>]
'''定義一個(gè)值為非 int 類型的枚舉成員, 再使用 auto() 函數(shù)'''
class Test(enum.Enum):
A = 'b'
B = enum.auto()
C = enum.auto()
print(list(Test))
# [<Test.A: 'b'>, <Test.B: 1>, <Test.C: 2>]
'''重載 _generate_next_value_()'''
class Test(enum.Enum):
def _generate_next_value_(name, start, count, last_values):
return name # 返回枚舉成員的名字
A = enum.auto()
B = enum.auto()
print(list(Test))
# [<Test.A: 'A'>, <Test.B: 'B'>]
在介紹 enum.auto()
函數(shù)時(shí),測(cè)試示例中缀蹄,枚舉對(duì)象轉(zhuǎn)換為 list
類型時(shí)少了一個(gè)枚舉成員峭跳,這是因?yàn)樯俚哪莻€(gè)枚舉成員與之前的枚舉成員的值是一樣的,這個(gè)枚舉成員被認(rèn)為是一個(gè)別名缺前,當(dāng)對(duì)枚舉成員迭代時(shí)蛀醉,不會(huì)給出別名。
class Test(enum.Enum):
A = 1
B = 1
print(list(Test))
# [<Test.A: 1>]
而特殊屬性 __members__
是一個(gè)從名稱到成員的只讀有序映射衅码。它包含枚舉中所有的枚舉成員拯刁,但是在迭代時(shí),因?yàn)閯e名的原因逝段,指向的枚舉成員還是該值第一個(gè)定義的枚舉成員垛玻。
class Test(enum.Enum):
A = 1
B = 1
print(dict(Test.__members__))
# {'A': <Test.A: 1>, 'B': <Test.A: 1>}
小栗子,找出枚舉中所有的別名:
class Test(enum.Enum):
A = 1
B = 1
C = 2
D = 2
print([名稱 for 名稱, 枚舉成員 in Test.__members__.items() if 枚舉成員.name != 名稱])
# ['B', 'D']
在枚舉中奶躯,不僅僅只可以定義一些枚舉成員帚桩,枚舉也屬于 python
的類,是可以擁有普通方法和特殊方法的嘹黔,這里列舉一個(gè)文檔上的示例:
class Mood(enum.Enum):
FUNKY = 1
HAPPY = 3
def describe(self):
return self.name, self.value
def __str__(self):
return 'my custom str! {0}'.format(self.value)
@classmethod
def favorite_mood(cls):
return cls.HAPPY
print(Mood.favorite_mood())
# my custom str! 3
print(Mood.HAPPY.describe())
# ('HAPPY', 3)
print(str(Mood.FUNKY))
# my custom str! 1
但是枚舉還是有一些限制账嚎,以單下劃線開頭和結(jié)尾的名稱是由枚舉保留而不可使用,例外項(xiàng)是包括特殊方法成員 (__str__()
参淹、__add__()
等)醉锄,描述符 (方法也屬于描述符) 以及在 _ignore_
中列出的變量名。
在介紹 _generate_next_value_()
函數(shù)時(shí)浙值,為了重載這個(gè)函數(shù)恳不,我們創(chuàng)建了一個(gè)基于 enum.Enum
的枚舉,可以成為 枚舉A
开呐,重載了 _generate_next_value_()
函數(shù)烟勋,然后又創(chuàng)建了一個(gè)基于 枚舉A
的枚舉。這樣的定義是被允許的筐付,在 enum
模塊的規(guī)定下卵惦,一個(gè)新的 Enum
類必須基于一個(gè) Enum
類,至多一個(gè)實(shí)體數(shù)據(jù)類型以及出于實(shí)際需要的任意多個(gè)基于 object
的 mixin
類瓦戚。 這些基類的順序?yàn)?
class EnumName([mix-in, ...,] [data-type,] base-enum):
pass
另外沮尿,沒有定義枚舉成員的枚舉才能被其他枚舉繼承,否則將會(huì)報(bào)錯(cuò):
class A(enum.Enum):
AA = 1
class B(A):
BB = 2
# TypeError: B: cannot extend enumeration 'A'
參考資料
官方文檔: https://docs.python.org/zh-cn/3/library/enum.html
源代碼: Lib/enum.py
閱讀更多文章請(qǐng)關(guān)注微信公眾號(hào)《python庫(kù)詳解》