set in motion
Previous Article "ThePython's Enumeration TypesAt the end of the article, it says that you can read the source code if you have the chance. So read on and see how a few important features of enumerations are implemented.
To read this section, an understanding of metaclass programming is required.
Duplicate member names are not allowed
My first idea for this part was to control the key in __dict__. But that's not a good way to do it. __dict__ has a large scope, it contains all the attributes and methods of the class. It's not just a namespace for enums. I found in the source code that enum uses another approach. The __prepare__ magic method returns an instance of the class dictionary, where you use the __prepare__ magic method to customize the namespace, and qualify the names of the members within that space without allowing duplication.
# Do it yourself 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'
Look again at the specific implementation of the Enum module:
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): ...
The _EnumDict in the module creates a list of _member_names to store the member names, this is because not all members in the namespace are members of the enumeration. For example, magic methods like __str__, __new__, etc. are not, so the __setitem__ here needs some filtering:
def __setitem__(self, key, value): if _is_sunder(key): # Starting and ending with an underscore, e.g. _order__ raise ValueError('_names_ are reserved for future Enum use') elif _is_dunder(key): # Double-underscore endings, such as __new__. if key == '__order__': key = '_order_' elif key in self._member_names: # Repeatedly defined key raise TypeError('Attempted to reuse key: %r' % key) elif not _is_descriptor(value): # value must not be a descriptor self._member_names.append(key) self._last_values.append(value) super().__setitem__(key, value)
Modules are considered more thoroughly.
Each member has a name attribute and a value attribute
In the above code, the value obtained is 1. In the eumu module, the enumerated classes are defined with names and attribute values for each member; and if you look closely, you will see that they are instances of Color. How can this be accomplished?
It's still done with metaclasses, implemented in the __new__ of the metaclass. The idea is to create the target class first, then create the same class for each member, and then add the subsequent class as an attribute to the target class by means of setattr. The pseudo-code is as follows:
def __new__(metacls, cls, bases, classdict): __new__ = cls.__new__ # Create enumerated classes enum_class = super().__new__() # Each member is an example of cls, injected into the target class via setattr for name, value in (): member = super().__new__() = name = value setattr(enum_class, name, member) return enum_class
Take a look at the next runnable 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 ("_"): 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__ # Create enumerated classes enum_class = super().__new__(metacls, cls, bases, classdict) # Create members 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__, ) print() # print() # red print() # 1
The enum module's implementation of the property of having every member have a name and value is along the same lines (I won't post the code). enumMeta.__new__ is the focus of the module, and almost all of the enumeration's features are implemented in this function.
When members have the same value, the second member is an alias of the first member
From this section onwards, instead of using the description of your own implementation of the class, you can illustrate its implementation by disassembling the code of the enum module, which, as you can see from the usage characteristics of the module, is an alias of the former if the members have the same value:
from enum import Enum class Color(Enum): red = 1 _red = 1 print( is Color._red) # True
From this we know that red and _red are the same object. How is this going to be realized?
The metaclass creates the _member_map_ property for the enum class to store the mapping of member names to members, and if it finds that the value of a created member is already in the mapping relationship, it replaces it with an object from the mapping table:
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 # Supersedes break else: # Aliases don't appear in member names (only in __members__). enum_class._member_names_.append(member_name) # New members, added to _member_names_ enum_class._member_map_[member_name] = enum_member ...
From the code, even if the member values are the same, the object is still created for both of them first, but the later one is quickly garbage collected (I think there is room for optimization on this side). By comparison with the _member_map_ mapping table, the member used to create that member value replaces the subsequent, but both member names will be in _member_map_, as in the example red and _red are both in that dictionary, but they point to the same object.
The attribute _member_names_ will only record the first one, which will be relevant for enumeration iterations.
You can get members by their values
print(Color['red']) # Get members by their names print(Color(1)) # Getting members by their values
Members in enumerated classes are in singleton mode, and value-to-member mapping relationships _value2member_map_ are maintained in enumerated classes created by metaclasses .
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 ...
Then just return the singleton in the Enum's __new__:
class Enum(metaclass=EnumMeta): def __new__(cls, value): if type(value) is cls: return value # Trying to get from _value2member_map_ try: if value in cls._value2member_map_: return cls._value2member_map_[value] except TypeError: # Get from _member_map_ mapping for member in cls._member_map_.values(): if member._value_ == value: return member raise ValueError("%r is not a valid %s" % (value, cls.__name__))
Iterative way to traverse members
Enumerated classes support iterative ways of traversing members, in the order in which they are defined, and if there are members with duplicate values, only the first member of the duplicate is fetched. For duplicate member values only the first member is fetched, it happens that the attribute _member_names_ will only record the first one:
class Enum(metaclass=EnumMeta): def __iter__(cls): return (cls._member_map_[name] for name in cls._member_names_)
summarize
The core features of the enum module are implemented along these lines, almost exclusively through metaclass black magic. You can't compare sizes between members, but you can compare values. That's a no-brainer, it's just the way it is when you inherit from object, you don't have to do anything extra to get that "feature".
In short, enum module is relatively independent, and the amount of code is not much, for those who want to know the metaclass programming can read about it, textbook teaching, and singleton patterns, etc., it is worth reading.
Well, the above is the full content of this article, I hope that the content of this article on your learning or work has a certain reference learning value, if there are questions you can leave a message to exchange, thank you for my support.