python:枚举类

python:枚举类

1 前言

python实际开发中,需要定义类型时,一般是使用一组整数或者字符串来表示。如果使用常规的定义方式,非常容易被外部修改,Python3.4后,增加了枚举类的使用,就是为了解决这种场景而设计的。

python枚举适用场景:值有限且固定(不希望被轻易或随意修改)的数据类型。Java也有枚举类的使用,使用关键字enum实现,而python一般是通过提供的Enum类继承实现的(python类支持多继承,java为类单继承)。

2 使用

2.0 初识枚举类

通过enum.Enum()来实现自定义的枚举类:

(‘SPRING’, ‘SUMMER’, ‘AUTUMN’, ‘WINTER’)是定义的多个枚举值:

import enum
# 定义Season枚举类
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'AUTUMN', 'WINTER'))
# 直接访问指定枚举
print(Season.SPRING)
# 访问枚举成员的变量名
print(Season.SPRING.name)
# 访问枚举成员的值
print(Season.SPRING.value)

# 根据枚举变量名访问枚举对象
print(Season['WINTER']) # Season.WINTER
# 根据枚举值访问枚举对象
print(Season(2)) # Season.SUMMER

# 遍历Season枚举的所有成员
for name, member in Season.__members__.items():
    print(name, '=>', member, ',', member.value)

执行结果:

在这里插入图片描述

2.1 枚举类简单示例:

还可以通过继承Enum枚举类,来实现一个自定义的枚举类:

from enum import Enum


class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2


class Type(Enum):
    TYPE_ONE = "fruit"
    TYPE_TWO = "animal"
    TYPE_THREE = "drinks"


print(OrderStatus.REFUND)
# OrderStatus.REFUND

print(Type.TYPE_ONE)
# Type.TYPE_ONE

相比于字典,普通类中的变量,枚举类优势如下:

(1)字典,普通类中的变量支持修改,而枚举类,在类外,是不支持修改的。

OrderStatus.REFUND = 3
# error: AttributeError: Cannot reassign members.

在这里插入图片描述

上述报错是因为,我们继承了Enum类,而Enum类的魔法方法def __setattr__(cls, name, value),限制了只要从_member_map_中获取的值,即会抛出异常:

def __setattr__(cls, name, value):
    """
    Block attempts to reassign Enum members.

    A simple assignment to the class namespace only changes one of the
    several possible ways to get an Enum member from the Enum class,
    resulting in an inconsistent Enumeration.
    """
    member_map = cls.__dict__.get('_member_map_', {})
    if name in member_map:
        raise AttributeError('Cannot reassign members.')
    super().__setattr__(name, value)

(2)python枚举类中不可以存在key相同的枚举项

下面枚举类有相同key:NO_PAY,会直接抛出异常:

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    NO_PAY = 4
    #  TypeError: Attempted to reuse key: 'NO_PAY'    

由枚举类的魔法方法def __setitem__(self, key, value)控制:

def __setitem__(self, key, value):
    """
    Changes anything not dundered or not a descriptor.

    If an enum member name is used twice, an error is raised; duplicate
    values are not checked for.

    Single underscore (sunder) names are reserved.
    """
    if _is_private(self._cls_name, key):
        import warnings
        warnings.warn(
                "private variables, such as %r, will be normal attributes in 3.10"
                    % (key, ),
                DeprecationWarning,
                stacklevel=2,
                )
    if _is_sunder(key):
        if key not in (
                '_order_', '_create_pseudo_member_',
                '_generate_next_value_', '_missing_', '_ignore_',
                ):
            raise ValueError('_names_ are reserved for future Enum use')
        if key == '_generate_next_value_':
            # check if members already defined as auto()
            if self._auto_called:
                raise TypeError("_generate_next_value_ must be defined before members")
            setattr(self, '_generate_next_value', value)
        elif key == '_ignore_':
            if isinstance(value, str):
                value = value.replace(',',' ').split()
            else:
                value = list(value)
            self._ignore = value
            already = set(value) & set(self._member_names)
            if already:
                raise ValueError(
                        '_ignore_ cannot specify already set names: %r'
                        % (already, )
                        )
    elif _is_dunder(key):
        if key == '__order__':
            key = '_order_'
    elif key in self._member_names:
        # descriptor overwriting an enum?
        raise TypeError('Attempted to reuse key: %r' % key)
    elif key in self._ignore:
        pass
    elif not _is_descriptor(value):
        if key in self:
            # enum overwriting a descriptor?
            raise TypeError('%r already defined as: %r' % (key, self[key]))
        if isinstance(value, auto):
            if value.value == _auto_null:
                value.value = self._generate_next_value(
                        key,
                        1,
                        len(self._member_names),
                        self._last_values[:],
                        )
                self._auto_called = True
            value = value.value
        self._member_names.append(key)
        self._last_values.append(value)
    super().__setitem__(key, value)

