이 기사는 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) # 1enum 모듈은 각 멤버의 이름과 값으로 속성을 구현하는 것과 동일한 아이디어를 가지고 있습니다(설명하지 않겠습니다) 코드)가 게시되었습니다). 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!