enum -- 枚舉(一)

前情提示: 測(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è)變種類及自定義類,第三部分將更深入的了解枚舉沼填。

傳送門

enum -- 枚舉(一)

enum -- 枚舉(二)

創(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 = 1B = '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è)基于 objectmixin 類瓦戚。 這些基類的順序?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ù)詳解》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市畜疾,隨后出現(xiàn)的幾起案子赴邻,更是在濱河造成了極大的恐慌,老刑警劉巖啡捶,帶你破解...
    沈念sama閱讀 221,635評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姥敛,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瞎暑,警方通過查閱死者的電腦和手機(jī)彤敛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來了赌,“玉大人墨榄,你說我怎么就攤上這事∽岵穑” “怎么了渠概?”我有些...
    開封第一講書人閱讀 168,083評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)嫂拴。 經(jīng)常有香客問我,道長(zhǎng)贮喧,這世上最難降的妖魔是什么筒狠? 我笑而不...
    開封第一講書人閱讀 59,640評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮箱沦,結(jié)果婚禮上辩恼,老公的妹妹穿的比我還像新娘。我一直安慰自己谓形,他們只是感情好灶伊,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,640評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著寒跳,像睡著了一般聘萨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上童太,一...
    開封第一講書人閱讀 52,262評(píng)論 1 308
  • 那天米辐,我揣著相機(jī)與錄音,去河邊找鬼书释。 笑死翘贮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的爆惧。 我是一名探鬼主播狸页,決...
    沈念sama閱讀 40,833評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼扯再!你這毒婦竟也來了芍耘?” 一聲冷哼從身側(cè)響起址遇,我...
    開封第一講書人閱讀 39,736評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎齿穗,沒想到半個(gè)月后傲隶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,280評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡窃页,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,369評(píng)論 3 340
  • 正文 我和宋清朗相戀三年跺株,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脖卖。...
    茶點(diǎn)故事閱讀 40,503評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乒省,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畦木,到底是詐尸還是另有隱情袖扛,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布十籍,位于F島的核電站蛆封,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏勾栗。R本人自食惡果不足惜惨篱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,870評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望围俘。 院中可真熱鬧砸讳,春花似錦、人聲如沸界牡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宿亡。三九已至常遂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間她混,已是汗流浹背烈钞。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坤按,地道東北人毯欣。 一個(gè)月前我還...
    沈念sama閱讀 48,909評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像臭脓,于是被迫代替她去往敵國(guó)和親酗钞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,512評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容