이 기사는 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'
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): ...
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 모듈의 코드가 구현되어 있습니다. 모듈의 사용 특성을 통해 멤버 값이 동일하면 후자가 전자의 별칭이 될 것임을 알 수 있습니다.from enum import Enum class Color(Enum): red = 1 _red = 1 print(Color.red is Color._red) # True
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 ...
멤버는 멤버 값을 통해 얻을 수 있습니다
print(Color['red']) # Color.red 通过成员名来获取成员 print(Color(1)) # Color.red 通过成员值来获取成员
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 ...
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 중국어 웹사이트의 기타 관련 기사를 참조하세요!