前言
打算写一系列文章来记录自己学习 Python 3 的点滴;本章主要介绍 Python 面向对象编程中有关枚举类型的相关内容;
本文为作者的原创作品,转载需注明出处;
Enum
Python 通过内置 class Enum,来实现 Python 中的枚举特性;构造枚举类有两种方式,一种是直接使用 Enum 构造函数的方式,一种是使用继承 Enum 自定义枚举类的方式;下面,我们以一年的十二个月份为例子,来看看相关的枚举类型是如何在 Python 中所定义的;
使用 Enum 构造函数
简单的源码分析
先来大致看看 Enum 的源码是如何实现的;Enum 在模块 enum.py 中,先来看看 Enum 类的片段
1
2
3
4
5
6class Enum(metaclass=EnumMeta):
"""Generic enumeration.
Derive from this class to define new enumerations.
"""
可以看到,Enum 是继承元类 EnumMeta 的;再看看 EnumMeta 的相关片段
1
2
3
4
5
6
7
8
9
10
11class EnumMeta(type):
"""Metaclass for Enum"""
@property
def __members__(cls):
"""Returns a mapping of member name->value.
This mapping lists all enum members, including aliases. Note that this
is a read-only view of the internal mapping.
"""
return MappingProxyType(cls._member_map_)
首先__members__方法返回的是一个包含一个Dict既 Map 的 MappingProxyType,并且通过 @property 将方法__members__(cls)的访问方式改变为了变量的的形式,既可以直接通过__members__来进行访问了;
构造一个 Enum
如何构造1
2>>>from enum import Enum
>>>M = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
通过上述的两行代码,我们就创建了一个有关月份的枚举类型Month,这里要注意的是构造参数,第一个参数Month表示的是该枚举类的类名,第二个 tuple 参数,表示的是枚举类的值;那么,看看我们如何对其取值以及背后的逻辑;
如何取值遍历所有值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15>>>for key, member in M.__members__.items():
... print( key, '=>', member, ',', member.value, ',', type(member) )
...
Jan => Month.Jan , 1 ,
Feb => Month.Feb , 2 ,
Mar => Month.Mar , 3 ,
Apr => Month.Apr , 4 ,
May => Month.May , 5 ,
Jun => Month.Jun , 6 ,
Jul => Month.Jul , 7 ,
Aug => Month.Aug , 8 ,
Sep => Month.Sep , 9 ,
Oct => Month.Oct , 10 ,
Nov => Month.Nov , 11 ,
Dec => Month.Dec , 12 ,
首先,M.__members__.items() 中所包含的每个元素 item 是什么?
我们来分析一下上述的代码,正如简单的源码分析中我们知道Month.__members__返回的是一个包含Dict的 MappingProxyType 对象,通过调用其 items() 方法既可以返回该Dict,从上述执行的结果中,我们不难分析出该Dict的组成结构是 这样的结构,key 就是对应的月份的名称,value 对应的是一个 member 对象,可以通过 member.value 获取 int 排序值;所以,综上所述,一个 item 既是上述Dict的一个键值对,键是月份的名称,值是一个 member 对象;
再次,那么member又是指的是什么呢?
通过上述的输出结果,我们不难发现,member就是一个Enum对象;
取某个固定值
根据 Key 进行取值,取得的是一个Enum对象;
1
2
3
4
5
6
7
8>>>Jan = M.Jan
>>>print(Jan)
Month.Jan
>>>Jan = M['Jan']
>>>print(Jan)
Month.Jan
>>>print(Jan.value)
1
得到的是一个Enum类型的对象
1
2>>>type(Jan)
根据 Enum.value 进行取值,取得是一个Enum对象
1
2
3>>>Feb = Month(12)
>>>Feb
取值的时候,注意不能越界,否则报错;
1
2
3
4
5
6
7
8
9
10>>>Month(13)
Traceback (most recent call last):
File "", line 1, in
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/enum.py", line 291, in __call__
return cls.__new__(cls, value)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/enum.py", line 533, in __new__
return cls._missing_(value)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/enum.py", line 546, in _missing_
raise ValueError("%r is not a valid %s" % (value, cls.__name__))
ValueError: 13 is not a valid Month
继承 Enum 自定义枚举类
那么,除了使用内置的 Enum 对象来构造枚举类型以外,我们该如何自定义自己的枚举类呢?同样以月份为例子,这次我们来构建自己的枚举类型,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from enum import Enum, unique
@unique
class Month(Enum):
Jan = 1
Feb = 2
Mar = 3
Apr = 4
May = 5
Jun = 6
Jul = 7
Aug = 8
Sep = 9
Oct = 10
Nov = 11
Dec = 12
是的,自定义构造一个枚举类就是那么简单,看看其调用逻辑,循环取得所有值,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15>>>for name, member in Month.__members__.items():
... print(name, '=>', member, ',', member.value)
...
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12