(3)python枚举类中的值可以相同,但是相同值的各项不同key,会被当做别名

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0


# 输出结果都是:OrderStatus.NO_PAY
print(OrderStatus.NO_PAY)
# OrderStatus.NO_PAY

print(OrderStatus.WAIT_PAY)
# OrderStatus.NO_PAY

如果不希望枚举类中有相同value值,可以使用unique装饰器,有枚举值含有相同value值,那么会直接报错:

from enum import Enum, unique

@unique
class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0

# 上述加了@unique的枚举类,定义完执行直接报错如下:
# ValueError: duplicate values found in <enum 'OrderStatus'>: WAIT_PAY -> NO_PAY

我们知道,装饰器是语法糖,上述装饰器的写法,实际就是如下的写法:

from enum import Enum, unique

# @unique
class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0


OrderStatus = unique(OrderStatus)
# 上述加了@unique的枚举类,定义完执行直接报错如下:
# ValueError: duplicate values found in <enum 'OrderStatus'>: WAIT_PAY -> NO_PAY

enum.py:

def unique(enumeration):
    """
    Class decorator for enumerations ensuring unique member values.
    """
    duplicates = []
    for name, member in enumeration.__members__.items():
        if name != member.name:
            duplicates.append((name, member.name))
    if duplicates:
        alias_details = ', '.join(
                ["%s -> %s" % (alias, name) for (alias, name) in duplicates])
        raise ValueError('duplicate values found in %r: %s' %
                (enumeration, alias_details))
    return enumeration

枚举类的构造方法:

import enum


class Sex(enum.Enum):
    MALE = '男', 'man'
    FEMALE = '女', 'woman'

    def __init__(self, cn_name, desc):
        self._cn_name = cn_name
        self._desc = desc

    @property
    def desc(self):
        return self._desc

    @property
    def cn_name(self):
        return self._cn_name


# 访问FEMALE的name
print('FEMALE的name:', Sex.FEMALE.name)
# 访问FEMALE的value
print('FEMALE的value:', Sex.FEMALE.value)
# 访问自定义的cn_name属性
print('FEMALE的cn_name:', Sex.FEMALE.cn_name)
# 访问自定义的desc属性
print('FEMALE的desc:', Sex.FEMALE.desc)

执行结果如下:

在这里插入图片描述

2.2 从枚举类中取值

(1)第一种获取枚举值方式,通过枚举类.__dict__.get(“_member_map_”).items()获取枚举值

上面为枚举类设置值时有报错信息,这提示了我们可以通过:枚举类.__dict__.get(“_member_map_”)的方式来获取枚举值,如下:

print(OrderStatus.__dict__)
# {'_generate_next_value_': <function Enum._generate_next_value_ at 0x000002926E3E3AF0>, '__module__': '__main__', 
# '__doc__': 'An enumeration.', 
# '_member_names_': ['NO_PAY', 'PAID', 'REFUND'], 
# '_member_map_': {'NO_PAY': <OrderStatus.NO_PAY: 0>, 'PAID': <OrderStatus.PAID: 1>, 'REFUND': <OrderStatus.REFUND: 2>}, 
# '_member_type_': <class 'object'>, 
# '_value2member_map_': {0: <OrderStatus.NO_PAY: 0>, 1: <OrderStatus.PAID: 1>, 2: <OrderStatus.REFUND: 2>}, 
# 'NO_PAY': <OrderStatus.NO_PAY: 0>, 'PAID': <OrderStatus.PAID: 1>, 'REFUND': <OrderStatus.REFUND: 2>, 
# '__new__': <function Enum.__new__ at 0x000002926E3E3A60>}

print("_member_map_" in OrderStatus.__dict__)
# True

print(OrderStatus.__dict__.get("_member_map_"))
# {'NO_PAY': <OrderStatus.NO_PAY: 0>, 'PAID': <OrderStatus.PAID: 1>, 'REFUND': <OrderStatus.REFUND: 2>}

