搜尋
首頁後端開發Python教學Python中enum模組原始碼的詳細分析(程式碼範例)

本篇文章帶給大家的內容是關於Python中enum模組原始碼的詳細分析(程式碼範例),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

上一篇 《Python中枚舉類型的詳解(程式碼範例)》 文末說有機會的話可以看看它的原始碼。那就來讀一讀,看看枚舉的幾個重要的特性是如何實現的。

要想閱讀這部分,需要對元類程式設計有所了解。

成員名不允許重複

這部分我的第一個想法是去控制 __dict__ 中的 key 。但這樣的方式並不好,__dict__ 範圍大,它包含該類別的所有屬性和方法。而不單單是枚舉的命名空間。我在原始碼中發現 enum 使用另一個方法。透過 __prepare__ 魔術方法可以傳回一個類別字典實例,在該實例
使用 __prepare__ 魔術方法自訂命名空間,在該空間內限定成員名不允許重複。

# 自己实现
class _Dict(dict):
    def __setitem__(self, key, value):
        if key in self:
            raise TypeError('Attempted to reuse key: %r' % key)
        super().__setitem__(key, value)

class MyMeta(type):
    @classmethod
    def __prepare__(metacls, name, bases):
        d = _Dict()
        return d

class Enum(metaclass=MyMeta):
    pass

class Color(Enum):
    red = 1
    red = 1         # TypeError: Attempted to reuse key: 'red'

再看看Enum 模組的具體實作:

class _EnumDict(dict):
    def __init__(self):
        super().__init__()
        self._member_names = []
        ...

    def __setitem__(self, key, value):
        ...
        elif key in self._member_names:
            # descriptor overwriting an enum?
            raise TypeError('Attempted to reuse key: %r' % key)
        ...
        self._member_names.append(key)
        super().__setitem__(key, value)
        
class EnumMeta(type):
    @classmethod
    def __prepare__(metacls, cls, bases):
        enum_dict = _EnumDict()
        ...
        return enum_dict

class Enum(metaclass=EnumMeta):
    ...

模組中的_EnumDict 建立了_member_names 清單來儲存成員名,這是因為不是所有的命名空間內的成員都是枚舉的成員。例如 __str__, __new__ 等魔術方法就不是了,所以這邊的 __setitem__ 需要做一些過濾:

def __setitem__(self, key, value):
    if _is_sunder(key):     # 下划线开头和结尾的,如 _order__
        raise ValueError('_names_ are reserved for future Enum use')
    elif _is_dunder(key):   # 双下划线结尾的, 如 __new__
        if key == '__order__':
            key = '_order_'
    elif key in self._member_names: # 重复定义的 key
        raise TypeError('Attempted to reuse key: %r' % key)
    elif not _is_descriptor(value): # value得不是描述符
        self._member_names.append(key)
        self._last_values.append(value)
    super().__setitem__(key, value)

模組考慮的會更全面。

每個成員都有名稱屬性和值屬性

上述的程式碼中,Color.red 取得的值是 1。而 eumu 模組中,定義的枚舉類別中,每個成員都是有名稱和屬性值的;並且細心的話還會發現 Color.red 是 Color 的範例。這樣的情況是如何來實現的呢。

還是用元類來完成,在元類的__new__ 中實現,具體的思路是,先創建目標類,然後為每個成員都創建一樣的類,再通過setattr 的方式將後續的類別作為屬性加入到目標類別中,偽代碼如下:

def __new__(metacls, cls, bases, classdict):
    __new__ = cls.__new__
    # 创建枚举类
    enum_class = super().__new__()
    # 每个成员都是cls的示例,通过setattr注入到目标类中
    for name, value in cls.members.items():
        member = super().__new__()
        member.name = name
        member.value = value
        setattr(enum_class, name, member)
    return enum_class

來看下一個可運行的demo:

class _Dict(dict):
    def __init__(self):
        super().__init__()
        self._member_names = []

    def __setitem__(self, key, value):
        if key in self:
            raise TypeError('Attempted to reuse key: %r' % key)

        if not key.startswith("_"):
            self._member_names.append(key)
        super().__setitem__(key, value)

