Python魔法方法:解锁对象的超能力

想让你创建的对象像Python内置对象一样优雅、智能吗?那就必须掌握这些神奇的双下划线方法!

引言:为什么需要魔法方法?

想象一下,你创建了一个对象,它不能进行加法运算(obj1 + obj2),不能像字典一样使用索引(obj[key]),甚至打印出来都是一堆看不懂的内存地址…多么无趣的对象啊!

幸运的是,Python为我们提供了一套“魔法方法”——那些以双下划线开头和结尾的特殊方法。它们就像是你对象的“超能力”,让普通对象瞬间变身超级对象!

第一章:生死轮回——对象的创建与销毁

每个对象都有它的生命周期,从诞生到消亡,都有魔法方法相伴。

__new__:对象的“产房”

class UpperStr(str):
"""创建一个总是大写的字符串类"""
def __new__(cls, string):
# 在对象创建之前,将字符串转换为大写
string = string.upper()
# 调用父类的__new__来实际创建对象
return super().__new__(cls, string)

# 使用示例
normal_str = str("hello") # "hello"
magic_str = UpperStr("hello") # "HELLO" - 看,它一生下来就是大写的!

重要提醒__new__是静态方法(但不用加@staticmethod),它在__init__之前被调用,负责“制造”对象本身。

__del__:对象的“临终遗言”

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

def __del__(self):
print(f"『{self.name}』对象:我走了,正如我轻轻地来...")

# 测试一下
obj = MemorableObject("短暂的生命")
del obj # 输出:『短暂的生命』对象:我走了,正如我轻轻地来...

注意__del__就像对象的遗言,但说遗言的时间由垃圾回收器决定,可能不会立刻执行。不要用它来清理重要资源(比如关闭文件),用with语句或显式的close()方法更好。

第二章:算数超能力——让对象会数学

想让你的对象支持加减乘除吗?简单!

class Vector2D:
"""二维向量类"""
def __init__(self, x, y):
self.x = x
self.y = y

def __add__(self, other):
"""定义加法:向量相加"""
return Vector2D(self.x + other.x, self.y + other.y)

def __sub__(self, other):
"""定义减法"""
return Vector2D(self.x - other.x, self.y - other.y)

def __mul__(self, scalar):
"""定义乘法:向量乘以标量"""
return Vector2D(self.x * scalar, self.y * scalar)

def __repr__(self):
return f"Vector2D({self.x}, {self.y})"

# 现在可以像数学对象一样操作了!
v1 = Vector2D(1, 2)
v2 = Vector2D(3, 4)

print(v1 + v2) # Vector2D(4, 6)
print(v2 - v1) # Vector2D(2, 2)
print(v1 * 3) # Vector2D(3, 6)

小知识

  • __add____sub__等对应+-运算符
  • __radd__是右加法,当左操作数不支持加法时使用
  • __iadd__是原地加法,对应+=运算符

第三章:属性守卫——精细控制属性访问

想给你的属性加个“保安”吗?这些方法能帮你!

class User:
"""一个对年龄有严格要求的用户类"""
def __init__(self, name, age):
self.name = name
self._age = age # 注意:这里用了单个下划线,表示"受保护的"

def __setattr__(self, name, value):
"""属性赋值时的守卫"""
if name == "_age": # 检查年龄
if not 0 <= value <= 150:
raise ValueError("年龄必须在0到150岁之间!")
if value < 18:
print("警告:未成年人 detected!")
# 调用父类方法实际设置属性
super().__setattr__(name, value)

def __getattr__(self, name):
"""访问不存在的属性时的处理"""
if name == "is_adult":
return self._age >= 18
elif name.startswith("default_"):
return f"这是{name}的默认值"
else:
raise AttributeError(f"'{self.__class__.__name__}'没有属性'{name}'")

def __delattr__(self, name):
"""删除属性时的处理"""
if name == "_age":
raise AttributeError("年龄是重要信息,不能删除!")
super().__delattr__(name)

# 试试看
user = User("小明", 25)
print(user.is_adult) # True - 动态创建的属性!
# user._age = 200 # ValueError: 年龄必须在0到150岁之间!
# del user._age # AttributeError: 年龄是重要信息,不能删除!

重要区别

  • __getattribute__:访问任何属性时都触发(包括存在的属性)
  • __getattr__:只在访问不存在的属性时触发(最后的防线)

