URLhttps://learnscript.net/zh/python/enumerations/
    复制链接转到说明  示例

    Python 枚举介绍,以及定义和使用枚举

    我被代码海扁署名-非商业-禁演绎
    阅读 21:21·字数 6405·更新 

    前提

    阅读本节的前提是对 Python 类的继承以及枚举的概念有所掌握,你可以查看Python 类继承介绍,以及实现类继承,多重继承,方法重写编程教程枚举介绍,以及枚举成员的组合,判断,移除来了解相关信息。

    定义 Python 枚举

    Python 并没有提供类似于enum这样的关键字来定义枚举,你需要从enum模块的Enum类或其派生类,派生新的类作为枚举,其基本语法格式如下。

    class <enumname>(Enum):
        <membername> = <membervalue>
        …

    enumname 部分

    enumname为枚举的名称,需要符合 Python 标识符规范。

    membername 部分

    membername为枚举成员的名称,需要符合 Python 标识符规范,建议所有字母均大写。

    membervalue 部分

    membervalue是一个表达式,用于计算枚举成员的值。

    enumerations.py
    from enum import *
    
    class SPORT(Enum): '''一个关于运动的枚举,包含了篮球,足球和棒球''' FOOTBALL = 1 BASKETBALL = 2 BASEBALL = 3

    除了通过直接定义类,Python 还支持通过Enum类或其派生类(FlagStrEnumIntEnum等)的构造器来创建新的枚举,其基本格式如下。

    Enum(value, names=None, *, module=None, qualname=None, type=None, start=1, boundary=None)

    value 参数

    value参数为枚举的名称。

    names 参数

    names参数包含了所有枚举成员的信息,该参数可以是一个仅包含成员名称的字符串,名称之间使用空格( )或逗号(,)分隔,比如'ONE,TWO',也可以是一个包含成员名称或名称和值的迭代器对象,比如['ONE','TWO'](('ONE',100),('TWO',200)),还可以是一个包含成员名称和值的映射对象,比如{'ONE':100,'TWO':200}

    module 参数

    module参数将成为枚举的__module__特性的值,表示枚举所在的模块的名称。

    qualname 参数

    qualname参数将成为枚举的__qualname__特性的值,表示枚举在模块中的完全限定名,比如,World.PLANT说明枚举PLANT定义在模块的World中,World可能是一个类。

    type 参数

    type参数为枚举的混合类型。

    start 参数

    start参数为枚举成员的起始值,由auto类使用。

    boundary 参数

    boundary参数表示对超出范围的值的处理方式。

    下面,我们使用StrEnum的构造器定义了新的枚举PLANT,由于是StrEnum,因此枚举成员的值需要是字符串。

    enumerations.py
    # 定义枚举 PLANT
    PLANT = StrEnum('PLANT', (['TREE', 'one tree'], ['FLOWER', 'one flower']))
    print(PLANT.__members__)
    {'TREE': <PLANT.TREE: 'one tree'>, 'FLOWER': <PLANT.FLOWER: 'one flower'>}

    Python 枚举的成员

    枚举成员(Member)是以类变量形式出现的特性(Attribute),与类变量一样,枚举成员拥有成员名称和值。比如,示例中枚举SPORT的成员FOOTBALL的名称为FOOTBALL,值为1

    虽然枚举成员在语法形式上与类变量类似,但他们是一种语法糖,枚举成员将是只读,不可写入的,每一个枚举成员都是枚举的一个实例,成员的类型是枚举自身,这一点可通过isinstancetype来验证。

    enumerations.py
    print(type(SPORT.FOOTBALL))
    # 判断成员 FOOTBALL 是否为 SPORT 的实例
    print(isinstance(SPORT.FOOTBALL, SPORT))
    <enum 'SPORT'>
    True

    枚举成员的值可以是任意类型

    这不同于我们对枚举的一般印象,因为很多语言中的枚举都和数字紧密相关,但 Python 并未对枚举成员值的类型施加限制,如果枚举没有指定混合类型,也没有从某些特定的类继承的话,比如IntEnumStrEnumIntFlag

    官方并不推荐使用IntEnumIntFlag等类,对枚举成员的值的类型进行约束,因为这将导致定义的枚举不符合一般约定。比如,从IntEnum派生的枚举可以与整数进行有效比较,这不应该发生。

    使用 @global_enum 修饰符将枚举成员定义至模块所在的命名空间

    如果为枚举添加enum模块中的@global_enum修饰符,那么该枚举的成员将出现在模块所在的命名空间,你不再需要通过枚举来访问他们。

    enumerations.py
    # 枚举成员 A 和 B 将出现在模块中
    @global_enum
    class LETTER(Enum):
    	A = 'a'
    	B = 'b'
    
    # 下面的访问方式都是正确的 print(LETTER.A) print(A)
    A
    A

    使用 @verify 修饰符确保枚举成员值的连贯性

    对于整数类型的枚举成员,如果你希望他们的值具有连贯性,比如从19,那么可以使用enum模块的CONTINUOUS@verify修饰符对枚举进行限制,如果枚举成员中的最大值与最小值之间存在未被使用的值,则将引发异常ValueError

    由于下面的枚举NUM未定义值为2的枚举成员,因此将引发异常。

    enumerations.py
    # 枚举 NUM 的成员的值需要是连贯的
    @verify(CONTINUOUS)
    class NUM(Enum):
    	ONE = 1
    	FOUR = 4
    	THREE = 3
    ValueError: invalid enum 'NUM': missing values 2

    枚举成员的名称不能与其他枚举成员或特性的名称相同

    在一般的类中,定义名称相同的特性,比如名称相同的类变量和实例方法,并不会产生语法上的错误,但这样的行为对于枚举成员是行不通的,枚举成员的名称不应该与其他枚举成员或特性的名称相同,这会导致异常TypeError

    将类变量从 Python 枚举成员中排除

    除了私有类变量,名称以单下划线(_)开头和结束,以及名称存在于_ignore_中的类变量,枚举中的其他类变量都将被解释为枚举成员。

    如果希望某个类变量从枚举成员中排除,那么可以使用enum模块的nonmember类为类变量赋值。当然,你也可以使用具有相反效果的member类为类变量赋值,他将使类变量成为枚举成员,只不过,这样的操作基本上是多余的。

    另外,可以将不希望解释为枚举成员的类变量名称,包含在类变量_ignore_中,该变量可以是字符串,列表或元组对象。如果是字符串,那么多个变量名称需要使用空格( )或逗号(,)分隔。需要指出的是,_ignore_应该定义在其对应的类变量之前,否则将导致异常ValueError,使用member类为_ignore_对应的类变量赋值,不会达到预期效果。

    enumerations.py
    class POSITION(Enum):
    	_ignore_ = ('Z', 'W')
    	# X 不是枚举成员
    	X = nonmember(1)
    	# Y 是枚举成员
    	Y = 2
    	# Z 和 W 不是枚举成员,虽然 W 使用了 member
    	Z = 3
    	W = member(4)
    
    print(POSITION.__members__)
    {'Y': <POSITION.Y: 2>}

    Python 枚举成员的别名

    在一个枚举中,当多个枚举成员拥有相同的名称时,之后定义的成员的名称,将成为首先定义的成员的别名。当你尝试获取之后定义的枚举成员时,返回的将是首先定义的枚举成员,这些枚举成员均指向枚举的同一个实例。

    下面,我们为枚举SPORT添加一个值为1的枚举成员SOCCER,他的名称SOCCER将成为枚举成员FOOTBALL的别名。

    enumerations.py
    class SPORT(Enum):
    	# …
    	SOCCER = 1
    
    print(SPORT.SOCCER) print(SPORT.SOCCER.name)
    SPORT.FOOTBALL
    FOOTBALL

    作为别名的枚举成员不会出现在对枚举的迭代遍历中

    对一个枚举进行迭代遍历,你将得到该枚举所有未作为别名的枚举成员,这是较为合理的,因为包含别名可能会导致在迭代遍历中执行多余的操作。

    enumerations.py
    # 这里不会出现 SOCCER,因为他是 FOOTBALL 的别名
    for i in SPORT:
    	print(f'{i.name}={i.value}')
    FOOTBALL=1
    BASKETBALL=2
    BASEBALL=3

    使用 @unique 或 @verify 修饰符确保枚举成员值的不重复

    如果不希望别名的情况发生,可以为枚举添加enum模块的@unique@verify修饰符,他们将确保枚举中的每一个枚举成员都拥有不同的值。其中,@verify修饰符需要书写为@verify(UNIQUE)UNIQUE位于enum模块。

    enumerations.py
    # 可以将 @verify(UNIQUE) 替换为 @unique
    @verify(UNIQUE)
    class ROLE(Enum):
    	PLAYER = 1
    	# ERROR 这里不能出现别名
    	HERO = 1
    ValueError: aliases found in <enum 'ROLE'>: HERO -> PLAYER

    获取 Python 枚举成员

    有多种方式可以获取枚举中的某个指定成员,最为常见的是通过形式为m.name的表达式,其中,m为枚举类,name为枚举成员(名称,标识符)。比如,SPORT.BASEBALL获取了枚举SPORT的成员BASEBALL

    除了.,你还可以通过[]获取某个枚举成员,只需给出成员的名称即可,如果无法确定想要获取的枚举成员,那么使用[]是一个不错的选择。假设,变量name含有某个成员的名称,SPORT[name]将返回该名称对应的枚举成员。

    如果只知道某个枚举成员的值,那么通过该枚举的构造器可获取对应的枚举成员。比如,SPORT(2)将返回枚举SPORT的成员BASKETBALL

    Python 枚举支持迭代遍历操作,你将得到所有未作为别名的枚举成员。如果要获取包括别名在内的全部成员,则可以使用枚举的__members__特性,该特性是一个映射类型对象,包含了全部枚举成员的信息,其键值对的键为枚举成员的名称,值为枚举成员的值。

    enumerations.py
    # 显示 SPORT 的全部成员,包括 SOCCER
    print(SPORT.__members__)
    {'FOOTBALL': <SPORT.FOOTBALL: 1>, 'BASKETBALL': <SPORT.BASKETBALL: 2>, 'BASEBALL': <SPORT.BASEBALL: 3>, 'SOCCER': <SPORT.FOOTBALL: 1>}

    获取 Python 枚举成员的名称和值

    枚举成员拥有两个只读属性(Property)namevalue,他们分别表示了枚举成员的名称和值,比如,SPORT.BASEBALL.value将返回3

    获取 Python 枚举成员的个数

    通过len函数,你可以得到枚举所定义的枚举成员的个数。

    enumerations.py
    # 获取枚举 NUM 中已经定义的成员的个数
    print(f'NUM 成员的个数为:{len(NUM)}')
    NUM 成员的个数为:4

    比较 Python 枚举成员

    在某些编程语言中,枚举可以有效的与整数类型进行比较操作,这带来了方便,但也会使代码的可读性降低。

    Python 中的枚举成员与任何非枚举成员总是不相等的,比如,表达式SPORT.FOOTBALL==1的运算结果为False,虽然枚举SPORT的成员FOOTBALL的值(SPORT.FOOTBALL.value)是1。枚举成员,以及枚举成员与非枚举成员之间,不能进行大于(>),小于(<),大于等于(>=),小于等于(<=)的比较,比如,表达式SPORT.FOOTBALL>=0将导致异常TypeError

    正如之前讲到的,枚举成员是枚举的实例,因此,枚举成员之间的比较,也就是实例之间的比较。当他们是同一个实例时,is运算会返回True,否则会返回False,比如,表达式SPORT.FOOTBALL is SPORT.BASKETBALL的运算结果为False

    重新定义枚举会导致前后的同一个枚举成员不相等

    如果你重新定义了枚举,那么之前保留的枚举成员不会与新的枚举成员相等,因为重新定义导致这些枚举成员成为了新的枚举实例。

    enumerations.py
    football = SPORT.FOOTBALL
    
    # 重新定义枚举 SPORT class SPORT(Enum): FOOTBALL = 1 BASKETBALL = 2 BASEBALL = 3 SOCCER = 1
    # 比较之前和新的枚举成员 FOOTBALL,返回 False print(football == SPORT.FOOTBALL)
    False

    继承自 IntEnum,IntFlag 的枚举的比较问题

    由于enum模块的IntEnumIntFlag类的基类包括int,因此从IntEnumIntFlag类派生的枚举,并不遵守以上关于比较的规则,他们可以与某些数字进行有效的比较。假设将枚举SPORT改为从IntEnum类派生,那么表达式SPORT.FOOTBALL==1将返回True,而不是False,表达式SPORT.FOOTBALL>=0将返回True,而不是引发异常。

    Python 枚举成员的值

    在默认情况下,枚举成员的值就是书写在=后面的表达式所返回的内容。如果你希望对这些内容施加新规则,从而产生新的值,那么可以通过定义__new__方法来完成该任务,=后的表达式的运算结果会作为该方法的参数。当然,__new__方法需要返回一个枚举实例,而新的值应被存储在实例的_value_变量中。

    枚举成员的 _value_ 变量

    枚举成员的_value_变量,用于实际存储枚举成员的值,该值也可被枚举成员的只读属性value返回。

    在下面的代码中,我们使用元组为枚举SPEED的成员LOWHIGH赋值,__new__方法会计算元组中的数字的和,并将其作为枚举成员的值。

    enumerations.py
    class SPEED(int, Enum):
    	def __new__(cls, *args):
    		# 计算所有数字的和,并保存至 total
    		total = 0
    		for i in args:
    			total += i
    
    # 创建枚举的实例,并将之前的计算结果赋值给 _value_ prop = int.__new__(cls) prop._value_ = total return prop
    LOW = (1, 2, 3, 4, 5) HIGH = (6, 7, 8, 9)
    print(SPEED.LOW.value)
    15

    自动设置 Python 枚举成员的值

    Python 的enum模块提供了名为auto的类,可以实现枚举成员值的自动设置,只需要简单的在=之后书写auto()即可。

    在默认情况下,auto()会返回一个可用的递增的整数,从1开始计算。如果枚举从StrEnum类派生,那么auto()会返回成员名称的小写字符串。如果枚举从Flag类派生,那么auto()会返回二的整数次幂2ⁿn0开始计算。

    如何设置 auto() 为枚举成员返回的成员值?

    当你使用修饰符@staticmethod在枚举中定义静态方法_generate_next_value_时,该静态方法的返回值可作为auto()的返回值。

    需要指出,_generate_next_value_方法需要定义在所有使用auto()赋值的枚举成员之前,否则将引发异常TypeError

    _generate_next_value_(name, start, count, last_values)

    name 参数

    name参数为当前正在定义的枚举成员的名称。

    start 参数

    start参数为枚举成员的预期起始值,默认为1

    count 参数

    count参数为已经定义的枚举成员的个数,不包括当前正在定义的成员。

    last_values 参数

    last_values是一个 Python 列表,包含了之前所有已定义枚举成员的值。

    在下面的枚举DIRECTION中,成员UPRIGHT的值分别为12,成员LEFT的值为101,因为第三个auto()会根据DOWN对应的100来决定返回值。

    enumerations.py
    class DIRECTION(Enum):
    	UP = auto()
    	RIGHT = auto()
    	DOWN = 100
    	# 将根据 100 返回 101
    	LEFT = auto()
    
    print(f'{DIRECTION.UP.value} {DIRECTION.RIGHT.value} {DIRECTION.DOWN.value} {DIRECTION.LEFT.value}')
    1 2 100 101

    为枚举DIRECTION加入静态方法_generate_next_value_,以控制auto()的返回值,它是一个字符串,包含了枚举成员的名称和一个数字。

    enumerations.py
    class DIRECTION(Enum):
    	# 需要定义在所有枚举成员之前
    	@staticmethod
    	def _generate_next_value_(name, start, count, last_values):
    		return f'{name}_{count}'
    	# …
    UP_0 RIGHT_1 100 LEFT_3

    Python 枚举的方法

    枚举可使用修饰符@classmethod@staticmethod来定义类方法或静态方法,通过枚举或枚举成员可以调用他们。对于枚举中定义的实例方法,其第一参数self表示枚举成员。

    enumerations.py
    class DAY(str, ReprEnum):
    	MONDAY = 1
    	TUESDAY = 2
    
    # self 代表了枚举成员 def show(self): print(f'{self.name}={self.value}')
    DAY.TUESDAY.show()
    TUESDAY=2

    超出取值范围的 Python 枚举成员

    你可以在枚举中定义名称为_missing_的类方法,用于处理枚举成员的取值超出范围的情况,比如,为枚举的构造器传递了一个值,该值没有对应任何一个枚举成员。

    如果_missing_方法最终返回了枚举的某个成员,那么该成员将作为值对应的枚举成员,如果_missing_方法返回了None,那么将引发异常,已说明取值是无效的。

    _missing_(cls, value)

    cls 参数

    cls参数为枚举。

    value 参数

    value为超出范围的取值。

    在枚举ANIMAL中,我们定义了类方法_missing_,已处理超出范围的取值,如果取值为小于100的整数,则将DOG作为其对应的枚举成员,如果取值为大于100小于200的整数,则将CAT作为其对应的枚举成员,其他情况返回None

    enumerations.py
    class ANIMAL(Enum):
    	DOG = 100
    	CAT = 200
    
    # 处理超出取值范围的枚举成员 @classmethod def _missing_(cls, value): if type(value) is int: if value < 100: # 小于 100 的整数,将被认为是 DOG return ANIMAL.DOG elif value < 200: # 大于 100 小于 200 的整数,将被认为是 CAT return ANIMAL.CAT
    # 其余取值返回 None,这将导致异常的发生 return None
    print(ANIMAL(99)) print(ANIMAL(111))
    ANIMAL.DOG
    ANIMAL.CAT

    继承 Python 枚举

    Python 允许从一个枚举派生另一个枚举,前提是作为基类的枚举尚未拥有任何枚举成员。enum模块自身包含了从Enum继承的FlagIntFlagIntEnumStrEnum等类,他们均可作为其他枚举的基类。

    IntEnum 类

    IntEnum类继承的枚举的成员的值必须是整数类型,=后的表达式的返回值将用于创建整数。IntEnum类的基类包括Enumint,因此他同时具有两者的特点,可以与其他数字进行算术运算,有效比较,或出现在任何允许使用整数的位置。当参与算术运算时,运算结果将不再是某个枚举成员。

    StrEnum 类

    StrEnum类继承的枚举的成员的值必须是字符串类型。StrEnum类的基类包括Enumstr,因此他同时具有两者的特点,可以出现在允许使用字符串的位置。

    ReprEnum 类

    ReprEnum类继承的枚举必须指定一种类型(比如intstrfloat)作为枚举的基类,以确定枚举成员值的类型,当你为成员指定其他类型的值时,将发生隐式转换。

    Flag,IntFlag 类

    FlagIntFlag类继承的枚举也被称为 Python 标志,他将支持枚举成员之间的组合操作。

    标志

    想要详细了解 Python 标志,你可以查看如何定义和使用支持成员组合的枚举?Python 标志介绍一节。

    枚举JOB的成员DOCTOR,使用元组'100',8来创建整数。枚举HERO的成员TOM,会将整数123转换为字符串'123'。枚举TIME的成员MINUTE将导致异常TypeError,因为StrEnum不支持非字符串类型的转换。

    enumerations.py
    class JOB(IntEnum):
    	WORKER = 1
    	DOCTOR = '100', 8
    
    # 参与算术运算后,运算结果不再是某个枚举成员 print(f'{type(JOB.WORKER)} {type(JOB.DOCTOR)}') print(type(JOB.WORKER + 3)) print(type(JOB.WORKER + JOB.DOCTOR))
    class HERO(str, ReprEnum): # 123 会被转换为 '123' TOM = 123 JERRY = 'mouse?'
    class TIME(StrEnum): SECOND = 'sec' # ERROR 1 并不会转换为 '1' MINUTE = 1
    <enum 'JOB'> <enum 'JOB'>
    <class 'int'>
    <class 'int'>

    TypeError: 1 is not a string

    内容分类

    源码

    enumerations.py·codebeatme/python·GitHub