class MyMeta(type):
    @classmethod
    def __prepare__(metacls, name, bases):
        d = _Dict()
        return d

    def __new__(metacls, cls, bases, classdict):
        __new__ = bases[0].__new__ if bases else object.__new__
        # 创建枚举类
        enum_class = super().__new__(metacls, cls, bases, classdict)

        # 创建成员
        for member_name in classdict._member_names:
            value = classdict[member_name]
            enum_member = __new__(enum_class)
            enum_member.name = member_name
            enum_member.value = value
            setattr(enum_class, member_name, enum_member)

        return enum_class

class MyEnum(metaclass=MyMeta):
    pass

class Color(MyEnum):
    red = 1
    blue = 2

    def __str__(self):
        return "%s.%s" % (self.__class__.__name__, self.name)

print(Color.red)        # Color.red
print(Color.red.name)   # red
print(Color.red.value)  # 1

enum 模組在讓每個成員都有名稱和值的屬性的實作思路是一樣的(程式碼我就不貼了)。 EnumMeta.__new__ 是這個模組的重點,幾乎所有枚舉的特性都在這個函數實作。

當成員值相同時,第二個成員是第一個成員的別名

從這節開始就不再使用自己實作的類別的說明了,而是透過拆解enum 模組的程式碼來說明其實現了,從模組的使用特性中可以知道,如果成員值相同,後者會是前者的一個別名:

from enum import Enum
class Color(Enum):
    red = 1
    _red = 1

print(Color.red is Color._red)  # True

從這可以知道,red和_red是同一物件。這又要怎麼實現呢?

元類別會為枚舉類別建立_member_map_ 屬性來儲存成員名稱與成員的映射關係,如果發現建立的成員的值已經在映射關係中了,就會用映射表中的物件來取代:

class EnumMeta(type):
    def __new__(metacls, cls, bases, classdict):
        ...
        # create our new Enum type
        enum_class = super().__new__(metacls, cls, bases, classdict)
        enum_class._member_names_ = []               # names in definition order
        enum_class._member_map_ = OrderedDict()      # name->value map

        for member_name in classdict._member_names:
            enum_member = __new__(enum_class)

            # If another member with the same value was already defined, the
            # new member becomes an alias to the existing one.
            for name, canonical_member in enum_class._member_map_.items():
                if canonical_member._value_ == enum_member._value_:
                    enum_member = canonical_member     # 取代
                    break
            else:
                # Aliases don't appear in member names (only in __members__).
                enum_class._member_names_.append(member_name)  # 新成员,添加到_member_names_中
            
            enum_class._member_map_[member_name] = enum_member
            ...

從程式碼上來看,即使是成員值相同,還是會先為他們都創建對象,不過後創建的很快就會被垃圾回收掉了(我認為這邊是有優化空間的)。透過與_member_map_ 映射表做對比,用以建立該成員值的成員取代後續,但兩者成員名稱都會在_member_map_ 中,如例子中的red 和_red 都在該字典,但他們指向的是同一個對象。

屬性 _member_names_  只會記錄第一個,這將會與列舉的迭代有關。

可以透過成員值來取得成員

print(Color['red'])  # Color.red  通过成员名来获取成员
print(Color(1))      # Color.red  通过成员值来获取成员

列舉類別中的成員都是單例模式,元類別建立的列舉類別中也維護了值到成員的映射關係_value2member_map_ :

class EnumMeta(type):
    def __new__(metacls, cls, bases, classdict):
        ...
        # create our new Enum type
        enum_class = super().__new__(metacls, cls, bases, classdict)
        enum_class._value2member_map_ = {}

        for member_name in classdict._member_names:
            value = enum_members[member_name]
            enum_member = __new__(enum_class)

            enum_class._value2member_map_[value] = enum_member
            ...

然後在Enum 的__new__ 傳回該單例即可:

