成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Python 的 enum 模塊源碼分析

muddyway / 644人閱讀

摘要:起步上一篇的枚舉類型文末說(shuō)有機(jī)會(huì)的話可以看看它的源碼。但這樣的方式并不好,范圍大,它包含該類的所有屬性和方法。而不單單是枚舉的命名空間。每個(gè)成員都有名稱屬性和值屬性上述的代碼中,取得的值是。

起步

上一篇 《Python 的枚舉類型》 文末說(shuō)有機(jī)會(huì)的話可以看看它的源碼。那就來(lái)讀一讀,看看枚舉的幾個(gè)重要的特性是如何實(shí)現(xiàn)的。

要想閱讀這部分,需要對(duì)元類編程有所了解。

成員名不允許重復(fù)

這部分我的第一個(gè)想法是去控制 __dict__ 中的 key 。但這樣的方式并不好,__dict__ 范圍大,它包含該類的所有屬性和方法。而不單單是枚舉的命名空間。我在源碼中發(fā)現(xiàn) enum 使用另一個(gè)方法。通過(guò) __prepare__ 魔術(shù)方法可以返回一個(gè)類字典實(shí)例,在該實(shí)例
使用 __prepare__ 魔術(shù)方法自定義命名空間,在該空間內(nèi)限定成員名不允許重復(fù)。

# 自己實(shí)現(xiàn)
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 模塊的具體實(shí)現(xiàn):

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 創(chuàng)建了 _member_names 列表來(lái)存儲(chǔ)成員名,這是因?yàn)椴皇撬械拿臻g內(nèi)的成員都是枚舉的成員。比如 __str__, __new__ 等魔術(shù)方法就不是了,所以這邊的 __setitem__ 需要做一些過(guò)濾:

def __setitem__(self, key, value):
    if _is_sunder(key):     # 下劃線開(kāi)頭和結(jié)尾的,如 _order__
        raise ValueError("_names_ are reserved for future Enum use")
    elif _is_dunder(key):   # 雙下劃線結(jié)尾的, 如 __new__
        if key == "__order__":
            key = "_order_"
    elif key in self._member_names: # 重復(fù)定義的 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)

模塊考慮的會(huì)更全面。

每個(gè)成員都有名稱屬性和值屬性

上述的代碼中,Color.red 取得的值是 1。而 eumu 模塊中,定義的枚舉類中,每個(gè)成員都是有名稱和屬性值的;并且細(xì)心的話還會(huì)發(fā)現(xiàn) Color.redColor 的示例。這樣的情況是如何來(lái)實(shí)現(xiàn)的呢。

還是用元類來(lái)完成,在元類的 __new__ 中實(shí)現(xiàn),具體的思路是,先創(chuàng)建目標(biāo)類,然后為每個(gè)成員都創(chuàng)建一樣的類,再通過(guò) setattr 的方式將后續(xù)的類作為屬性添加到目標(biāo)類中,偽代碼如下:

def __new__(metacls, cls, bases, classdict):
    __new__ = cls.__new__
    # 創(chuàng)建枚舉類
    enum_class = super().__new__()
    # 每個(gè)成員都是cls的示例,通過(guò)setattr注入到目標(biāo)類中
    for name, value in cls.members.items():
        member = super().__new__()
        member.name = name
        member.value = value
        setattr(enum_class, name, member)
    return enum_class

來(lái)看下一個(gè)可運(yùn)行的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__
        # 創(chuàng)建枚舉類
        enum_class = super().__new__(metacls, cls, bases, classdict)

        # 創(chuàng)建成員
        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 模塊在讓每個(gè)成員都有名稱和值的屬性的實(shí)現(xiàn)思路是一樣的(代碼我就不貼了)。EnumMeta.__new__ 是該模塊的重點(diǎn),幾乎所有枚舉的特性都在這個(gè)函數(shù)實(shí)現(xiàn)。

當(dāng)成員值相同時(shí),第二個(gè)成員是第一個(gè)成員的別名

從這節(jié)開(kāi)始就不再使用自己實(shí)現(xiàn)的類的說(shuō)明了,而是通過(guò)拆解 enum 模塊的代碼來(lái)說(shuō)明其實(shí)現(xiàn)了,從模塊的使用特性中可以知道,如果成員值相同,后者會(huì)是前者的一個(gè)別名:

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

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

從這可以知道,red和_red是同一對(duì)象。這又要怎么實(shí)現(xiàn)呢?

元類會(huì)為枚舉類創(chuàng)建 _member_map_ 屬性來(lái)存儲(chǔ)成員名與成員的映射關(guān)系,如果發(fā)現(xiàn)創(chuàng)建的成員的值已經(jīng)在映射關(guān)系中了,就會(huì)用映射表中的對(duì)象來(lái)取代:

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
            ...

