enum -- 枚举(一)

前情提示: 测试代码中,右尖括号(>)表示命令行中输入的命令; 单独一行并以井字符(#)开头的为输出内容; 库的导入仅在本文的第一个测试代码中展现,其他代码块均省略库的导入代码。

  • 系统类型: Windows 10
  • python 版本: Python 3.9.0

枚举是一组符号名称(枚举成员)的集合,枚举成员应该是唯一的、不可变的。

enum 模块将分为三个部分解析,第一部分主要介绍枚举的特性和 Enum 类,第二部分将介绍 Enum 类的三个变种类及自定义类,第三部分将更深入的了解枚举。

传送门

enum -- 枚举(一)

enum -- 枚举(二)

创建一个 Enum

枚举是使用 class 语法来创建的,这使得它们易于读写。但还是有一种以使用函数形式的创建方式,这个留到下文再讲,现在主要来说明一下使用 class 语法的创建方式。

import enum

class Test(enum.Enum):
    A = 1
    B = 'b'

上面的代码创建了一个枚举,class Test 是一个枚举,其中 Test 就是枚举对象的名称。

A = 1B = 'b' 这些等号两边的整体被称为 枚举成员,等号左边的被称为枚举成员的名称,枚举通常是用来表示常量的,所以枚举成员的名称一般使用大写形式。等号右边的被称为枚举成员的值,枚举成员的值可以是任意类型,如 intstr等等,只需要注意枚举成员的值在 枚举对象中是唯一的。

虽然创建枚举时使用了 class 语法,但是枚举与普通的 python 类不同,可以打印一些信息来进行对比:

'''枚举'''
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

首先,枚举创建出的对象自成一种类型,类型名就是枚举的名称(类名)。枚举的枚举成员(类的属性)被创建成了一个对象,并且类型就是所属枚举的类型。

枚举可以按照定义顺序进行迭代,并且枚举成员是不可变的,是可哈希的,因此字典中的键可以使用枚举成员。

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

枚举中不允许有相同的枚举成员,但是允许枚举成员有同样的值,若查找的值有多个枚举成员,那么会按定义顺序返回第一个。

'''枚举成员有相同的名称'''
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 模块中定义了一个装饰器 -- @enum.unique,可以强制规定枚举成员的值也必须是唯一的。

@enum.unique
class Test(enum.Enum):
    A = 1
    B = 1
# ValueError: duplicate values found in <enum 'Test'>: B -> A

若枚举成员的值不重要或者其他不需要一一指定枚举成员的场景下,可以使用 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>]

当使用 enum.auto() 来定义枚举成员的值时,再次使用 int 类型的值时,最后得到的结果可能会和预想的不太一样。

enum.auto() 函数的返回值是由 _generate_next_value_() 函数决定的,默认情况下,此函数是根据最后一个 int 类型的枚举成员的值增加 1。此函数是可以重载的,重载时,方法定义必须在任何成员之前。

'''先定义一个值为 int 类型的枚举成员, 再使用 auto() 函数'''
class Test(enum.Enum):
    A = 2
    B = enum.auto()
    C = enum.auto()
print(list(Test))
# [<Test.A: 2>, <Test.B: 3>, <Test.C: 4>]

'''定义一个值为非 int 类型的枚举成员, 再使用 auto() 函数'''
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() 函数时,测试示例中,枚举对象转换为 list 类型时少了一个枚举成员,这是因为少的那个枚举成员与之前的枚举成员的值是一样的,这个枚举成员被认为是一个别名,当对枚举成员迭代时,不会给出别名。

class Test(enum.Enum):
    A = 1
    B = 1
print(list(Test))
# [<Test.A: 1>]

而特殊属性 __members__是一个从名称到成员的只读有序映射。它包含枚举中所有的枚举成员,但是在迭代时,因为别名的原因,指向的枚举成员还是该值第一个定义的枚举成员。

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 的类,是可以拥有普通方法和特殊方法的,这里列举一个文档上的示例:

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

但是枚举还是有一些限制,以单下划线开头和结尾的名称是由枚举保留而不可使用,例外项是包括特殊方法成员 (__str__()__add__() 等),描述符 (方法也属于描述符) 以及在 _ignore_ 中列出的变量名。

在介绍 _generate_next_value_() 函数时,为了重载这个函数,我们创建了一个基于 enum.Enum 的枚举,可以成为 枚举A,重载了 _generate_next_value_() 函数,然后又创建了一个基于 枚举A 的枚举。这样的定义是被允许的,在 enum 模块的规定下,一个新的 Enum 类必须基于一个 Enum 类,至多一个实体数据类型以及出于实际需要的任意多个基于 objectmixin 类。 这些基类的顺序为:

class EnumName([mix-in, ...,] [data-type,] base-enum):
    pass

另外,没有定义枚举成员的枚举才能被其他枚举继承,否则将会报错:

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

阅读更多文章请关注微信公众号《python库详解》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容