print(OrderStatus.__dict__._member_map_)
# 报错:AttributeError: 'mappingproxy' object has no attribute '_member_map_'

根据上述枚举类.__dict__.get(“_member_map_”)返回的结果可知,和第一种获取枚举的方式类似,返回结果为字典,字典的key是枚举的名称,字典的value是对应的枚举值对象。该枚举对象有name和value属性。java的枚举如果反编译后查看,其本质是public static final定义的枚举类的实例对象(java虽然枚举使用enum关键字,但是本质是Class类,所以可以称为枚举类),所以若枚举有属性时,则需要为枚举类添加对应的构造方法添加枚举属性等等,也就对应于python枚举对象的value属性。

修改上述的枚举类:

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0

获取枚举值并打印结果:

print(OrderStatus.__dict__.get("_member_map_"))

print("#" * 20)

for name, member in OrderStatus.__dict__.get("_member_map_").items():
    print(name)
    print(type(name))
    print(member)
    print(type(member)) # 这是枚举值
    print("枚举name:%s" % member.name)
    print(f"枚举value:{member.value}")
    print("*" * 20)

结果:

{'NO_PAY': <OrderStatus.NO_PAY: 0>, 'PAID': <OrderStatus.PAID: 1>, 'REFUND': <OrderStatus.REFUND: 2>, 'WAIT_PAY': <OrderStatus.NO_PAY: 0>}
####################
NO_PAY
<class 'str'>
OrderStatus.NO_PAY
<enum 'OrderStatus'>
枚举name:NO_PAY
枚举value:0
********************
PAID
<class 'str'>
OrderStatus.PAID
<enum 'OrderStatus'>
枚举name:PAID
枚举value:1
********************
REFUND
<class 'str'>
OrderStatus.REFUND
<enum 'OrderStatus'>
枚举name:REFUND
枚举value:2
********************
WAIT_PAY
<class 'str'>
OrderStatus.NO_PAY
<enum 'OrderStatus'>
枚举name:NO_PAY
枚举value:0
********************

基于此,我们可以为枚举类实现一个静态方法,用于根据name,即枚举名称,来获取该枚举对象:

class Type(Enum):
    TYPE_ONE = "fruit"
    TYPE_TWO = "animal"
    TYPE_THREE = "drinks"

    def getTypeByName(type_name: str):
        for name, member in Type.__dict__.get("_member_map_").items():
            if (name.__eq__(type_name)):
                return member
        return None


print(Type.getTypeByName("TYPE_THIRD"))
print(Type.getTypeByName("TYPE_TWO"))
print(Type.getTypeByName("TYPE_TWO").name)
print(Type.getTypeByName("TYPE_TWO").value)

结果如下:

在这里插入图片描述

(2)第二种获取枚举值方式,通过枚举类.__members__.items()获取枚举值

from enum import Enum, unique

# @unique
class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0


for name, member in OrderStatus.__members__.items():
    print(name, member)

# 输出结果如下:
# NO_PAY OrderStatus.NO_PAY
# PAID OrderStatus.PAID
# REFUND OrderStatus.REFUND
# WAIT_PAY OrderStatus.NO_PAY

打印信息更详细些,如下:

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0


for name, member in OrderStatus.__members__.items():
    print(name, member)
    print("枚举name:%s" % member.name)
    print("value:%s" % member.value)
    print("*" * 20)

结果:

NO_PAY OrderStatus.NO_PAY
枚举name:NO_PAY
value:0
********************
PAID OrderStatus.PAID
枚举name:PAID
value:1
********************
REFUND OrderStatus.REFUND
枚举name:REFUND
value:2
********************
WAIT_PAY OrderStatus.NO_PAY
枚举name:NO_PAY
value:0
********************

可以根据结果发现,第二种获取枚举值的方式,和第一种方式是类似的:

在这里插入图片描述

同样的,我们可以为枚举类实现一个静态方法,亦是根据name来获取该枚举对象:

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0

    def getOrderStatusByName(status_name: str):
        for name, member in OrderStatus.__members__.items():
            if (name.__eq__(status_name)):
                return member
        return None


print(OrderStatus.getOrderStatusByName("PAID"))
print(OrderStatus.getOrderStatusByName("PAID").name)
print(OrderStatus.getOrderStatusByName("PAID").value)
print(OrderStatus.getOrderStatusByName("WAIT_PAY"))
print(OrderStatus.getOrderStatusByName("WAIT_PAY").name)
print(OrderStatus.getOrderStatusByName("WAIT_PAY").value)
print(OrderStatus.getOrderStatusByName("Unknown"))

