Python魔术方法详解

一、str — 用户友好的字符串表示

触发时机

  • 调用 str(obj) 时
  • 使用 print(obj) 时
  • 使用 f”{obj}” 或 “{}”.format(obj) 时

用途
给普通用户看的可读字符串,注重可读性

1
2
3
4
5
6
7
8
9
10
11
12
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return f"姓名: {self.name}, 年龄: {self.age}"

p = Person("张三", 25)
print(p) # 输出: 姓名: 张三, 年龄: 25
print(str(p)) # 输出: 姓名: 张三, 年龄: 25
print(f"{p}") # 输出: 姓名: 张三, 年龄: 25

二、repr — 开发者调试用的字符串表示

触发时机

  • 在交互式解释器中直接输入对象时
  • 调用 repr(obj) 时
  • 未定义 str 时,print() 会回退使用 repr
  • f”{obj!r}” 时

用途
给开发者看的字符串,注重准确性,理想情况下可以用来重建对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __repr__(self):
return f"Person(name={self.name!r}, age={self.age!r})"

def __str__(self):
return f"姓名: {self.name}, 年龄: {self.age}"

p = Person("张三", 25)
repr(p) # 输出: Person(name='张三', age=25)
print(p) # 优先用 __str__: 姓名: 张三, 年龄: 25

# 列表中的对象使用 __repr__
print([p]) # 输出: [Person(name='张三', age=25)]

三、len — 定义对象长度

触发时机

  • 调用 len(obj) 时
  • 用于布尔判断时(len == 0 则为 False)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Playlist:
def __init__(self):
self.songs = []

def add(self, song):
self.songs.append(song)

def __len__(self):
return len(self.songs)

pl = Playlist()
pl.add("歌曲A")
pl.add("歌曲B")

print(len(pl)) # 输出: 2

# 布尔判断
if pl:
print("播放列表不为空") # 会执行

empty_pl = Playlist()
if not empty_pl:
print("播放列表为空") # 会执行

四、eq — 定义相等性判断

触发时机

  • 使用 == 运算符时

默认行为
未定义时,== 比较的是内存地址(同 is)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented # 类型不同时返回 NotImplemented
return self.x == other.x and self.y == other.y

p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)

print(p1 == p2) # True(值相同)
print(p1 == p3) # False
print(p1 == "abc") # False(NotImplemented 让Python反向尝试)

# 注意:定义 __eq__ 后,__hash__ 自动变为 None
# 对象不能作为字典键或集合元素,需同时定义 __hash__

五、其他常用魔术方法速览

比较运算符系列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Temperature:
def __init__(self, celsius):
self.celsius = celsius

def __eq__(self, other): return self.celsius == other.celsius # ==
def __ne__(self, other): return self.celsius != other.celsius # !=
def __lt__(self, other): return self.celsius < other.celsius # <
def __le__(self, other): return self.celsius <= other.celsius # <=
def __gt__(self, other): return self.celsius > other.celsius # >
def __ge__(self, other): return self.celsius >= other.celsius # >=

t1 = Temperature(20)
t2 = Temperature(30)
print(t1 < t2) # True
print(t1 > t2) # False

技巧:使用 @functools.total_ordering 只需实现 eq 和一个比较方法,其余自动推导。

1
2
3
4
5
6
7
8
9
10
11
12
13
from functools import total_ordering

@total_ordering
class Temperature:
def __init__(self, celsius):
self.celsius = celsius

def __eq__(self, other):
return self.celsius == other.celsius

def __lt__(self, other):
return self.celsius < other.celsius
# __le__, __gt__, __ge__ 自动生成

算术运算符系列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y

def __add__(self, other): # v1 + v2
return Vector(self.x + other.x, self.y + other.y)

def __sub__(self, other): # v1 - v2
return Vector(self.x - other.x, self.y - other.y)

def __mul__(self, scalar): # v * 3
return Vector(self.x * scalar, self.y * scalar)

def __rmul__(self, scalar): # 3 * v (反向)
return self.__mul__(scalar)

def __neg__(self): # -v
return Vector(-self.x, -self.y)

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

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6)
print(v1 * 3) # Vector(3, 6)
print(3 * v1) # Vector(3, 6)
print(-v1) # Vector(-1, -2)

容器协议系列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyList:
def __init__(self):
self._data = []

def __len__(self): # len(obj)
return len(self._data)

def __getitem__(self, index): # obj[index]
return self._data[index]

def __setitem__(self, index, value): # obj[index] = value
self._data[index] = value

def __delitem__(self, index): # del obj[index]
del self._data[index]

def __contains__(self, item): # item in obj
return item in self._data

def __iter__(self): # for x in obj
return iter(self._data)

生命周期与上下文管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class DatabaseConnection:
def __init__(self, url):
self.url = url
self.conn = None

def __enter__(self): # with 语句进入
self.conn = connect(self.url)
return self.conn

def __exit__(self, exc_type, exc_val, exc_tb): # with 语句退出
self.conn.close()
return False # False = 不吞掉异常

def __del__(self): # 对象被垃圾回收时
if self.conn:
self.conn.close()

# 使用
with DatabaseConnection("db://localhost") as conn:
conn.query("SELECT * FROM users")
# 自动关闭连接

hash — 配合 eq 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Point:
def __init__(self, x, y):
self.x = x
self.y = y

def __eq__(self, other):
return self.x == other.x and self.y == other.y

def __hash__(self):
# 定义了 __eq__ 必须同时定义 __hash__
return hash((self.x, self.y))

p1 = Point(1, 2)
p2 = Point(1, 2)

# 可作为字典键和集合元素
d = {p1: "point A"}
print(d[p2]) # "point A"(p1 == p2,hash相同)

s = {p1, p2}
print(len(s)) # 1(被视为相同元素)

六、魔术方法总览表

类别 方法 触发场景
字符串 str print(), str(), f”{}”
字符串 repr repr(), 交互器, 容器中
长度 len len(), 布尔判断
比较 eq lt gt == < >

核心原则:魔术方法让自定义类与Python内置语法深度集成,是Python鸭子类型思想的核心体现——只要实现了对应的魔术方法,对象就能支持对应的语法和操作。