引言
Python中的魔法方法(Magic Methods),也被称为特殊方法(Special Methods)或双下划线方法(Dunder Methods),是Python面向对象编程的核心特性之一。这些以双下划线开头和结尾的方法允许我们自定义类的行为,使其能够与Python的内置功能深度整合。
本文将全面介绍Python中的所有魔法方法,按照功能分类进行详细讲解,并提供丰富的示例代码帮助理解。
目录
对象生命周期管理
__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__: 只有当属性不存在时才会被调用
调用顺序
- 首先调用
__getattribute__ - 如果
__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__ |
|---|---|---|
| 调用时机 | 属性不存在时 | 每次属性访问时 |
| 性能 | 更好 | 较差 |
| 使用难度 | 简单 | 复杂,容易出错 |
| 推荐程度 | 推荐 | 谨慎使用 |
| 适用场景 | 动态属性、默认值 | 访问控制、日志记录 |
最佳实践:
- 优先使用
__getattr__,除非需要拦截所有属性访问 - 在
__getattribute__中必须调用super().__getattribute__(name)避免无限递归 - 避免在
__getattr__中设置属性,避免在__getattribute__中访问self.__dict__ - 考虑性能影响,
__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的内置功能无缝集成。通过合理使用这些方法,我们可以:
- 创建直观的API:让对象支持自然语言般的操作
- 实现设计模式:如单例模式、工厂模式等
- 构建领域特定语言:创建表达力强的领域模型
- 优化性能:通过自定义容器和迭代器提高效率
- 增强可读性:让代码更加Pythonic
在使用魔法方法时,需要注意:
- 保持一致性:相关的方法应该协同工作
- 考虑性能:避免不必要的计算和内存分配
- 处理异常:合理处理各种边界情况
- 文档化:清楚地说明自定义行为
掌握这些魔法方法,将大大提升你的Python编程能力,让你能够编写出更加优雅和强大的代码。
参考资源
本文涵盖了Python中所有主要的魔法方法,从基础的对象生命周期管理到高级的协程支持。通过丰富的示例代码,希望能够帮助读者深入理解并掌握这些强大的特性。