python:枚举类
python实际开发中,需要定义类型时,一般是使用一组整数或者字符串来表示。如果使用常规的定义方式,非常容易被外部修改,Python3.4后,增加了枚举类的使用,就是为了解决这种场景而设计的。python枚举适用场景:值有限且固定(不希望被轻易或随意修改)的数据类型。Java也有枚举类的使用,使用关键字enum实现,而python一般是通过提供的Enum类继承实现的(python类支持多继承,ja
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装饰器修饰枚举类即可。
更多推荐
所有评论(0)