Custom Classes: A Complete Guide to Python Magic Methods

In Python, double underscore__Beginning and ending methods (e.g.__init____str__) are called magic methods (or special methods). They are the core of Python class customization, allowing our custom types to behave as naturally as built-in types (such as lists and strings). This article will sort out the usage of common magic methods and use examples to help you master the skills of class customization.

1. Object representation method: let the instance "speak"

When we print an object or view it in an interactive environment, Python calls special methods to generate a string representation. The most commonly used is__str__and__repr__

__str__and__repr__

class Student:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        # 面向用户的友好表示:print() 或 str() 时调用
        return f"学生对象(姓名:{self.name})"

    def __repr__(self):
        # 面向开发者的精确表示:交互环境直接输入变量名时调用
        return f"Student(name={self.name!r})"

    # 若两者内容一致,可简写为:__repr__ = __str__

Usage Example:

s = Student("张三")
print(s)   # 调用 __str__:学生对象(姓名:张三)
s          # 调用 __repr__:Student(name='张三')

💡 Best Practices

  • __repr__Information about the reconstructed object should be included where possible (e.g.Student(name='张三')Can be copied and run directly);
  • If only one is implemented, give priority__repr__——__str__Will automatically fall back to when undefined__repr__

2. Iterator protocol: Make instances "loopable"

Want the object to beforLoop traversal needs to be implemented__iter__and__next__Two methods, this is the iterator protocol.

🔄 __iter__and__next__

Take the Fibonacci sequence generator as an example:

class Fibonacci:
    def __init__(self, max_num=100):
        self.a, self.b = 0, 1
        self.max_num = max_num

    def __iter__(self):
        # 迭代器协议要求 __iter__ 返回一个迭代器——这里实例本身就是迭代器
        return self

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b
        if self.a > self.max_num:
            # 超过上限时抛出异常,终止迭代
            raise StopIteration()
        return self.a

Usage Example:

for num in Fibonacci(20):
    print(num)  # 输出:1 1 2 3 5 8 13

3. Sequence protocol: Let the instance "support subscript access"

If you want to use the object like a list[]Access (including indexing and slicing) needs to be implemented__getitem__; If you want to support modifying or deleting elements, you can continue to implement__setitem__and__delitem__

🔢 __getitem__Example

Add subscript access functions to the Fibonacci class:

class IndexedFib:
    def __getitem__(self, n):
        if isinstance(n, int):                # 处理单个索引
            a, b = 1, 1
            for _ in range(n):
                a, b = b, a + b
            return a
        elif isinstance(n, slice):            # 处理切片
            start = n.start or 0
            stop = n.stop
            step = n.step or 1
            result = []
            a, b = 1, 1
            for i in range(stop):
                if i >= start and (i - start) % step == 0:
                    result.append(a)
                a, b = b, a + b
            return result
        else:
            raise TypeError("索引必须是整数或切片")

Usage Example:

fib = IndexedFib()
fib[5]          # 单个索引:8
fib[1:10:2]     # 切片:[1, 3, 8, 21, 55]

You can also add__len__methods to supportlen()function:

def __len__(self):
    # 这里简单返回前100项的长度,实际可按需实现
    return 100

4. Attribute access control: dynamic processing of attributes

When accessing or setting a property of an object that does not exist, Python calls__getattr__and__setattr__, can be used to implement dynamic attribute management.

🧩 __getattr__and__setattr__

class DynamicAttrs:
    def __init__(self):
        # 用私有字典存储动态属性
        self._data = {}

    def __getattr__(self, name):
        # 访问不存在的属性时调用
        if name in self._data:
            return self._data[name]
        raise AttributeError(f"属性 {name} 不存在")

    def __setattr__(self, name, value):
        # 设置属性时调用(注意:所有属性赋值都会触发它!)
        if name == "_data":
            # 避免递归:直接调用父类的 __setattr__
            super().__setattr__(name, value)
        else:
            self._data[name] = value

Usage Example:

obj = DynamicAttrs()
obj.age = 18      # 调用 __setattr__,存入 _data
obj.age           # 调用 __getattr__,返回 18

5. Callable objects: Make instances "like functions"

accomplish__call__After the method, instances of the class can be called like functions, which is very useful in "functions" that need to save state.

📞 __call__Example

Make a prefixed logger:

class PrefixedLogger:
    def __init__(self, prefix):
        self.prefix = prefix

    def __call__(self, message):
        print(f"[{self.prefix}] {message}")

Usage Example:

debug_log = PrefixedLogger("DEBUG")
debug_log("这是一条调试信息")   # 输出:[DEBUG] 这是一条调试信息

callable(debug_log)           # 返回 True

6. Context manager: let the instance "support the with statement"

withStatements can automatically manage resources (such as opening/closing files). For classes to support it, they need to be implemented__enter__and__exit__

🧹 __enter__and__exit__Example

Simulate a resource management class:

class Resource:
    def __enter__(self):
        print("获取资源")
        # 返回的对象会赋值给 with 后的 as 变量
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # exc_type/val/tb:若有异常,分别是异常类型、值、回溯;否则为 None
        print("释放资源")
        # 返回 False 表示不抑制异常(默认行为),返回 True 会吞掉异常
        return False

Usage Example:

with Resource() as res:
    print("使用资源中...")
# 输出:
# 获取资源
# 使用资源中...
# 释放资源

7. New practical magic methods in Python 3.x

🔡 __bytes__: Define the behavior of converting byte sequences

class Student:
    def __init__(self, name):
        self.name = name
    def __bytes__(self):
        return self.name.encode("utf-8")

s = Student("李四")
bytes(s)  # 返回 b'李四'

🎨 __format__: Custom formatted output

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
    def __format__(self, format_spec):
        if format_spec == "score":
            return f"{self.name}{self.score}分"
        return f"{self.name}"

s = Student("王五", 90)
f"{s:score}"  # 返回 "王五:90分"

🧬 __class_getitem__(3.7+): Support generic type hints

Simply put, it allows a class likelist[int]The same is used for type annotations, such as:

class MyContainer:
    def __class_getitem__(cls, item_type):
        return f"{cls.__name__}[{item_type.__name__}]"

MyContainer[str]  # 返回 "MyContainer[str]"

Best Practices

  1. Maintain consistency: The behavior of magic methods must be aligned with built-in types (such as__len__Must return a non-negative integer);
  2. ** Priority implementation__repr__**: It is the "official" representation of the object and can override__str__the absence of;
  3. Avoid__getattribute__Abuse: All attribute access will trigger it, which can easily lead to recursion and should not be used unless necessary;
  4. Performance first: Magic methods are often called implicitly (such as in loops__next__), to ensure high efficiency.

Summarize

Python's magic methods are like a set of "hooks" that allow us to seamlessly customize the behavior of a class. From string representation to iteration, from attribute management to context management, mastering these methods will help you write more concise and Python-style code. If you want to know all the magic methods, you can refer to 官方文档.