结果如下:

在这里插入图片描述

(3)第三种获取枚举值方式,通过for … in 枚举类 获取枚举值

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0

    def getOrderStatusByName(status_name: str):
        for name, member in OrderStatus.__members__.items():
            if (name.__eq__(status_name)):
                return member
        return None


for i in OrderStatus:
    print(i)
    print(i.name)
    print(i.value)
    print("*" * 20)

执行结果如下:

在这里插入图片描述

上述结果可见,如果枚举类中value有重复的,for i 的方式不会输出别名的枚举值,如果希望别名的枚举也输出,那么采用上述的第一种和第二种方式即可。

2.3 枚举的比较

(1)枚举间可以使用等值比较

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0

    def getOrderStatusByName(status_name: str):
        for name, member in OrderStatus.__members__.items():
            if (name.__eq__(status_name)):
                return member
        return None


print(OrderStatus.NO_PAY == OrderStatus.WAIT_PAY)
# True
print(OrderStatus.NO_PAY == OrderStatus.PAID)
# False
print(OrderStatus.REFUND == OrderStatus.REFUND)
# True

上述可知,别名枚举值间,==是True,非别名枚举间,同一个枚举,==才为True。

print(OrderStatus.REFUND != OrderStatus.REFUND)
# False

或者判断不相等,使用!=也是支持的。

(2)枚举间可以使用is比较

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0

    def getOrderStatusByName(status_name: str):
        for name, member in OrderStatus.__members__.items():
            if (name.__eq__(status_name)):
                return member
        return None


print(OrderStatus.NO_PAY is OrderStatus.WAIT_PAY)
# True
print(OrderStatus.NO_PAY is OrderStatus.PAID)
# False
print(OrderStatus.REFUND is OrderStatus.REFUND)
# True

is的使用同上述==的结果。

同理,is not也是支持的:

print(OrderStatus.REFUND is not OrderStatus.REFUND)
# False

(3)枚举间不可以进行大小比较

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 0

    def getOrderStatusByName(status_name: str):
        for name, member in OrderStatus.__members__.items():
            if (name.__eq__(status_name)):
                return member
        return None

print(OrderStatus.NO_PAY < OrderStatus.WAIT_PAY)
# 报错
# TypeError: '<' not supported between instances of 'OrderStatus' and 'OrderStatus'

2.4 枚举类型转换

枚举最大的好处或者说优势,是增加了代码的可读性:

if i == 1:
    pass
elif i == 2:
    pass

上面代码可读性很差,换成枚举则会好上许多:

if i == OrderStatus.PAID.value:
    pass
elif i == OrderStatus.REFUND.value:
    pass

2.5 总结

(1)枚举类不能用来实例化对象,同时,枚举类型是使用单例模式实现的。在创建枚举类的时候,Python就在内存中为我们创建了枚举类的对象,因此我们不必实例化枚举类。并且由于枚举类的“__new__”方法,将会保证内存中只会存在一个枚举类的实例。

class OrderStatus(Enum):
    NO_PAY = 0
    PAID = 1
    REFUND = 2
    WAIT_PAY = 5

    def getOrderStatusByName(status_name: str):
        for name, member in OrderStatus.__members__.items():
            if (name.__eq__(status_name)):
                return member
        return None

o = OrderStatus()
# TypeError: __call__() missing 1 required positional argument: 'value'

(2)访问枚举类中某一项,直接使用类名访问加上要访问的项即可:OrderStatus.PAID

(3)枚举类里面定义的枚举值Key = Value,在类外部不能修改枚举值的Value值(def __setattr__(cls, name, value)有限制)

(4)枚举值可以通过==或者is比较,不能比较大小。

(5)枚举类中的枚举值的Key不能相同,但Value可以相同。Value相同的各个枚举值,后续相同的各个Key都会被视为别名。

(6)枚举类的枚举值获取,可以通过for in,或者枚举类.__dict__.get(“_member_map_”).items()、枚举类.__members__.items()的方式获取枚举值,区别在于for in不会获取到别名枚举,其它两种方式可以获取别名枚举。

(7)如果枚举类中各个枚举值的Value值不能相同,那么使用@unique装饰器修饰枚举类即可。

  • 27
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值