目 录CONTENT

文章目录

【Python】魔法方法详解:从基础到高级的完整指南

EulerBlind
2025-10-11 / 0 评论 / 1 点赞 / 3 阅读 / 0 字

引言

Python中的魔法方法(Magic Methods),也被称为特殊方法(Special Methods)或双下划线方法(Dunder Methods),是Python面向对象编程的核心特性之一。这些以双下划线开头和结尾的方法允许我们自定义类的行为,使其能够与Python的内置功能深度整合。

本文将全面介绍Python中的所有魔法方法,按照功能分类进行详细讲解,并提供丰富的示例代码帮助理解。

目录

  1. 对象生命周期管理
  2. 字符串表示
  3. 算术运算符重载
  4. 比较运算符重载
  5. 容器类型方法
  6. 属性访问控制
  7. 上下文管理器
  8. 可调用对象
  9. 描述符协议
  10. 数值类型方法
  11. 类型转换方法
  12. 迭代器协议
  13. 协程支持
  14. 其他重要方法

对象生命周期管理

__new__(cls, ...)

__new__ 是对象创建的第一步,负责创建实例。它返回一个实例,然后 __init__ 被调用来初始化这个实例。

class Singleton:
    _instance = None
    
    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    
    def __init__(self, value):
        self.value = value

# 测试单例模式
s1 = Singleton(1)
s2 = Singleton(2)
print(s1 is s2)  # True
print(s1.value, s2.value)  # 2 2

__init__(self, ...)

__init__ 是构造方法,在对象创建后调用,用于初始化对象属性。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def __str__(self):
        return f"Person(name='{self.name}', age={self.age})"

person = Person("Alice", 30)
print(person)  # Person(name='Alice', age=30)

__del__(self)

__del__ 是析构方法,在对象被垃圾回收时调用。注意:不建议依赖此方法进行重要资源清理。

class Resource:
    def __init__(self, name):
        self.name = name
        print(f"Resource {self.name} created")
    
    def __del__(self):
        print(f"Resource {self.name} destroyed")

# 测试析构方法
r = Resource("test")
del r  # 输出: Resource test destroyed

字符串表示

__str__(self)

定义对象的用户友好字符串表示,str(obj)print(obj) 时调用。

__repr__(self)

定义对象的官方字符串表示,repr(obj) 时调用,通常用于调试。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __str__(self):
        return f"Point({self.x}, {self.y})"
    
    def __repr__(self):
        return f"Point(x={self.x}, y={self.y})"

p = Point(3, 4)
print(str(p))   # Point(3, 4)
print(repr(p))  # Point(x=3, y=4)

__format__(self, format_spec)

定义对象在格式化字符串中的行为。

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
    
    def __format__(self, format_spec):
        if format_spec == 'f':
            return f"{self.celsius * 9/5 + 32:.1f}°F"
        elif format_spec == 'c':
            return f"{self.celsius:.1f}°C"
        else:
            return str(self)

temp = Temperature(25)
print(f"{temp:f}")  # 77.0°F
print(f"{temp:c}")  # 25.0°C

算术运算符重载

基本算术运算

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    def __truediv__(self, scalar):
        return Vector(self.x / scalar, self.y / scalar)
    
    def __floordiv__(self, scalar):
        return Vector(self.x // scalar, self.y // scalar)
    
    def __mod__(self, scalar):
        return Vector(self.x % scalar, self.y % scalar)
    
    def __pow__(self, power):
        return Vector(self.x ** power, self.y ** power)
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

# 测试算术运算
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2)  # Vector(4, 6)
print(v1 * 2)   # Vector(6, 8)
print(v1 ** 2)  # Vector(9, 16)

反向算术运算

当左操作数不支持相应运算时,Python会尝试右操作数的反向方法。

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    def __rmul__(self, scalar):
        return self.__mul__(scalar)
    
    def __str__(self):
        return f"Vector({self.x}, {self.y})"

v = Vector(3, 4)
print(2 * v)  # Vector(6, 8) - 调用 __rmul__

就地运算

class Counter:
    def __init__(self, value=0):
        self.value = value
    
    def __iadd__(self, other):
        self.value += other
        return self
    
    def __isub__(self, other):
        self.value -= other
        return self
    
    def __imul__(self, other):
        self.value *= other
        return self
    
    def __str__(self):
        return f"Counter({self.value})"

c = Counter(10)
c += 5
print(c)  # Counter(15)

