Home Backend Development Python Tutorial Detailed analysis of the source code of the enum module in Python (code example)

Detailed analysis of the source code of the enum module in Python (code example)

Dec 11, 2018 am 10:33 AM
python

This article brings you a detailed analysis (code example) of the enum module source code in Python. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

Previous article "Detailed explanation of enumeration types in Python (code examples)" At the end of the article, it is said that if you have the opportunity, you can take a look at its source code. Then read it and see how several important features of enumeration are implemented.

To read this part, you need to have some understanding of metaclass programming.

Repeated member names are not allowed

My first idea for this part is to control the key in __dict__. But this way is not good, __dict__ has a large scope, it contains all attributes and methods of the class. Not just the enumeration namespace. I found in the source code that enum uses another method. A dictionary-like instance can be returned through the __prepare__ magic method. In this instance, the __prepare__ magic method is used to customize the namespace, and repeated member names are not allowed in this space.

# 自己实现
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'
Copy after login
Let’s look 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):
    ...
Copy after login
The _EnumDict in the module creates a _member_names list to store member names. This is because not all members in the namespace are Is a member of the enumeration. For example, __str__, __new__ and other magic methods are not, so the __setitem__ here needs to do some filtering:

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)
Copy after login
The module will consider it more comprehensively.

Each member has a name attribute and a value attribute

In the above code, the value obtained by Color.red is 1. In the eumu module, each member of the defined enumeration class has a name and attribute value; and if you are careful, you will find that Color.red is an example of Color. How is this situation achieved?

It is still done with metaclasses and implemented in __new__ of metaclasses. The specific idea is to first create the target class, then create the same class for each member, and then use setattr to add subsequent The class is added to the target class as an attribute. The pseudo code is as follows:

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
Copy after login
Let’s 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 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
Copy after login
The enum module allows each member to have a name and value. The implementation idea of ​​attributes is the same (I won’t post the code). EnumMeta.__new__ is the focus of this module, and almost all enumeration features are implemented in this function.

When the member values ​​are the same, the second member is an alias of the first member

From this section onwards, the description of the class implemented by yourself will no longer be used. , but illustrates its implementation by disassembling the code of the enum module. From the usage characteristics of the module, we can know that if the member values ​​​​are the same, the latter will be an alias of the former:

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

print(Color.red is Color._red)  # True
Copy after login
You can know from this , red and _red are the same object. How to achieve this?

The metaclass will create the _member_map_ attribute for the enumeration class to store the mapping relationship between member names and members. If it is found that the value of the created member is already in the mapping relationship, the object in the mapping table will be used. Replace:

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
            ...
Copy after login
From a code point of view, even if the member values ​​are the same, objects will still be created for them all first, but the objects created later will be garbage collected soon (I think there is optimization here spatial). By comparing with the _member_map_ mapping table, the member used to create the member value replaces the subsequent ones, but both member names will be in the _member_map_. For example, red and _red in the example are both in the dictionary, but they point to the same an object.

The attribute _member_names_ will only record the first one, which will be related to the iteration of the enumeration.

Members can be obtained through member values

print(Color['red'])  # Color.red  通过成员名来获取成员
print(Color(1))      # Color.red  通过成员值来获取成员
Copy after login
The members in the enumeration class are singleton mode, and the values ​​​​are also maintained in the enumeration class created by the metaclass Mapping relationship to members_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
            ...
Copy after login
Then return the singleton in Enum’s __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__))
Copy after login

Iteratively traverse the members

The enumeration class supports iterative traversal of members in the defined order. If there are members with duplicate values, only the first duplicate member is obtained. For duplicate member values, only the first member is obtained, and 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_)
Copy after login

Summary

Implementation of the core features of the enum module This is the idea, almost all achieved through meta-class black magic. The members cannot be compared in size but can be compared in equal value. There is no need to talk about this. This is actually inherited from object. It has "features" without doing anything extra.

The above is the detailed content of Detailed analysis of the source code of the enum module in Python (code example). For more information, please follow other related articles on the PHP Chinese website!

Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

Video Face Swap

Video Face Swap

Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Is the vscode extension malicious? Is the vscode extension malicious? Apr 15, 2025 pm 07:57 PM

VS Code extensions pose malicious risks, such as hiding malicious code, exploiting vulnerabilities, and masturbating as legitimate extensions. Methods to identify malicious extensions include: checking publishers, reading comments, checking code, and installing with caution. Security measures also include: security awareness, good habits, regular updates and antivirus software.

Can vs code run in Windows 8 Can vs code run in Windows 8 Apr 15, 2025 pm 07:24 PM

VS Code can run on Windows 8, but the experience may not be great. First make sure the system has been updated to the latest patch, then download the VS Code installation package that matches the system architecture and install it as prompted. After installation, be aware that some extensions may be incompatible with Windows 8 and need to look for alternative extensions or use newer Windows systems in a virtual machine. Install the necessary extensions to check whether they work properly. Although VS Code is feasible on Windows 8, it is recommended to upgrade to a newer Windows system for a better development experience and security.

How to run programs in terminal vscode How to run programs in terminal vscode Apr 15, 2025 pm 06:42 PM

In VS Code, you can run the program in the terminal through the following steps: Prepare the code and open the integrated terminal to ensure that the code directory is consistent with the terminal working directory. Select the run command according to the programming language (such as Python's python your_file_name.py) to check whether it runs successfully and resolve errors. Use the debugger to improve debugging efficiency.

Can visual studio code be used in python Can visual studio code be used in python Apr 15, 2025 pm 08:18 PM

VS Code can be used to write Python and provides many features that make it an ideal tool for developing Python applications. It allows users to: install Python extensions to get functions such as code completion, syntax highlighting, and debugging. Use the debugger to track code step by step, find and fix errors. Integrate Git for version control. Use code formatting tools to maintain code consistency. Use the Linting tool to spot potential problems ahead of time.

Choosing Between PHP and Python: A Guide Choosing Between PHP and Python: A Guide Apr 18, 2025 am 12:24 AM

PHP is suitable for web development and rapid prototyping, and Python is suitable for data science and machine learning. 1.PHP is used for dynamic web development, with simple syntax and suitable for rapid development. 2. Python has concise syntax, is suitable for multiple fields, and has a strong library ecosystem.

Can vscode be used for mac Can vscode be used for mac Apr 15, 2025 pm 07:36 PM

VS Code is available on Mac. It has powerful extensions, Git integration, terminal and debugger, and also offers a wealth of setup options. However, for particularly large projects or highly professional development, VS Code may have performance or functional limitations.

Can visual studio code run python Can visual studio code run python Apr 15, 2025 pm 08:00 PM

VS Code not only can run Python, but also provides powerful functions, including: automatically identifying Python files after installing Python extensions, providing functions such as code completion, syntax highlighting, and debugging. Relying on the installed Python environment, extensions act as bridge connection editing and Python environment. The debugging functions include setting breakpoints, step-by-step debugging, viewing variable values, and improving debugging efficiency. The integrated terminal supports running complex commands such as unit testing and package management. Supports extended configuration and enhances features such as code formatting, analysis and version control.

Golang vs. Python: Concurrency and Multithreading Golang vs. Python: Concurrency and Multithreading Apr 17, 2025 am 12:20 AM

Golang is more suitable for high concurrency tasks, while Python has more advantages in flexibility. 1.Golang efficiently handles concurrency through goroutine and channel. 2. Python relies on threading and asyncio, which is affected by GIL, but provides multiple concurrency methods. The choice should be based on specific needs.

See all articles