Python 魔术方法详解:掌握面向对象编程的精髓
魔术方法(Magic Methods),也称为双下划线方法或特殊方法,是Python中一种强大的特性。它们允许我们在类中定义特定行为的实现,让我们的对象能够支持Python内置的操作和语法。
什么是魔术方法?
魔术方法是以双下划线开头和结尾的方法,例如 __init__、__str__ 等。这些方法在特定情况下会被Python自动调用,让我们能够自定义类的行为。
常用的魔术方法分类
1. 构造和初始化方法
__new__(cls, ...)
- 作用 :创建类的新实例,在
__init__之前调用 - 返回值:新创建的实例
python
class Singleton:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True - 单例模式
__init__(self, ...)
- 作用:对象初始化方法,最常用的魔术方法
- 返回值:None
python
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person("Alice", 25)
__del__(self)
- 作用:对象被销毁时调用(析构函数)
python
class Resource:
def __init__(self, name):
self.name = name
print(f"资源 {self.name} 已创建")
def __del__(self):
print(f"资源 {self.name} 已释放")
res = Resource("数据库连接")
del res # 输出: 资源 数据库连接 已释放
2. 字符串表示方法
__str__(self)
- 作用 :定义对象的字符串表示,用于
str()和print()
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(p) # Point(3, 4)
__repr__(self)
- 作用 :定义对象的官方字符串表示,用于
repr()和调试
python
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(repr(p)) # Point(3, 4)
3. 比较运算符方法
python
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __eq__(self, other):
return self.score == other.score
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
s1 = Student("Alice", 85)
s2 = Student("Bob", 90)
print(s1 < s2) # True
print(s1 == s2) # False
4. 算术运算符方法
python
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 __matmul__(self, other):
"""矩阵乘法 @ 运算符 (Python 3.5+)"""
if isinstance(other, Vector):
# 点积
return self.x * other.x + self.y * other.y
elif isinstance(other, (int, float)):
# 数乘
return Vector(self.x * other, self.y * other)
else:
raise TypeError("不支持的操作数类型")
def __str__(self):
return f"Vector({self.x}, {self.y})"
v1 = Vector(2, 3)
v2 = Vector(1, 1)
print(v1 + v2) # Vector(3, 4)
print(v1 * 3) # Vector(6, 9)
print(v1 @ v2) # 5 (点积结果)
5. 容器类型方法
实现类似列表的行为
python
class Playlist:
def __init__(self, songs=None):
self.songs = songs or []
def __len__(self):
return len(self.songs)
def __getitem__(self, index):
return self.songs[index]
def __setitem__(self, index, value):
self.songs[index] = value
def __delitem__(self, index):
del self.songs[index]
def __contains__(self, item):
return item in self.songs
def append(self, song):
self.songs.append(song)
playlist = Playlist(["Song1", "Song2", "Song3"])
print(len(playlist)) # 3
print(playlist[1]) # Song2
print("Song1" in playlist) # True
__missing__(self, key)
- 作用:在字典子类中,当访问不存在的键时调用
- 应用场景:实现默认值字典、缓存字典等
python
class DefaultDict(dict):
def __init__(self, default_factory, *args, **kwargs):
super().__init__(*args, **kwargs)
self.default_factory = default_factory
def __missing__(self, key):
"""当键不存在时调用"""
if self.default_factory is None:
raise KeyError(key)
value = self.default_factory()
self[key] = value
return value
# 使用示例
counter = DefaultDict(int)
counter['a'] += 1
counter['b'] += 2
print(counter) # {'a': 1, 'b': 2}
list_dict = DefaultDict(list)
list_dict['numbers'].append(1)
list_dict['numbers'].append(2)
print(list_dict) # {'numbers': [1, 2]}
class CacheDict(dict):
def __missing__(self, key):
"""模拟缓存未命中"""
print(f"缓存未命中: {key}")
value = f"computed_value_for_{key}"
self[key] = value
return value
cache = CacheDict()
print(cache['name']) # 缓存未命中: name → computed_value_for_name
print(cache['name']) # computed_value_for_name (直接从缓存获取)
6. 可调用对象方法
__call__(self, ...)
- 作用:让对象可以像函数一样被调用
python
class Multiplier:
def __init__(self, factor):
self.factor = factor
def __call__(self, x):
return x * self.factor
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
7. 上下文管理方法
__enter__(self) 和 __exit__(self, ...)
- 作用 :实现上下文管理器,用于
with语句
python
class Timer:
def __init__(self, name):
self.name = name
def __enter__(self):
import time
self.start = time.time()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
import time
self.end = time.time()
print(f"{self.name} 耗时: {self.end - self.start:.2f}秒")
with Timer("计算任务"):
# 执行一些操作
sum(range(1000000))
# 输出: 计算任务 耗时: 0.03秒
8. 属性访问方法
python
class SmartDict:
def __init__(self, data=None):
self._data = data or {}
def __getattr__(self, name):
"""访问不存在的属性时调用"""
if name in self._data:
return self._data[name]
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
def __setattr__(self, name, value):
"""设置属性时调用"""
if name == '_data':
super().__setattr__(name, value)
else:
self._data[name] = value
def __getitem__(self, key):
"""支持下标访问"""
return self._data[key]
def __setitem__(self, key, value):
"""支持下标赋值"""
self._data[key] = value
obj = SmartDict()
obj.name = "Alice"
obj["age"] = 25
print(obj.name) # Alice
print(obj.age) # 25
9. 数值转换方法
__index__(self)
- 作用:将对象转换为整数索引,用于切片操作
- 应用场景:自定义数值类型在切片中的使用
python
class BinaryNumber:
"""二进制数类"""
def __init__(self, value):
self.value = value
def __index__(self):
"""将对象转换为整数索引"""
return int(str(self.value), 2)
def __str__(self):
return f"Binary({self.value})"
def __repr__(self):
return f"BinaryNumber('{self.value}')"
# 使用示例
binary = BinaryNumber('1101') # 二进制 1101 = 十进制 13
print(binary) # Binary(1101)
# 在切片中使用
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
print(numbers[binary]) # 13 - 相当于 numbers[13]
# 在 bin(), hex(), oct() 中使用
print(bin(binary)) # 0b1101
print(hex(binary)) # 0xd
print(oct(binary)) # 0o15
class EnumIndex:
"""枚举索引示例"""
def __init__(self, name, index):
self.name = name
self._index = index
def __index__(self):
return self._index
def __str__(self):
return self.name
MONDAY = EnumIndex("Monday", 0)
TUESDAY = EnumIndex("Tuesday", 1)
days = ["Monday", "Tuesday", "Wednesday"]
print(days[MONDAY]) # Monday
print(days[TUESDAY]) # Tuesday
10. 内存优化方法
__slots__
- 作用:限制类实例的属性,节省内存
- 优点:减少内存占用,提高属性访问速度
- 缺点:不能动态添加新属性
python
class RegularPerson:
"""普通类 - 使用 __dict__ 存储属性"""
def __init__(self, name, age):
self.name = name
self.age = age
class SlotsPerson:
"""使用 __slots__ 的类"""
__slots__ = ['name', 'age']
def __init__(self, name, age):
self.name = name
self.age = age
# 内存占用比较
import sys
regular = RegularPerson("Alice", 25)
slots = SlotsPerson("Bob", 30)
print(f"RegularPerson 实例大小: {sys.getsizeof(regular)} bytes")
print(f"SlotsPerson 实例大小: {sys.getsizeof(slots)} bytes")
print(f"RegularPerson dict 大小: {sys.getsizeof(regular.__dict__)} bytes")
# 动态添加属性
regular.email = "alice@example.com" # 正常工作
try:
slots.email = "bob@example.com" # 抛出 AttributeError
except AttributeError as e:
print(f"错误: {e}")
class AdvancedSlots:
"""高级 __slots__ 用法"""
__slots__ = ['x', 'y', '_z']
def __init__(self, x, y, z):
self.x = x
self.y = y
self._z = z # 私有属性也可以放在 slots 中
@property
def z(self):
return self._z
@z.setter
def z(self, value):
self._z = value
# 继承中的 slots 使用
class Base:
__slots__ = ['base_attr']
class Child(Base):
__slots__ = ['child_attr'] # 不会继承父类的 slots
def __init__(self):
self.base_attr = "base"
self.child_attr = "child"
# 使用 weakref 时需要显式声明
import weakref
class WithWeakref:
__slots__ = ['data', '__weakref__'] # 需要显式声明 __weakref__
def __init__(self, data):
self.data = data
实际应用示例:自定义分数类
python
class Fraction:
"""自定义分数类"""
def __init__(self, numerator, denominator=1):
if denominator == 0:
raise ValueError("分母不能为零")
# 约分
gcd = self._gcd(numerator, denominator)
self.numerator = numerator // gcd
self.denominator = denominator // gcd
@staticmethod
def _gcd(a, b):
"""计算最大公约数"""
while b:
a, b = b, a % b
return a
def __add__(self, other):
"""分数加法"""
if isinstance(other, int):
other = Fraction(other)
new_num = self.numerator * other.denominator + other.numerator * self.denominator
new_den = self.denominator * other.denominator
return Fraction(new_num, new_den)
def __sub__(self, other):
"""分数减法"""
if isinstance(other, int):
other = Fraction(other)
new_num = self.numerator * other.denominator - other.numerator * self.denominator
new_den = self.denominator * other.denominator
return Fraction(new_num, new_den)
def __mul__(self, other):
"""分数乘法"""
if isinstance(other, int):
other = Fraction(other)
new_num = self.numerator * other.numerator
new_den = self.denominator * other.denominator
return Fraction(new_num, new_den)
def __truediv__(self, other):
"""分数除法"""
if isinstance(other, int):
other = Fraction(other)
new_num = self.numerator * other.denominator
new_den = self.denominator * other.numerator
return Fraction(new_num, new_den)
def __eq__(self, other):
"""相等比较"""
if isinstance(other, int):
other = Fraction(other)
return (self.numerator == other.numerator and
self.denominator == other.denominator)
def __str__(self):
"""字符串表示"""
if self.denominator == 1:
return str(self.numerator)
return f"{self.numerator}/{self.denominator}"
def __repr__(self):
"""官方表示"""
return f"Fraction({self.numerator}, {self.denominator})"
# 使用示例
f1 = Fraction(1, 2)
f2 = Fraction(1, 3)
print(f1 + f2) # 5/6
print(f1 * f2) # 1/6
print(f1 == Fraction(2, 4)) # True
新增方法综合示例
python
class Matrix:
"""矩阵类,演示 __matmul__ 和 __slots__ 的使用"""
__slots__ = ['data', 'rows', 'cols']
def __init__(self, data):
self.data = data
self.rows = len(data)
self.cols = len(data[0]) if self.rows > 0 else 0
def __matmul__(self, other):
"""矩阵乘法 @ 运算符"""
if self.cols != other.rows:
raise ValueError("矩阵维度不匹配")
result = [
[sum(a * b for a, b in zip(row, col))
for col in zip(*other.data)]
for row in self.data
]
return Matrix(result)
def __str__(self):
return '\n'.join([' '.join(map(str, row)) for row in self.data])
# 使用示例
m1 = Matrix([[1, 2], [3, 4]])
m2 = Matrix([[5, 6], [7, 8]])
result = m1 @ m2 # 矩阵乘法
print(result)
最佳实践和注意事项
- 保持一致性:如果实现了比较方法,建议实现所有相关的比较方法
- 异常处理:在魔术方法中妥善处理异常情况
- 性能考虑:魔术方法会被频繁调用,注意性能影响
- 文档注释:为魔术方法提供清晰的文档说明
- 不要滥用:只在确实需要自定义行为时使用魔术方法
__slots__使用场景:当需要创建大量实例且内存占用重要时使用__missing__适用性:主要用于字典子类__index__注意事项:必须返回整数
总结
魔术方法是Python面向对象编程的核心特性之一,它们让我们的自定义类能够与Python语言本身无缝集成。通过合理使用魔术方法,我们可以创建出行为自然、易于使用的类,大大提升代码的可读性和可用性。
掌握魔术方法的关键在于理解它们被调用的时机和预期的行为。随着对这些方法的熟悉,你将能够编写出更加Pythonic的代码,充分发挥Python面向对象编程的威力。