從代碼上來(lái)看,即使是成員值相同,還是會(huì)先為他們都創(chuàng)建對(duì)象,不過(guò)后創(chuàng)建的很快就會(huì)被垃圾回收掉了(我認(rèn)為這邊是有優(yōu)化空間的)。通過(guò)與 _member_map_ 映射表做對(duì)比,用以創(chuàng)建該成員值的成員取代后續(xù),但兩者成員名都會(huì)在 _member_map_ 中,如例子中的 red_red 都在該字典,但他們指向的是同一個(gè)對(duì)象。

屬性 _member_names_ 只會(huì)記錄第一個(gè),這將會(huì)與枚舉的迭代有關(guān)。

可以通過(guò)成員值來(lái)獲取成員
print(Color["red"])  # Color.red  通過(guò)成員名來(lái)獲取成員
print(Color(1))      # Color.red  通過(guò)成員值來(lái)獲取成員

枚舉類中的成員都是單例模式,元類創(chuàng)建的枚舉類中還維護(hù)了值到成員的映射關(guān)系 _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__))
迭代的方式遍歷成員

枚舉類支持迭代的方式遍歷成員,按定義的順序,如果有值重復(fù)的成員,只獲取重復(fù)的第一個(gè)成員。對(duì)于重復(fù)的成員值只獲取第一個(gè)成員,正好屬性 _member_names_ 只會(huì)記錄第一個(gè):

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

enum 模塊的核心特性的實(shí)現(xiàn)思路就是這樣,幾乎都是通過(guò)元類黑魔法來(lái)實(shí)現(xiàn)的。對(duì)于成員之間不能做比較大小但可以做等值比較。這反而不需要講,這其實(shí)繼承自 object 就是這樣的,不用額外做什么就有的“特性”了。

總之,enum 模塊相對(duì)獨(dú)立,且代碼量不多,對(duì)于想知道元類編程可以閱讀一下,教科書(shū)式教學(xué),還有單例模式等,值得一讀。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/42781.html

相關(guān)文章

  • Python 枚舉類型

    摘要:起步的原生類型中并不包含枚舉類型。枚舉類型可以看作是一種標(biāo)簽或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期月份狀態(tài)等。簡(jiǎn)單的示例枚舉成員有值默認(rèn)可重復(fù),枚舉成員具有友好的字符串表示枚舉類型不可實(shí)例化,不可更改。 起步 Python 的原生類型中并不包含枚舉類型。為了提供更好的解決方案,Python 通過(guò) PEP 435 在 3.4 版本中添加了 enum 標(biāo)準(zhǔn)庫(kù)。 枚舉...

    wall2flower 評(píng)論0 收藏0
  • PyTips 0x17-Python枚舉類型

    摘要:中的枚舉類型枚舉類型可以看作是一種標(biāo)簽或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期月份狀態(tài)等。 Python 中的枚舉類型 枚舉類型可以看作是一種標(biāo)簽或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期、月份、狀態(tài)等。Python 的原生類型(Built-in types)里并沒(méi)有專門(mén)的枚舉類型,但是我們可以通過(guò)很多方法來(lái)實(shí)現(xiàn)它,例如字典、類等: WEEKD...

    yedf 評(píng)論0 收藏0
  • 別再說(shuō)Python沒(méi)有枚舉類型了,好好看看

    摘要:枚舉類型可以看作是一種標(biāo)簽或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期月份狀態(tài)等。 showImg(https://segmentfault.com/img/remote/1460000018679998?w=740&h=728); 枚舉類型可以看作是一種標(biāo)簽或是一系列常量的集合,通常用于表示某些特定的有限集合,例如星期、月份、狀態(tài)等。 Python 的原生類型(Bu...

    zsy888 評(píng)論0 收藏0
  • Python入門(mén)細(xì)節(jié)

    摘要:入門(mén)細(xì)節(jié)相除后的類型雙斜杠是整除,出來(lái)的類型是。序列,集合和字典屬于組,是的基本數(shù)據(jù)類型。變量名區(qū)分大小寫(xiě)。盡量避免循環(huán)引入。變量函數(shù)等引入內(nèi)指定的變量函數(shù)等。中如果沒(méi)有出現(xiàn)模塊名也是絕對(duì)導(dǎo)入。頂級(jí)包與入口文件的位置 python入門(mén)細(xì)節(jié) 相除后的類型 type(2/2) float type(2//2) int 雙斜杠是整除,出來(lái)的類型是int。單斜杠的出來(lái)的是float類型。 進(jìn)制...

    microcosm1994 評(píng)論0 收藏0
  • python模塊enum_上

    摘要:模塊定義了種枚舉類裝飾器助手在中加入創(chuàng)建枚舉注意點(diǎn)枚舉值可以是任何類型,如果值不重要可以使用自動(dòng)選擇。使用裝飾器可以對(duì)枚舉值進(jìn)行唯一約束枚舉專用的類裝飾器。序列化一般要求序列化的枚舉要定義在模塊頂層,因?yàn)榉葱蛄谢竺杜e能夠從模塊導(dǎo)入。 enum模塊定義了: 4種枚舉類:Enum, IntEnum, Flag, IntFlag 裝飾器:unique() 助手:auto Flag, ...

    kelvinlee 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<