比较运算符重载

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score
    
    def __eq__(self, other):
        return self.score == other.score
    
    def __ne__(self, other):
        return not self.__eq__(other)
    
    def __lt__(self, other):
        return self.score < other.score
    
    def __le__(self, other):
        return self.score <= other.score
    
    def __gt__(self, other):
        return self.score > other.score
    
    def __ge__(self, other):
        return self.score >= other.score
    
    def __str__(self):
        return f"Student({self.name}, {self.score})"

# 测试比较运算
s1 = Student("Alice", 85)
s2 = Student("Bob", 90)
print(s1 < s2)   # True
print(s1 == s2)  # False

容器类型方法

基本容器操作

class MyList:
    def __init__(self, items=None):
        self.items = items or []
    
    def __len__(self):
        return len(self.items)
    
    def __getitem__(self, index):
        return self.items[index]
    
    def __setitem__(self, index, value):
        self.items[index] = value
    
    def __delitem__(self, index):
        del self.items[index]
    
    def __contains__(self, item):
        return item in self.items
    
    def __str__(self):
        return str(self.items)

# 测试容器操作
ml = MyList([1, 2, 3, 4, 5])
print(len(ml))      # 5
print(ml[2])        # 3
print(3 in ml)      # True
ml[0] = 10
print(ml)           # [10, 2, 3, 4, 5]

迭代器支持

