1. 程式人生 > 實用技巧 >列舉類

列舉類

列舉類的使用

實際開發中,我們離不開定義常量,當我們需要定義常量時,其中一個辦法是用大寫變數通過整數來定義,例如月份:

JAN = 1
FEB = 2
MAR = 3
...
NOV = 11
DEC = 12

當然這樣做簡單快捷,缺點是型別是 int ,並且仍然是變數。

那有沒有什麼好的方法呢?

這時候我們定義一個 class 型別,每個常量都是 class 裡面唯一的例項。

正好 Python 提供了 Enum 類來實現這個功能如下:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-

from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 遍歷列舉型別
for name, member in Month.__members__.items():
    print(name, '---------', member, '----------', member.value)

# 直接引用一個常量
print('\n', Month.Jan)

輸出的結果如下:

我們使用 Enum 來定義了一個列舉類。

上面的程式碼,我們建立了一個有關月份的列舉型別 Month ,這裡要注意的是構造引數,第一個引數 Month 表示的是該列舉類的類名,第二個 tuple 引數,表示的是列舉類的值;當然,列舉類通過 __members__ 遍歷它的所有成員的方法。

注意的一點是 , member.value 是自動賦給成員的 int 型別的常量,預設是從 1 開始的。

而且 Enum 的成員均為單例(Singleton),並且不可例項化,不可更改

Enum 的原始碼

通過上面的例項可以知道通過 __members__ 可以遍歷列舉類的所有成員。

那有沒有想過為什麼呢?

當你看到那段程式碼的時候,有沒有想過為什麼通過 __members__ 就能遍歷列舉型別的所有成員出來?

我們可以先來大致看看 Enum 的原始碼是如何實現的;

Enum 在模組 enum.py 中,先來看看 Enum 類的片段

class Enum(metaclass=EnumMeta):
    """Generic enumeration.
    Derive from this class to define new enumerations.
    """

可以看到,Enum 是繼承元類 EnumMeta 的;再看看 EnumMeta 的相關片段

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__ 方法返回的是一個包含一個 Dict 既 Map 的 MappingProxyType,並且通過 @property 將方法 __members__(cls) 的訪問方式改變為了變數的的形式,那麼就可以直接通過 __members__ 來進行訪問了

列舉的比較

因為列舉成員不是有序的,所以它們只支援通過標識(identity) 和相等性 (equality) 進行比較。下面來看看 ==is 的使用:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from enum import Enum


class User(Enum):
    Twowater = 98
    Liangdianshui = 30
    Tom = 12


Twowater = User.Twowater
Liangdianshui = User.Liangdianshui

print(Twowater == Liangdianshui, Twowater == User.Twowater)
print(Twowater is Liangdianshui, Twowater is User.Twowater)

try:
    print('\n'.join('  ' + s.name for s in sorted(User)))
except TypeError as err:
    print(' Error : {}'.format(err))

輸出的結果:


False True
False True
 Error : '<' not supported between instances of 'User' and 'User'

可以看看最後的輸出結果,報了個異常,那是因為大於和小於比較運算子引發 TypeError 異常。也就是 Enum 類的列舉是不支援大小運算子的比較的。

那麼能不能讓列舉類進行大小的比較呢?

當然是可以的,使用 IntEnum 類進行列舉,就支援比較功能。

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import enum


class User(enum.IntEnum):
    Twowater = 98
    Liangdianshui = 30
    Tom = 12


try:
    print('\n'.join(s.name for s in sorted(User)))
except TypeError as err:
    print(' Error : {}'.format(err))

看看輸出的結果:

Tom
Liangdianshui
Twowater

通過輸出的結果可以看到,列舉類的成員通過其值得大小進行了排序。也就是說可以進行大小的比較。