class Enum(metaclass=EnumMeta):
    def __new__(cls, value):
        if type(value) is cls:
            return value

        # 尝试从 _value2member_map_ 获取
        try:
            if value in cls._value2member_map_:
                return cls._value2member_map_[value]
        except TypeError:
            # 从 _member_map_ 映射获取
            for member in cls._member_map_.values():
                if member._value_ == value:
                    return member

        raise ValueError("%r is not a valid %s" % (value, cls.__name__))

迭代的方式遍歷成員

#枚舉類別支援迭代的方式遍歷成員,依定義的順序,如果有值重複的成員,只取得重複的第一個成員。對於重複的成員值只取得第一個成員,剛好屬性_member_names_  只會記錄第一個:

class Enum(metaclass=EnumMeta):
    def __iter__(cls):
        return (cls._member_map_[name] for name in cls._member_names_)

總結

enum 模組的核心特性的實現想法就是這樣,幾乎都是透過元類黑魔法來實現的。對於成員之間不能做比較大小但可以做等值比較。這反而不需要講,這其實繼承自 object 就是這樣的,不用額外做什麼就有的「特性」了。

#

以上是Python中enum模組原始碼的詳細分析(程式碼範例)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:segmentfault。如有侵權,請聯絡admin@php.cn刪除
Python vs.C:申請和用例Python vs.C:申請和用例Apr 12, 2025 am 12:01 AM

Python适合数据科学、Web开发和自动化任务,而C 适用于系统编程、游戏开发和嵌入式系统。Python以简洁和强大的生态系统著称,C 则以高性能和底层控制能力闻名。

2小時的Python計劃:一種現實的方法2小時的Python計劃:一種現實的方法Apr 11, 2025 am 12:04 AM

2小時內可以學會Python的基本編程概念和技能。 1.學習變量和數據類型,2.掌握控制流(條件語句和循環),3.理解函數的定義和使用,4.通過簡單示例和代碼片段快速上手Python編程。

Python:探索其主要應用程序Python:探索其主要應用程序Apr 10, 2025 am 09:41 AM

Python在web開發、數據科學、機器學習、自動化和腳本編寫等領域有廣泛應用。 1)在web開發中,Django和Flask框架簡化了開發過程。 2)數據科學和機器學習領域,NumPy、Pandas、Scikit-learn和TensorFlow庫提供了強大支持。 3)自動化和腳本編寫方面,Python適用於自動化測試和系統管理等任務。

您可以在2小時內學到多少python?您可以在2小時內學到多少python?Apr 09, 2025 pm 04:33 PM

兩小時內可以學到Python的基礎知識。 1.學習變量和數據類型,2.掌握控制結構如if語句和循環,3.了解函數的定義和使用。這些將幫助你開始編寫簡單的Python程序。

如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎?如何在10小時內通過項目和問題驅動的方式教計算機小白編程基礎?Apr 02, 2025 am 07:18 AM

如何在10小時內教計算機小白編程基礎?如果你只有10個小時來教計算機小白一些編程知識,你會選擇教些什麼�...

如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到?如何在使用 Fiddler Everywhere 進行中間人讀取時避免被瀏覽器檢測到?Apr 02, 2025 am 07:15 AM

使用FiddlerEverywhere進行中間人讀取時如何避免被檢測到當你使用FiddlerEverywhere...

Python 3.6加載Pickle文件報錯"__builtin__"模塊未找到怎麼辦?Python 3.6加載Pickle文件報錯"__builtin__"模塊未找到怎麼辦?Apr 02, 2025 am 07:12 AM

Python3.6環境下加載Pickle文件報錯:ModuleNotFoundError:Nomodulenamed...

如何提高jieba分詞在景區評論分析中的準確性?如何提高jieba分詞在景區評論分析中的準確性?Apr 02, 2025 am 07:09 AM

如何解決jieba分詞在景區評論分析中的問題?當我們在進行景區評論分析時,往往會使用jieba分詞工具來處理文�...

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解鎖Myrise中的所有內容
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

強大的PHP整合開發環境

SublimeText3 Linux新版

SublimeText3 Linux新版

SublimeText3 Linux最新版