第三十一篇:定制类——特殊方法详解
前言
Python 的强大之处在于:自定义类可以通过实现特殊方法(魔术方法)表现得像内置类型一样。通过 __str__、__len__、__iter__、__getitem__ 等方法,你的类可以支持 print()、len()、for 循环、索引访问等操作。本文将系统讲解这些核心特殊方法。
str 与 repr —— 对象的字符串表示
基本区别
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
p = Person("小明", 25)
print(p) # <__main__.Person object at 0x...> ← 不友好
print(str(p)) # 同上
实现 str 和 repr
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __str__(self):
"""print() 和 str() 调用,面向用户"""
return f"Person(name={self.name}, age={self.age})"
def __repr__(self):
"""交互式环境直接显示,面向开发者"""
return f"Person('{self.name}', {self.age})"
p = Person("小明", 25)
print(p) # Person(name=小明, age=25)
p # Person('小明', 25)
repr 的备份策略
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# 如果只实现一个,实现 __repr__
# 因为 __repr__ 会作为 __str__ 的后备
def __repr__(self):
return f"Product('{self.name}', {self.price})"
prod = Product("键盘", 299)
print(prod) # Product('键盘', 299) ← __repr__ 作为 __str__ 后备
print([prod]) # [Product('键盘', 299)] ← 列表中始终用 __repr__
len 与 bool —— 长度与真值
class Stack:
def __init__(self):
self._items = []
def push(self, item):
self._items.append(item)
def pop(self):
if not self._items:
raise IndexError("pop from empty stack")
return self._items.pop()
def __len__(self):
"""支持 len(stack)"""
return len(self._items)
def __bool__(self):
"""支持 if stack: 判断是否为空"""
return len(self._items) > 0
stack = Stack()
print(len(stack)) # 0
print(bool(stack)) # False
stack.push("a")
stack.push("b")
print(len(stack)) # 2
if stack: # True
print("栈不为空")
stack.pop()
stack.pop()
if not stack: # True
print("栈为空")
iter 与 next —— 让类可迭代
class NumberRange:
"""自定义数字范围类"""
def __init__(self, start, end, step=1):
self.current = start
self.end = end
self.step = step
def __iter__(self):
"""返回迭代器对象(通常返回 self)"""
return self
def __next__(self):
"""返回下一个值,结束时抛出 StopIteration"""
if self.current >= self.end:
raise StopIteration
value = self.current
self.current += self.step
return value
# 使用 for 循环遍历
for num in NumberRange(1, 10, 2):
print(num, end=" ")
# 1 3 5 7 9
# 也可以手动迭代
rng = NumberRange(0, 5)
print(next(rng)) # 0
print(next(rng)) # 1
print(next(rng)) # 2
可重复迭代的类
class CardDeck:
"""一副扑克牌,可以反复洗牌、发牌"""
suits = ["♠", "♥", "♦", "♣"]
ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"]
def __init__(self):
self._cards = [(s, r) for s in self.suits for r in self.ranks]
self._index = 0
def __iter__(self):
"""每次迭代都从头开始"""
self._index = 0
return self
def __next__(self):
if self._index >= len(self._cards):
raise StopIteration
card = self._cards[self._index]
self._index += 1
return card
def __len__(self):
return len(self._cards)
deck = CardDeck()
print(len(deck)) # 52
# 第一次遍历
for suit, rank in deck:
print(f"{suit}{rank}", end=" ")
print()
# 第二次遍历仍然可用
for suit, rank in deck:
pass # 52 张牌
getitem —— 索引与切片访问
class Sentence:
"""自定义句子类,支持索引访问"""
def __init__(self, text):
self.words = text.split()
def __getitem__(self, index):
"""支持索引访问和切片"""
if isinstance(index, slice):
# 切片操作
return Sentence(" ".join(self.words[index]))
elif isinstance(index, int):
# 索引访问
return self.words[index]
else:
raise TypeError(f"索引类型不支持: {type(index)}")
def __len__(self):
return len(self.words)
def __repr__(self):
return f"Sentence('{ ' '.join(self.words) }')"
sentence = Sentence("Hello World Python is awesome")
print(sentence[0]) # Hello
print(sentence[-1]) # awesome
print(sentence[1:3]) # Sentence('World Python')
print(len(sentence)) # 5
# 有了 __getitem__,还能用 for 遍历!
for word in sentence:
print(word, end=" ")
# Hello World Python is awesome
setitem 与 delitem
class Matrix:
"""简单的矩阵类"""
def __init__(self, rows, cols):
self._data = [[0] * cols for _ in range(rows)]
self._rows = rows
self._cols = cols
def __getitem__(self, pos):
row, col = pos
return self._data[row][col]
def __setitem__(self, pos, value):
row, col = pos
self._data[row][col] = value
def __delitem__(self, pos):
row, col = pos
self._data[row][col] = 0 # 重置为默认值
def __repr__(self):
lines = []
for row in self._data:
lines.append(" " + " ".join(f"{v:3}" for v in row))
return "Matrix(\n" + "\n".join(lines) + "\n)"
m = Matrix(3, 3)
m[0, 0] = 1
m[1, 1] = 2
m[2, 2] = 3
print(m[0, 0]) # 1
print(m)
# Matrix(
# 1 0 0
# 0 2 0
# 0 0 3
# )
getattr —— 动态属性访问
class Config:
"""配置类,支持动态属性访问"""
def __init__(self, **kwargs):
self._data = kwargs
def __getattr__(self, name):
"""只在属性不存在时被调用"""
if name.startswith("_"):
raise AttributeError(f"'{type(self).__name__}' 对象没有属性 '{name}'")
if name in self._data:
return self._data[name]
raise AttributeError(f"配置项 '{name}' 不存在")
def __setattr__(self, name, value):
if name.startswith("_"):
super().__setattr__(name, value)
else:
self._data[name] = value
def __repr__(self):
return f"Config({self._data})"
config = Config(host="localhost", port=8080, debug=True)
print(config.host) # localhost
print(config.port) # 8080
print(config.debug) # True
print(config) # Config({'host': 'localhost', 'port': 8080, 'debug': True})
# 设置新属性
config.timeout = 30
print(config.timeout) # 30
getattr 的链式调用
class Chain:
"""链式调用构建 URL 路径"""
def __init__(self, path=""):
self._path = path
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(name)
new_path = f"{self._path}/{name}" if self._path else name
return Chain(new_path)
def __call__(self, *args):
"""支持 Chain().users(123) 这种调用"""
if args:
new_path = f"{self._path}/{'/'.join(str(a) for a in args)}"
return Chain(new_path)
return self
def __str__(self):
return f"/{self._path}"
# 构建 API 路径
api = Chain()
print(api.users) # /users
print(api.users.posts) # /users/posts
print(api.users(123)) # /users/123
print(api.users(123).posts(456)) # /users/123/posts/456
call —— 让实例可调用
class Multiplier:
"""乘法器,实例本身可以像函数一样被调用"""
def __init__(self, factor):
self.factor = factor
def __call__(self, value):
return value * self.factor
def __repr__(self):
return f"Multiplier({self.factor})"
double = Multiplier(2)
triple = Multiplier(3)
print(double(5)) # 10
print(triple(5)) # 15
print(callable(double)) # True ← 实例是可调用对象
实战:带状态的计数器
class Counter:
"""可调用计数器"""
def __init__(self):
self.count = 0
def __call__(self):
self.count += 1
return self.count
counter = Counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
print(counter.count) # 3
eq 与 hash —— 相等性判断与哈希
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
"""判断两个点是否相等"""
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
def __hash__(self):
"""让 Point 可用作字典键或集合元素"""
return hash((self.x, self.y))
def __repr__(self):
return f"Point({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(1, 2)
p3 = Point(3, 4)
print(p1 == p2) # True
print(p1 == p3) # False
# 可用作字典键
locations = {Point(1, 2): "北京", Point(3, 4): "上海"}
print(locations[p2]) # 北京
# 可用作集合元素
point_set = {p1, p2, p3}
print(len(point_set)) # 2 ← p1 和 p2 相同
enter 与 exit —— 上下文管理器
class FileManager:
"""文件管理器,支持 with 语句"""
def __init__(self, filename, mode="r"):
self.filename = filename
self.mode = mode
self.file = None
def __enter__(self):
"""进入 with 块时调用"""
self.file = open(self.filename, self.mode)
return self.file
def __exit__(self, exc_type, exc_val, exc_tb):
"""离开 with 块时调用(即使发生异常)"""
if self.file:
self.file.close()
# 返回 False(或 None)表示不抑制异常
return False
# 使用方式
# with FileManager("test.txt", "w") as f:
# f.write("Hello World")
class Timer:
"""计时器上下文管理器"""
import time
def __enter__(self):
self.start = self.time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.end = self.time.perf_counter()
elapsed = self.end - self.start
print(f"耗时: {elapsed:.4f} 秒")
return False
with Timer():
sum(range(1_000_000))
# 耗时: 0.0312 秒
contains —— 成员判断
class RangeChecker:
"""范围检查类"""
def __init__(self, low, high):
self.low = low
self.high = high
def __contains__(self, item):
"""支持 in 运算符"""
return self.low <= item <= self.high
checker = RangeChecker(1, 100)
print(50 in checker) # True
print(150 in checker) # False
print(0 not in checker) # True
综合实战:自定义链表
class LinkedList:
"""单链表,展示多个特殊方法组合"""
class _Node:
def __init__(self, value):
self.value = value
self.next = None
def __init__(self, values=None):
self._head = None
self._size = 0
if values:
for v in values:
self.append(v)
def append(self, value):
node = self._Node(value)
if self._head is None:
self._head = node
else:
current = self._head
while current.next:
current = current.next
current.next = node
self._size += 1
def __len__(self):
return self._size
def __iter__(self):
current = self._head
while current:
yield current.value
current = current.next
def __getitem__(self, index):
if index < 0:
index += self._size
if not (0 <= index < self._size):
raise IndexError("索引越界")
current = self._head
for _ in range(index):
current = current.next
return current.value
def __contains__(self, value):
for v in self:
if v == value:
return True
return False
def __repr__(self):
items = list(self)
return f"LinkedList({items})"
ll = LinkedList([1, 2, 3, 4, 5])
print(len(ll)) # 5
print(ll[0]) # 1
print(ll[-1]) # 5
print(3 in ll) # True
print(ll) # LinkedList([1, 2, 3, 4, 5])
for v in ll:
print(v, end=" ")
# 1 2 3 4 5
常用特殊方法速查表
| 方法 | 触发方式 | 用途 |
|---|---|---|
__str__ |
print(obj) / str(obj) |
用户友好的字符串 |
__repr__ |
交互式直接显示 | 开发者友好的字符串 |
__len__ |
len(obj) |
返回长度 |
__bool__ |
if obj: / bool(obj) |
真值判断 |
__iter__ |
for x in obj |
返回迭代器 |
__next__ |
next(obj) |
返回下一个值 |
__getitem__ |
obj[key] |
索引/切片访问 |
__setitem__ |
obj[key] = val |
索引赋值 |
__delitem__ |
del obj[key] |
索引删除 |
__getattr__ |
obj.attr(不存在时) |
动态属性 |
__call__ |
obj(args) |
实例调用 |
__eq__ |
obj1 == obj2 |
相等判断 |
__hash__ |
hash(obj) |
哈希值 |
__contains__ |
x in obj |
成员判断 |
__enter__/__exit__ |
with obj: |
上下文管理 |
总结
本文系统讲解了 Python 定制类的核心特殊方法:
- str 与 repr — 控制对象的字符串表示
- len 与 bool — 支持
len()和真值判断 - iter 与 next — 让自定义类可迭代
- getitem — 支持索引和切片访问
- getattr — 动态属性访问与链式调用
- call — 让实例像函数一样可调用
- eq 与 hash — 相等性判断与哈希支持
- enter 与 exit — 上下文管理器协议
- contains — 自定义
in运算符行为 - 综合实战 — 通过组合特殊方法实现类内置类型行为
下一篇文章将讲解枚举类与元类。
本文是 Python 教程系列的第三十一篇。
