Python 系列学习十四:面向对象编程 - 枚举类

前言

打算写一系列文章来记录自己学习 Python 3 的点滴;本章主要介绍 Python 面向对象编程中有关枚举类型的相关内容;

本文为作者的原创作品,转载需注明出处;

Enum

Python 通过内置 class Enum,来实现 Python 中的枚举特性;构造枚举类有两种方式,一种是直接使用 Enum 构造函数的方式,一种是使用继承 Enum 自定义枚举类的方式;下面,我们以一年的十二个月份为例子,来看看相关的枚举类型是如何在 Python 中所定义的;

使用 Enum 构造函数

简单的源码分析

先来大致看看 Enum 的源码是如何实现的;Enum 在模块 enum.py 中,先来看看 Enum 类的片段

1
2
3
4
5
6
class 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
11
class 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__方法返回的是一个包含一个DictMapMappingProxyType,并且通过 @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. 遍历所有值

    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 , <enum 'Month'>
    Feb => Month.Feb , 2 , <enum 'Month'>
    Mar => Month.Mar , 3 , <enum 'Month'>
    Apr => Month.Apr , 4 , <enum 'Month'>
    May => Month.May , 5 , <enum 'Month'>
    Jun => Month.Jun , 6 , <enum 'Month'>
    Jul => Month.Jul , 7 , <enum 'Month'>
    Aug => Month.Aug , 8 , <enum 'Month'>
    Sep => Month.Sep , 9 , <enum 'Month'>
    Oct => Month.Oct , 10 , <enum 'Month'>
    Nov => Month.Nov , 11 , <enum 'Month'>
    Dec => Month.Dec , 12 , <enum 'Month'>

    首先,M.__members__.items() 中所包含的每个元素 item 是什么?

    我们来分析一下上述的代码,正如简单的源码分析中我们知道Month.__members__返回的是一个包含DictMappingProxyType 对象,通过调用其 items() 方法既可以返回该Dict,从上述执行的结果中,我们不难分析出该Dict的组成结构是 <key, value=member> 这样的结构,key 就是对应的月份的名称,value 对应的是一个 member 对象,可以通过 member.value 获取 int 排序值;所以,综上所述,一个 item 既是上述Dict的一个键值对,键是月份的名称,值是一个 member 对象;

    再次,那么member又是指的是什么呢?

    通过上述的输出结果,我们不难发现,member就是一个Enum对象;

  2. 取某个固定值

    • 根据 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 'Month'>
    • 根据 Enum.value 进行取值,取得是一个Enum对象

      1
      2
      3
      >>> Feb = Month(12)
      >>> Feb
      <Month.Dec: 12>

      取值的时候,注意不能越界,否则报错;

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      >>> Month(13)
      Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      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
16
from 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