class Range:
    def __init__(self, start, stop, step=1):
        self.start = start
        self.stop = stop
        self.step = step
    
    def __iter__(self):
        return RangeIterator(self)
    
    def __len__(self):
        return max(0, (self.stop - self.start + self.step - 1) // self.step)

class RangeIterator:
    def __init__(self, range_obj):
        self.range_obj = range_obj
        self.current = range_obj.start
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.current >= self.range_obj.stop:
            raise StopIteration
        value = self.current
        self.current += self.range_obj.step
        return value

# 测试迭代器
r = Range(1, 10, 2)
for i in r:
    print(i, end=' ')  # 1 3 5 7 9
print()

属性访问控制

基本属性访问

class AttributeDemo:
    def __init__(self):
        self.public_attr = "public"
        self._protected_attr = "protected"
        self.__private_attr = "private"
    
    def __getattr__(self, name):
        return f"Attribute '{name}' not found"
    
    def __setattr__(self, name, value):
        if name.startswith('_'):
            print(f"Setting protected/private attribute: {name}")
        super().__setattr__(name, value)
    
    def __delattr__(self, name):
        if name.startswith('__'):
            raise AttributeError(f"Cannot delete private attribute: {name}")
        super().__delattr__(name)

# 测试属性访问
obj = AttributeDemo()
print(obj.nonexistent)  # Attribute 'nonexistent' not found

__getattribute____getattr__ 的区别

这两个方法都用于属性访问,但它们有重要的区别:

调用时机

  • __getattribute__: 每次访问属性时都会被调用,无论属性是否存在
  • __getattr__: 只有当属性不存在时才会被调用

调用顺序

  1. 首先调用 __getattribute__
  2. 如果 __getattribute__ 抛出 AttributeError,才会调用 __getattr__

示例对比

class AttributeComparison:
    def __init__(self):
        self.existing_attr = "I exist"
    
    def __getattribute__(self, name):
        print(f"__getattribute__ called for: {name}")
        # 必须调用父类方法,否则会无限递归
        return super().__getattribute__(name)
    
    def __getattr__(self, name):
        print(f"__getattr__ called for: {name}")
        return f"Dynamic attribute: {name}"

# 测试区别
obj = AttributeComparison()

print("=== 访问存在的属性 ===")
print(obj.existing_attr)
# 输出:
# __getattribute__ called for: existing_attr
# I exist

print("\n=== 访问不存在的属性 ===")
print(obj.nonexistent_attr)
# 输出:
# __getattribute__ called for: nonexistent_attr
# __getattr__ called for: nonexistent_attr
# Dynamic attribute: nonexistent_attr

实际应用场景

使用 __getattr__ 的场景(推荐):

class DynamicAttributes:
    def __init__(self):
        self.data = {}
    
    def __getattr__(self, name):
        # 只在属性不存在时调用,性能更好
        if name.startswith('get_'):
            key = name[4:]  # 去掉 'get_' 前缀
            return lambda: self.data.get(key, f"No data for {key}")
        return f"Unknown attribute: {name}"
    
    def __setattr__(self, name, value):
        if name == 'data':
            super().__setattr__(name, value)
        else:
            self.data[name] = value

# 测试动态属性
obj = DynamicAttributes()
obj.name = "Alice"
obj.age = 30

print(obj.get_name())  # Alice
print(obj.get_age())   # 30
print(obj.get_city())  # No data for city
print(obj.unknown)     # Unknown attribute: unknown

使用 __getattribute__ 的场景(谨慎使用):

class AccessLogger:
    def __init__(self):
        self.public_data = "public"
        self._private_data = "private"
    
    def __getattribute__(self, name):
        # 记录所有属性访问
        print(f"Accessing: {name}")
        
        # 阻止访问私有属性
        if name.startswith('_') and name != '__class__':
            raise AttributeError(f"Access denied to private attribute: {name}")
        
        return super().__getattribute__(name)
    
    def public_method(self):
        return "This is a public method"

# 测试访问控制
obj = AccessLogger()
print(obj.public_data)     # Accessing: public_data -> public
print(obj.public_method()) # Accessing: public_method -> This is a public method
# print(obj._private_data) # 会抛出 AttributeError

常见陷阱和注意事项

class BadExample:
    def __init__(self):
        self.value = 42
    
    def __getattribute__(self, name):
        # 错误:会导致无限递归
        # return self.__dict__[name]  # 这会再次调用 __getattribute__
        
        # 正确:使用 super() 或 object 类
        return super().__getattribute__(name)
    
    def __getattr__(self, name):
        # 错误:不应该在这里设置属性,会导致无限递归
        # self.new_attr = "value"  # 这会调用 __setattr__
        
        # 正确:直接返回默认值
        return f"Default value for {name}"

# 测试
obj = BadExample()
print(obj.value)      # 42
print(obj.missing)    # Default value for missing

性能考虑

import time

class PerformanceTest:
    def __init__(self):
        self.data = {f"attr_{i}": i for i in range(1000)}
    
    def __getattr__(self, name):
        # 只在需要时调用,性能更好
        return self.data.get(name, f"Not found: {name}")

class SlowPerformanceTest:
    def __init__(self):
        self.data = {f"attr_{i}": i for i in range(1000)}
    
    def __getattribute__(self, name):
        # 每次访问都调用,性能较差
        if name == 'data':
            return super().__getattribute__(name)
        return self.data.get(name, f"Not found: {name}")

# 性能测试
def test_performance(obj, iterations=10000):
    start = time.time()
    for _ in range(iterations):
        _ = obj.attr_500
    end = time.time()
    return end - start

fast_obj = PerformanceTest()
slow_obj = SlowPerformanceTest()

print(f"__getattr__: {test_performance(fast_obj):.4f}s")
print(f"__getattribute__: {test_performance(slow_obj):.4f}s")

总结

特性__getattr____getattribute__
调用时机属性不存在时每次属性访问时
性能更好较差
使用难度简单复杂,容易出错
推荐程度推荐谨慎使用
适用场景动态属性、默认值访问控制、日志记录

最佳实践

  1. 优先使用 __getattr__,除非需要拦截所有属性访问
  2. __getattribute__ 中必须调用 super().__getattribute__(name) 避免无限递归
  3. 避免在 __getattr__ 中设置属性,避免在 __getattribute__ 中访问 self.__dict__
  4. 考虑性能影响,__getattribute__ 会影响所有属性访问的性能

上下文管理器

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None
    
    def __enter__(self):
        print(f"Opening file: {self.filename}")
        self.file = open(self.filename, self.mode)
        return self.file
    
    def __exit__(self, exc_type, exc_value, traceback):
        print(f"Closing file: {self.filename}")
        if self.file:
            self.file.close()
        if exc_type:
            print(f"Exception occurred: {exc_value}")
        return False  # 不抑制异常

# 测试上下文管理器
with FileManager("test.txt", "w") as f:
    f.write("Hello, World!")

可调用对象

class Multiplier:
    def __init__(self, factor):
        self.factor = factor
    
    def __call__(self, number):
        return number * self.factor
    
    def __str__(self):
        return f"Multiplier({self.factor})"

# 测试可调用对象
multiply_by_3 = Multiplier(3)
print(multiply_by_3(5))  # 15
print(multiply_by_3(10)) # 30

描述符协议

class Descriptor:
    def __init__(self, name):
        self.name = name
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name, None)
    
    def __set__(self, instance, value):
        instance.__dict__[self.name] = value
    
    def __delete__(self, instance):
        del instance.__dict__[self.name]

class Person:
    name = Descriptor('name')
    age = Descriptor('age')
    
    def __init__(self, name, age):
        self.name = name
        self.age = age

# 测试描述符
person = Person("Alice", 30)
print(person.name)  # Alice
person.name = "Bob"
print(person.name)  # Bob

数值类型方法

