>백엔드 개발 >파이썬 튜토리얼 >Python의 enum 모듈 소스 코드에 대한 자세한 분석(코드 예)

Python의 enum 모듈 소스 코드에 대한 자세한 분석(코드 예)

不言
不言앞으로
2018-12-11 10:33:082448검색

이 기사는 Python의 enum 모듈 소스 코드에 대한 자세한 분석(코드 예제)을 제공합니다. 이는 특정 참조 가치가 있으므로 도움이 될 수 있습니다.

이전 글 "파이썬의 열거형에 대한 자세한 설명(코드 예시)" 글 말미에는 기회가 된다면 소스코드도 살펴보실 수 있다고 합니다. 그런 다음 그것을 읽고 열거의 몇 가지 중요한 기능이 어떻게 구현되는지 확인하십시오.

이 부분을 읽으려면 메타클래스 프로그래밍에 대한 이해가 필요합니다.

멤버 이름의 중복은 허용되지 않습니다

이 부분에 대한 첫 번째 아이디어는 __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 목록을 생성합니다. 이는 네임스페이스의 모든 멤버가 열거형의 멤버가 아니기 때문입니다. 예를 들어 __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__는 이 모듈의 초점이며 거의 모든 열거 기능이 이 함수에서 구현됩니다.

멤버 값이 동일할 경우 두 번째 멤버는 첫 번째 멤버의 별칭입니다

이 섹션부터는 더 이상 직접 구현한 클래스에 대한 설명을 사용하지 않고 클래스를 분해하여 설명합니다. 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_)

Summary

enum enum 모듈의 핵심 기능은 거의 모두 메타클래스 흑마술을 통해 이렇게 구현됩니다. 달성하기 위해. 구성원은 크기로 비교할 수 없지만 동일한 값으로 비교할 수 있습니다. 이에 대해 말할 필요가 없습니다. 이것은 실제로 객체에서 상속되며 추가 작업 없이 "기능"을 갖습니다.

위 내용은 Python의 enum 모듈 소스 코드에 대한 자세한 분석(코드 예)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 segmentfault.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제