在 Python 中使用 enum

浮生若夢的程式設計發表於2019-03-01

原因與初衷

首先便是業務程式碼中會出現巨量的 XXType 之類的東西,業務越複雜,這種東西就越多,它們符合 enum 的基本假定,用 enum 去管理這些常量是很自然的想法。

其次便是 Python 並沒有實現 enum 這一抽象手段(其實我極其希望加入這一特性,另一個希望加入的特性是 const )。

常用的做法

方式1:模組級別常量
這也是標準庫和許多流行庫的做法,比如:

# direction.py
UP = 1
DOWN = 2
LEFT = 3
RIGHT = 4
複製程式碼

方式2:使用 enum 庫
Python 2 和Python 3 均可使用,Python 3 更是直接變為標準庫,從側面可知道 enum 這種東西的重要性。

典型使用:

import enum
import unittest


class BaseEnum(enum.Enum):
    """一般都會再次封裝下, 後文詳述原因"""

    @classmethod
    def values(cls):
        """獲取右邊value的集合
        
        因為我們經常要用到 xxvalue in xxEnum.values(), 這是我們
        封裝的原因與初衷
        
        :return:
        """
        values = []
        for attr in cls:
            values.append(attr.value)

        return values


class DirectionEnum(BaseEnum):
    """使用"""
    
    UP = 1
    DOWN = 2
    LEFT = 3
    RIGHT = 4
    
    
######################### 單元測試 ##########################
class TestBaseEnum(unittest.TestCase):
    def test_values(self):
        expected = frozenset((DirectionEnum.UP.value, DirectionEnum.DOWN.value,
                              DirectionEnum.LEFT.value, DirectionEnum.RIGHT.value))

        actual = frozenset(DirectionEnum.values())

        self.assertSetEqual(expected, actual)

    def test_value_in(self):
        value = 1

        self.assertTrue(value in DirectionEnum.values())

    def test_value_equal(self):
        value = 1

        self.assertTrue(value == DirectionEnum.UP.value)
複製程式碼

我的做法

上面的幾種做法已經覆蓋得很全面了,我完全沒必要自己再去弄一個醜陋的輪子。

之所以不用方式1,因為 module 對我而言是一個很重要的名稱空間,直接把常量掛載在其下面,對我而言是一種浪費。

之所以不用方式2,完全是因為我有點不喜歡 xxvalue == DirectionEnum.UP.value 這種寫法(難道不是 xxvalue == DirectionEnum.UP這種寫法更加自然嗎?)。但是總的來說,enum 庫畢竟是大神的作品,而且也實現了 enum 這一概念的的方方面面,我們自然可以放心使用,而且我也推薦使用。:)

我最終還是選擇了一種適合自己業務場景的一種使用方式,雖然土,但總還算是 work。

import unittest
import abc


class BaseEnum(abc.ABC):
    @abc.abstractclassmethod
    def values(cls):
        cls_dict = cls.__dict__

        return [cls_dict[key]
                for key in cls_dict.keys()
                if not key.startswith(`_`) and key.isupper()]


class DirectionEnum(BaseEnum):
    UP = 1
    DOWN = 2
    LEFT = 3
    RIGHT = 4

######################## 單元測試 ################################
class TestBaseEnum(unittest.TestCase):
    def test_values(self):
        expected = frozenset([DirectionEnum.UP, DirectionEnum.DOWN,
                              DirectionEnum.LEFT, DirectionEnum.RIGHT])
        actual = frozenset(DirectionEnum.values())

        self.assertSetEqual(expected, actual)

    def test_value_in(self):
        value = 1

        self.assertTrue(value in DirectionEnum.values())

    def test_value_equal(self):
        value = 1

        self.assertTrue(value == DirectionEnum.UP)
複製程式碼

生產環境中的用法

目錄結構一般我會定義如下

app/
    constants/
        __init__.py            把所有子檔案中的 API 匯入到package級別, 方便客戶端呼叫
        enum_constants.py      所有XXEnum都在這裡面, 檔案命名不重要, 反正我會提升到package級別
        xx_contants.py
複製程式碼

使用

from constants import DirectionEnum, XXEnum, YYEnum

def func():
    if somevalue == DirectionEnum.UP:
        # do something
複製程式碼

總結

其實更加希望在語言級別提供常量管理的常用手段,而不是為了 simple 而 simple(並沒有什麼意義)。

相關文章