ホームページ  >  記事  >  バックエンド開発  >  Python の enum モジュールのソース コードの詳細な分析 (コード例)

Python の enum モジュールのソース コードの詳細な分析 (コード例)

不言
不言転載
2018-12-11 10:33:082357ブラウズ

この記事では、Python の enum モジュールのソース コードの詳細な分析 (コード例) を紹介します。一定の参考値があります。必要な友人は参照してください。お役に立てれば幸いです。

前回の記事「Pythonの列挙型の詳しい解説(コード例)」記事の最後に、機会があれば見てみると良いと書いてあります。そのソースコード。次に、それを読んで、列挙のいくつかの重要な機能がどのように実装されているかを確認します。

この部分を読むには、メタクラス プログラミングについてある程度の理解が必要です。

メンバー名の繰り返しは許可されません

この部分の最初のアイデアは、__dict__ でキーを制御することです。しかし、この方法は良くありません。 __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 リストを作成します。これは、名前空間内のすべてのメンバーが Is であるわけではないためです。列挙のメンバー。たとえば、__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)
モジュールはそれをより包括的に考慮します。

各メンバーには name 属性と 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
次の実行可能なデモを見てみましょう:

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__ がこのモジュールの焦点であり、ほぼすべての列挙機能がこの関数に実装されています。

メンバーの値が同じ場合、2 番目のメンバーは最初のメンバーのエイリアスです。

このセクション以降、 で実装されるクラスの説明になります。自分自身は使用されなくなりますが、enum モジュールのコードを逆アセンブルすることによってその実装を示しています。モジュールの使用特性から、メンバーの値が同じ場合、後者は前者の別名:

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

print(Color.red is Color._red)  # True
これから、red と _red が同じオブジェクトであることがわかります。これを達成するにはどうすればよいでしょうか?

メタクラスは、列挙型クラスの _member_map_ 属性を作成して、メンバー名とメンバーの間のマッピング関係を保存します。作成されたメンバーの値がすでにマッピング関係にあることが判明した場合、そのオブジェクトはコードの観点から見ると、メンバーの値が同じであっても、最初にすべてのオブジェクトが作成されますが、後で作成されるオブジェクトはガベージはすぐに収集されます (ここには空間的な最適化があると思います)。 _member_map_ マッピング テーブルと比較すると、メンバー値の作成に使用されたメンバーが後続のメンバーを置き換えますが、両方のメンバー名が _member_map_ に含まれます。たとえば、例の red と _red は両方ともディクショナリ内にありますが、それらは次のことを指します。同じオブジェクトです。

属性 _member_names_ は、列挙の反復に関連する最初の属性のみを記録します。

メンバーはメンバー値を通じて取得できます

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
            ...
列挙型クラスのメンバーはシングルトン モードであり、値も列挙型クラスで維持されますメタクラスによって作成された members_value2member_map_:
print(Color['red'])  # Color.red  通过成员名来获取成员
print(Color(1))      # Color.red  通过成员值来获取成员
へのマッピング関係。次に Enum の __new__:

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

メンバーを反復的に走査します。##列挙型クラス定義された順序でのメンバーの反復走査をサポートします。重複する値を持つメンバーが存在する場合、最初の重複メンバーのみが取得されます。重複するメンバー値の場合、最初のメンバーのみが取得され、属性 _member_names_ は最初のメンバーのみを記録します。

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__))

summary

enum module これはアイデアであり、ほとんどすべてがメタクラスの黒魔術によって実現されます。メンバーのサイズを比較することはできませんが、等しい値で比較することはできます。これは説明するまでもありませんが、実はオブジェクトから継承されており、余計なことをせずに「機能」を持っています。

以上がPython の enum モジュールのソース コードの詳細な分析 (コード例)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。