第四章:序列化超能力——像列表一样操作

想让你的对象支持索引和切片吗?安排!

class Playlist:
"""一个音乐播放列表"""
def __init__(self, songs):
self.songs = list(songs)

def __getitem__(self, index):
"""支持索引和切片"""
if isinstance(index, slice):
# 如果是切片,返回新的Playlist
return Playlist(self.songs[index])
# 否则返回单个歌曲
return self.songs[index]

def __setitem__(self, index, value):
"""设置歌曲时自动添加后缀"""
if isinstance(index, slice):
self.songs[index] = [f"🎵 {song} 🎵" for song in value]
else:
self.songs[index] = f"🎵 {value} 🎵"

def __len__(self):
"""支持len()函数"""
return len(self.songs)

def __repr__(self):
return f"Playlist({self.songs})"

# 现在可以像操作列表一样操作Playlist了!
my_playlist = Playlist(["晴天", "夜曲", "七里香"])
print(my_playlist[0]) # "晴天"
print(my_playlist[1:]) # Playlist(['夜曲', '七里香'])
print(len(my_playlist)) # 3

my_playlist[0] = "稻香" # 自动添加音乐符号
print(my_playlist[0]) # "🎵 稻香 🎵"

第五章:迭代的艺术——让对象可循环

想让你的对象能在for循环中使用吗?需要两个魔法方法!

class Countdown:
"""倒计时迭代器"""
def __init__(self, start):
self.current = start

def __iter__(self):
"""返回迭代器对象"""
return self

def __next__(self):
"""返回下一个值"""
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value

# 现在可以用for循环了!
for num in Countdown(5):
print(num, end=" ") # 输出: 5 4 3 2 1

第六章:关系测试——判断元素是否存在

class LotteryNumbers:
"""彩票号码池"""
def __init__(self, numbers):
self.numbers = set(numbers)

def __contains__(self, number):
"""支持in操作符"""
return number in self.numbers

lottery = LotteryNumbers([3, 7, 11, 23, 42])
print(7 in lottery) # True
print(99 in lottery) # False

代偿机制:如果类没有实现__contains__,Python会尝试使用__iter__,如果还没有,会尝试__getitem__。这就是Python的”代偿”机制,总能找到一种方式满足你的需求!

第七章:伪装成函数——可调用对象

想让对象像函数一样被调用吗?只需一个魔法方法!

class Multiplier:
"""一个可以记住倍数的乘法器"""
def __init__(self, factor):
self.factor = factor

def __call__(self, number):
"""让对象可以像函数一样被调用"""
return number * self.factor

# 创建对象
double = Multiplier(2)
triple = Multiplier(3)

# 像函数一样调用对象
print(double(5)) # 10
print(triple(5)) # 15
print(double(10)) # 20

第八章:字符串变身术——美化对象展示

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

def __str__(self):
"""给人看的字符串(简洁友好)"""
return f"{self.name}, {self.age}岁"

def __repr__(self):
"""给开发者看的字符串(详细准确)"""
return f"Person('{self.name}', {self.age})"

person = Person("张三", 30)

# 不同的展示方式
print(person) # 张三, 30岁 (调用__str__)
print(str(person)) # 张三, 30岁 (调用__str__)
print(repr(person)) # Person('张三', 30) (调用__repr__)

# 在交互式环境中直接输入person,会显示repr的结果
# >>> person
# Person('张三', 30)

重要提示:如果只实现了__repr__,Python会用它来代偿__str__。但反过来不行,所以至少要实现__repr__

总结:魔法方法的力量

通过魔法方法,你可以:

  1. 控制对象的生命周期__new____init____del__
  2. 赋予对象运算能力__add____sub__等)
  3. 精细管理属性访问__getattr____setattr__等)
  4. 让对象支持索引和切片__getitem____setitem__
  5. 使对象可迭代__iter____next__
  6. 支持成员关系测试__contains__
  7. 让对象可调用__call__
  8. 美化对象显示__str____repr__

掌握这些魔法方法,你的Python对象就能从”普通对象”升级为”超级对象”,拥有和Python内置对象一样优雅的行为和强大的功能!

记住,能力越大,责任越大。合理使用魔法方法,让你的代码既强大又优雅!