class Fraction:
    def __init__(self, numerator, denominator=1):
        self.numerator = numerator
        self.denominator = denominator
    
    def __abs__(self):
        return Fraction(abs(self.numerator), self.denominator)
    
    def __neg__(self):
        return Fraction(-self.numerator, self.denominator)
    
    def __pos__(self):
        return Fraction(self.numerator, self.denominator)
    
    def __invert__(self):
        return Fraction(self.denominator, self.numerator)
    
    def __int__(self):
        return self.numerator // self.denominator
    
    def __float__(self):
        return self.numerator / self.denominator
    
    def __complex__(self):
        return complex(float(self))
    
    def __str__(self):
        return f"{self.numerator}/{self.denominator}"

# 测试数值方法
f = Fraction(3, 4)
print(abs(f))      # 3/4
print(-f)          # -3/4
print(int(f))      # 0
print(float(f))    # 0.75

类型转换方法

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius
    
    def __int__(self):
        return int(self.celsius)
    
    def __float__(self):
        return float(self.celsius)
    
    def __bool__(self):
        return self.celsius != 0
    
    def __bytes__(self):
        return str(self.celsius).encode('utf-8')
    
    def __str__(self):
        return f"{self.celsius}°C"

# 测试类型转换
temp = Temperature(25.5)
print(int(temp))    # 25
print(float(temp))  # 25.5
print(bool(temp))   # True
print(bytes(temp))  # b'25.5'

迭代器协议

class Fibonacci:
    def __init__(self, max_count):
        self.max_count = max_count
        self.count = 0
        self.a, self.b = 0, 1
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.count >= self.max_count:
            raise StopIteration
        self.count += 1
        if self.count == 1:
            return self.a
        elif self.count == 2:
            return self.b
        else:
            self.a, self.b = self.b, self.a + self.b
            return self.b

# 测试斐波那契迭代器
fib = Fibonacci(10)
for num in fib:
    print(num, end=' ')  # 0 1 1 2 3 5 8 13 21 34
print()

协程支持

import asyncio

class AsyncCounter:
    def __init__(self, max_count):
        self.max_count = max_count
        self.count = 0
    
    def __aiter__(self):
        return self
    
    async def __anext__(self):
        if self.count >= self.max_count:
            raise StopAsyncIteration
        await asyncio.sleep(0.1)  # 模拟异步操作
        self.count += 1
        return self.count

# 测试异步迭代器
async def main():
    async for num in AsyncCounter(5):
        print(f"Async count: {num}")

# asyncio.run(main())

其他重要方法

__hash____eq__

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __eq__(self, other):
        if not isinstance(other, Point):
            return False
        return self.x == other.x and self.y == other.y
    
    def __hash__(self):
        return hash((self.x, self.y))
    
    def __str__(self):
        return f"Point({self.x}, {self.y})"

# 测试哈希和相等
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(2, 3)

print(p1 == p2)  # True
print(p1 == p3)  # False
print(hash(p1) == hash(p2))  # True

# 可以作为字典键
points = {p1: "first", p3: "third"}
print(points[p2])  # first

__bool__

class NonEmptyList:
    def __init__(self, items=None):
        self.items = items or []
    
    def __bool__(self):
        return len(self.items) > 0
    
    def __len__(self):
        return len(self.items)
    
    def __str__(self):
        return str(self.items)

# 测试布尔值
empty_list = NonEmptyList([])
non_empty_list = NonEmptyList([1, 2, 3])

print(bool(empty_list))     # False
print(bool(non_empty_list)) # True

if empty_list:
    print("List is not empty")
else:
    print("List is empty")  # 输出这个

__dir__

class CustomDir:
    def __init__(self):
        self.public_attr = "public"
        self._protected_attr = "protected"
        self.__private_attr = "private"
    
    def public_method(self):
        return "public method"
    
    def _protected_method(self):
        return "protected method"
    
    def __private_method(self):
        return "private method"
    
    def __dir__(self):
        # 自定义 dir() 返回的属性列表
        return ['public_attr', 'public_method', 'custom_attribute']

# 测试自定义 dir
obj = CustomDir()
print(dir(obj))  # ['custom_attribute', 'public_attr', 'public_method']

实际应用示例

实现一个简单的数据库ORM

class Field:
    def __init__(self, field_type, nullable=True):
        self.field_type = field_type
        self.nullable = nullable
    
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__.get(self.name)
    
    def __set__(self, instance, value):
        if not self.nullable and value is None:
            raise ValueError(f"Field {self.name} cannot be None")
        instance.__dict__[self.name] = value

