Python面向对象:__iter__与__next__创建可迭代对象

一、开篇:让你的对象能被for循环
你每天都在用for循环遍历列表、字典、文件。但你是否想过——为什么你能for x in my_object?答案是迭代器协议:__iter__和__next__。
⌨️ 先看核心概念:
# 可迭代对象(Iterable):实现了__iter__,可以被for循环使用
# 迭代器(Iterator):实现了__iter__和__next__,逐个产出元素
# 内置的例子
my_list = [1, 2, 3]
# 1. my_list是可迭代对象——有__iter__
print(hasattr(my_list, '__iter__')) # True
# 2. 调用iter()获得迭代器
iterator = iter(my_list) # 等价于 my_list.__iter__()
print(type(iterator)) # <class 'list_iterator'>
# 3. 调用next()获取下一个元素
print(next(iterator)) # 1
print(next(iterator)) # 2
print(next(iterator)) # 3
# print(next(iterator)) # StopIteration —— 迭代结束
二、创建自定义迭代器
2.1 通过__iter__和__next__
class Countdown:
"""倒计时迭代器"""
def __init__(self, start):
self.start = 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 n in Countdown(5):
print(n, end=" ") # 5 4 3 2 1
print()
print(list(Countdown(3))) # [3, 2, 1]
# ⚠️ 上述实现的限制:只能迭代一次
cd = Countdown(3)
print(list(cd)) # [3, 2, 1]
print(list(cd)) # [] —— 第二次为空!current已经到0了
2.2 分离可迭代对象和迭代器
# ✅ 更好的设计:可迭代对象每次产生新的迭代器
# 这样可以多次迭代!
class CountdownIterable:
"""倒计时——可多次迭代"""
def __init__(self, start):
self.start = start
def __iter__(self):
"""每次调用返回一个新的迭代器"""
return CountdownIterator(self.start)
class CountdownIterator:
"""倒计时迭代器——不需要在外部直接使用"""
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
# 现在可以多次迭代了
cd = CountdownIterable(3)
print(list(cd)) # [3, 2, 1]
print(list(cd)) # [3, 2, 1] —— 没问题!每次迭代都是新的迭代器
三、用生成器简化实现
3.1 __iter__返回生成器
# 使用yield可以极大简化__iter__的实现
# 不需要单独写__next__
class Fibonacci:
"""斐波那契数列——使用生成器的__iter__"""
def __init__(self, max_count):
self.max_count = max_count
def __iter__(self):
"""生成器函数——自动创建迭代器"""
a, b = 0, 1
for _ in range(self.max_count):
yield a
a, b = b, a + b
# 使用
fib = Fibonacci(10)
print(list(fib)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# 可以多次迭代
print(list(fib)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# 💡 这是最Pythonic的做法!
# 用yield在__iter__中定义迭代逻辑
# 简洁、可读、正确(自动支持多次迭代)
3.2 实际应用:分页遍历
class Paginator:
"""分页器——自动按页遍历大数据集"""
def __init__(self, items, page_size=10):
self.items = items
self.page_size = page_size
def __iter__(self):
"""按页产出数据"""
for i in range(0, len(self.items), self.page_size):
page = self.items[i:i + self.page_size]
page_num = i // self.page_size + 1
total_pages = (len(self.items) + self.page_size – 1) // self.page_size
yield {
"page": page_num,
"total_pages": total_pages,
"items": page,
"has_next": page_num < total_pages,
}
def __len__(self):
"""返回总页数"""
return (len(self.items) + self.page_size – 1) // self.page_size
# 使用
data = list(range(1, 26)) # 25条数据
paginator = Paginator(data, page_size=10)
for page in paginator:
print(f"第{page['page']}/{page['total_pages']}页: {page['items']}")
# 第1/3页: [1, 2, …, 10]
# 第2/3页: [11, 12, …, 20]
# 第3/3页: [21, 22, 23, 24, 25]
四、实战案例
4.1 日期范围迭代器
from datetime import datetime, timedelta
class DateRange:
"""日期范围——可以for循环遍历每一天"""
def __init__(self, start_date, end_date):
self.start_date = start_date
self.end_date = end_date
def __iter__(self):
current = self.start_date
while current <= self.end_date:
yield current
current += timedelta(days=1)
def __contains__(self, date):
"""支持 in 操作符"""
return self.start_date <= date <= self.end_date
def __len__(self):
"""返回天数"""
return (self.end_date – self.start_date).days + 1
# 使用
start = datetime(2024, 6, 1)
end = datetime(2024, 6, 10)
date_range = DateRange(start, end)
# for循环遍历
for d in date_range:
if d.weekday() >= 5: # 周末
print(f"{d.strftime('%Y-%m-%d')} 是周末")
# in操作
check_date = datetime(2024, 6, 5)
print(f"{check_date.date()} {'在' if check_date in date_range else '不在'}范围内")
# 2024-06-05 在范围内
# len
print(f"共{len(date_range)}天")
4.2 文件行迭代器(带行号)
class LineNumberedFile:
"""读取文件——自动添加行号"""
def __init__(self, filepath, encoding='utf-8'):
self.filepath = filepath
self.encoding = encoding
def __iter__(self):
"""逐行产出,每行带行号"""
with open(self.filepath, 'r', encoding=self.encoding) as f:
for line_num, line in enumerate(f, 1):
yield {
"number": line_num,
"content": line.rstrip('\\n'),
"length": len(line.rstrip('\\n')),
"is_empty": line.strip() == "",
}
# 使用
# reader = LineNumberedFile("data.txt")
# for line_info in reader:
# if not line_info["is_empty"]:
# print(f"第{line_info['number']}行({line_info['length']}字): "
# f"{line_info['content'][:50]}…")
五、总结
实现__iter__让你的对象可以被for循环使用——这是Python集合生态的"通行证"。
💡 核心要点:
✅ 最佳实践:在__iter__中用生成器(yield)——代码简洁且自动支持多次迭代。