class IntegerField(Field):
    def __init__(self, nullable=True):
        super().__init__(int, nullable)
    
    def __set__(self, instance, value):
        if value is not None and not isinstance(value, int):
            raise ValueError("Value must be an integer")
        super().__set__(instance, value)

class StringField(Field):
    def __init__(self, max_length=None, nullable=True):
        super().__init__(str, nullable)
        self.max_length = max_length
    
    def __set__(self, instance, value):
        if value is not None:
            if not isinstance(value, str):
                raise ValueError("Value must be a string")
            if self.max_length and len(value) > self.max_length:
                raise ValueError(f"String too long (max {self.max_length})")
        super().__set__(instance, value)

class Model:
    def __init__(self, **kwargs):
        for field_name, field in self.__class__.__dict__.items():
            if isinstance(field, Field):
                field.name = field_name
                if field_name in kwargs:
                    setattr(self, field_name, kwargs[field_name])
    
    def __str__(self):
        attrs = []
        for field_name, field in self.__class__.__dict__.items():
            if isinstance(field, Field):
                value = getattr(self, field_name, None)
                attrs.append(f"{field_name}={value}")
        return f"{self.__class__.__name__}({', '.join(attrs)})"

class User(Model):
    id = IntegerField(nullable=False)
    name = StringField(max_length=100)
    email = StringField(max_length=255)

# 测试ORM
user = User(id=1, name="Alice", email="[email protected]")
print(user)  # User(id=1, name=Alice, [email protected])

最佳实践和注意事项

1. 魔法方法的性能考虑

class OptimizedList:
    def __init__(self, items=None):
        self._items = items or []
    
    def __len__(self):
        return len(self._items)
    
    def __getitem__(self, index):
        return self._items[index]
    
    def __setitem__(self, index, value):
        self._items[index] = value
    
    def __iter__(self):
        # 使用生成器表达式提高性能
        return (item for item in self._items)
    
    def append(self, item):
        self._items.append(item)
    
    def __str__(self):
        return str(self._items)

2. 避免常见的陷阱

class SafeClass:
    def __init__(self, value):
        self.value = value
    
    def __setattr__(self, name, value):
        # 避免无限递归
        if name == 'value':
            super().__setattr__(name, value)
        else:
            raise AttributeError(f"Cannot set attribute {name}")
    
    def __getattr__(self, name):
        # 避免无限递归
        if name.startswith('__'):
            raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'")
        return f"Dynamic attribute: {name}"

# 测试安全类
obj = SafeClass(42)
print(obj.value)        # 42
print(obj.dynamic)      # Dynamic attribute: dynamic

3. 魔法方法的组合使用

class SmartNumber:
    def __init__(self, value):
        self.value = value
    
    def __add__(self, other):
        if isinstance(other, SmartNumber):
            return SmartNumber(self.value + other.value)
        return SmartNumber(self.value + other)
    
    def __radd__(self, other):
        return self.__add__(other)
    
    def __iadd__(self, other):
        if isinstance(other, SmartNumber):
            self.value += other.value
        else:
            self.value += other
        return self
    
    def __str__(self):
        return str(self.value)
    
    def __repr__(self):
        return f"SmartNumber({self.value})"
    
    def __bool__(self):
        return self.value != 0
    
    def __int__(self):
        return int(self.value)
    
    def __float__(self):
        return float(self.value)

# 测试智能数字
num1 = SmartNumber(10)
num2 = SmartNumber(5)
print(num1 + num2)      # 15
print(num1 + 3)         # 13
print(3 + num1)         # 13
num1 += 2
print(num1)             # 12
print(bool(num1))       # True
print(int(num1))        # 12

总结

Python的魔法方法提供了强大的机制来自定义类的行为,使其能够与Python的内置功能无缝集成。通过合理使用这些方法,我们可以:

  1. 创建直观的API:让对象支持自然语言般的操作
  2. 实现设计模式:如单例模式、工厂模式等
  3. 构建领域特定语言:创建表达力强的领域模型
  4. 优化性能:通过自定义容器和迭代器提高效率
  5. 增强可读性:让代码更加Pythonic

在使用魔法方法时,需要注意:

  • 保持一致性:相关的方法应该协同工作
  • 考虑性能:避免不必要的计算和内存分配
  • 处理异常:合理处理各种边界情况
  • 文档化:清楚地说明自定义行为

掌握这些魔法方法,将大大提升你的Python编程能力,让你能够编写出更加优雅和强大的代码。

参考资源


本文涵盖了Python中所有主要的魔法方法,从基础的对象生命周期管理到高级的协程支持。通过丰富的示例代码,希望能够帮助读者深入理解并掌握这些强大的特性。

1
博主关闭了所有页面的评论