知道什么是迭代器之前,需要先区分清楚两个概念:可迭代对象(iterable)、迭代器(iterator)。
概念:能被for循环遍历的对象,就是可迭代对象(iterable)。
例如,列表、元组、字符串等都是可迭代对象,但是数字int、float就不是迭代对象。
names = ['乔丹', '科比', '詹姆斯']
cities =('北京', '上海', '深圳')
lang = 'python'
for name in names:
print(name)
print()
for city in cities:
print(city)
print()
for char in lang:
print(char)
可迭代对象都拥有__iter__方法。
names = ['乔丹', '科比', '詹姆斯']
cities =('北京', '上海', '深圳')
lang = 'python'
age = 100
def test():
pass
# 主动调用__iter__()方法
names.__iter__()
cities.__iter__()
lang.__iter__()
# age.__iter__() # 报错:AttributeError: 'int' object has no attribute '__iter__'
# 通过hasattr()检测对象是否有__iter__()方法
print(hasattr(names, '__iter__')) # True
print(hasattr(cities, '__iter__')) # True
print(hasattr(lang, '__iter__')) # True
print(hasattr(age, '__iter__')) # False
print(hasattr(test, '__iter__')) # False
可迭代对象调用__iter__()方法会得到:迭代器(iterator)。
注意:
❏ __iter__()方法是一个魔术方法,当调用iter()函数时,__iter__()会自动调用。
❏ 可迭代对象.__iter__()等价于:iter(可迭代对象)。
❏ 如果item(obj)能得到一个迭代器(iterator),那obj就是可迭代对象。
names = ['乔丹', '科比', '詹姆斯']
cities =('北京', '上海', '深圳')
lang = 'python'
print(names.__iter__()) # <list_iterator object at 0x000001794C4ABBB0>
print(iter(names)) # <list_iterator object at 0x000001794C4ABBB0>
print(cities.__iter__()) # <tuple_iterator object at 0x000001794C4ABBB0>
print(iter(cities)) # <tuple_iterator object at 0x000001794C4ABBB0>
print(lang.__iter__()) # <str_ascii_iterator object at 0x000001794C4ABBB0>
print(iter(lang)) # <str_ascii_iterator object at 0x000001794C4ABBB0>
迭代器(iterator)拥有__next__()方法,每次调用都会根据当前的状态,返回下一个元素。
注意:
❏ 迭代器.__next__()等价于next(迭代器)。
❏ 当所有元素全部都取出后,若继续调用__next()__(),Python 会抛出StopIteration异常。
names = ['乔丹', '科比', '詹姆斯']
it = names.__iter__()
print(it.__next__())
print(it.__next__())
print(it.__next__())
# print(it.__next__()) # 此行代码运行时会抛出 StopIteration 异常
it2 = iter(names)
print(next(it2))
print(next(it2))
print(next(it2))
# print(next(it2)) # 此行代码运行时会抛出 StopIteration 异常
拓展:使用迭代器实现for循环背后的逻辑
# 拓展:使用迭代器实现for循环背后的逻辑
names = ['乔丹', '科比', '詹姆斯']
# 1. 调用【可迭代对象的__iter__方法】获取到一个迭代器
it = iter(names)
# 2. 开启一个无限循环
loop_index: int = 0
while True:
loop_index += 1
try:
# 3. 调用__next__方法,获取下一个元素
item = next(it)
print(f'{loop_index}. {item}')
except StopIteration:
# 4. 捕获StopIteration异常,随后结束循环
break
迭代器(iterator)也拥有__iter__方法,并且其返回值是迭代器自身。
这样设计的原因是:让for循环也能遍历迭代器(即:为了让iter(迭代器)不出错)。
names = ['乔丹', '科比', '詹姆斯']
it = iter(names)
print(it) # <list_iterator object at 0x000001E43E19BAC0>
result = iter(it)
print(result) # <list_iterator object at 0x000001E43E19BAC0>
xt = iter(result)
print(xt) # <list_iterator object at 0x000001E43E19BAC0>
for item in xt:
print(item)
一个对象如果同时满足如下规范,那么该对象就是一个迭代器:
- 能被
iter()接受;- 能被
next()一步一步取值。
迭代器是一次性的,状态只会向前推进,且不会自动重置(迭代器在遍历的过程中会被“消耗”)。
names = ['乔丹', '科比', '詹姆斯']
it1 = iter(names)
it2 = iter(names)
# it1 和 it2 是两个迭代器,内存地址不同
print(it1) # <list_iterator object at 0x000001E4396CB8E0>
print(it2) # <list_iterator object at 0x000001E4396CB910>
print(next(it1)) # 乔丹
print(next(it1)) # 科比
print(next(it1)) # 詹姆斯
# print(next(it1)) # 因为迭代器元素通过next已经被读取完了,运行到此行会抛出StopIteration异常
# 如果想重新依次获取元素,需要使用新的迭代器it2
print(next(it2)) # 乔丹
print(next(it2)) # 科比
print(next(it2)) # 詹姆斯
需求:让
for循环可以遍历Person的实例对象。
➊ 实现方式一:Person借助额外的迭代器实现。
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
def __iter__(self):
return PersonIterator(self)
class PersonIterator:
def __init__(self, person):
# 将外部传进来的数据保存好
self.person = person
# 设置迭代器的初始化状态(指针位置)
self.index = 0
# 配置好要遍历的内容
self.attrs = [person.name, person.age, person.gender, person.address]
# 迭代器的__iter__方法会返回迭代器自身
def __iter__(self):
return self
# 每次调用__next__方法,会根据当前的状态,返回下一个元素
def __next__(self):
# 如果指针的位置超出范围,那就抛出StopIteration异常
if self.index >= len(self.attrs):
raise StopIteration
# 获取要返回的内容
value = self.attrs[self.index]
# 更新迭代器状态(指针位置)
self.index += 1
# 返回value
return value
p1 = Person('远方', 41, '男', '山西太原')
for attr in p1:
print(attr)
print('--' * 10)
for attr in p1:
print(attr)
# 运行结果如下:
# 远方
# 41
# 男
# 山西太原
# --------------------
# 远方
# 41
# 男
# 山西太原
➋ 实现方式二:Person自身扩展成为迭代器。
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
# 设置迭代器的初始化状态(指针位置)
self.__index = 0
# 配置要遍历的内容
self.__attrs = [name, age, gender, address]
def __iter__(self):
# 重置迭代器的状态,让指针回到初始位置
self.__index = 0
return self
def __next__(self):
# 如果指针的位置超出了范围,则抛出StopIteration异常
if self.__index >= len(self.__attrs):
raise StopIteration
# 获取要返回的内容
value = self.__attrs[self.__index]
# 更新迭代器的状态(指针位置)
self.__index += 1
# 返回value
return value
p2 = Person('远方', 41, '男', '山西太原')
for item in p2:
print(item)
print('--' * 10)
for item in p2:
print(item)
# 运行结果如下:
# 远方
# 41
# 男
# 山西太原
# --------------------
# 远方
# 41
# 男
# 山西太原
重点:迭代器的关键应用就是__next__,在__next__方法中可以加工处理迭代的元素。
from cn2an import an2cn
class Person:
def __init__(self, name, age, gender, address):
self.name = name
self.age = age
self.gender = gender
self.address = address
# 设置迭代器的初始化状态(指针位置)
self.__index = 0
# 配置要遍历的内容
self.__attrs = [name, age, gender, address]
def __iter__(self):
# 重置迭代器的状态,让指针回到初始位置
self.__index = 0
return self
def __next__(self):
# 如果指针的位置超出了范围,则抛出StopIteration异常
if self.__index >= len(self.__attrs):
raise StopIteration
# 获取要返回的内容
value = self.__attrs[self.__index]
# 加工value:将字符串转为大写
if isinstance(value, str):
value = value.upper()
# 加工value:将数字转为汉语形式
if isinstance(value, int):
value = an2cn(value)
# 更新迭代器的状态(指针位置)
self.__index += 1
# 返回value
return value
p2 = Person('yuanfang', 41, '男', '山西太原')
for item in p2:
print(item)
# 运行结果如下:
# YUANFANG
# 四十一
# 男
# 山西太原
迭代器是惰性计算,不会一次性生成所有结果,所以能显著降低内存占用。
当数据量很大,不确定要用多少结果时,推荐使用迭代器。
➊ 使用迭代器实现【斐波那契额数列】:
class Fibo:
def __init__(self, total):
# 要生成多少个数
self.total = total
# 当前生成到第几个了(计数器,指针)
self.index = 0
# 初始的两个值
self.pre = 1
self.cur = 1
def __iter__(self):
return self
def __next__(self):
# 当生成足够数量后,抛出StopIteration异常
if self.index >= self.total:
raise StopIteration
# 前两项都是1
if self.index < 2:
value = 1
else:
# 新的结果等于前两项的和
value = self.pre + self.cur
# 更新pre和cur
self.pre = self.cur
self.cur = value
# 移动迭代器指针+1
self.index += 1
# 返回value
return value
f = Fibo(10)
print(f)
result = []
for n in f:
result.append(n)
print(result) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
➋ 不使用迭代器实现【斐波那契额数列】:
def fibo(total):
if total <= 0:
return []
elif total == 1:
return [1]
nums = [1, 1]
for i in range (2, total):
nums.append(nums[-1] + nums[-2])
return nums
result2 = fibo(10)
print(result2) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
对比分析上面两种实现方式内存占用情况:
import tracemalloc
tracemalloc.start()
f1 = Fibo(100000)
m1 = tracemalloc.get_traced_memory()[1]
print(f'迭代器实现方式占用内存:{m1 / 1024 / 1024}MB')
# 迭代器实现方式占用内存:0.00030517578125MB
tracemalloc.stop()
tracemalloc.start()
f2 = fibo(100000)
m2 = tracemalloc.get_traced_memory()[1]
print(f'非迭代器实现方式占用内存:{m2 / 1024 / 1024}MB')
# 非迭代器实现方式占用内存:444.99246978759766MB
tracemalloc.stop()
通过运行结果,对于数据量比较大的情况下,会发现迭代器实现明显节约内存。
原理是:迭代器创建实例对象后,并未真正生成数据,只有遍历迭代器或者通过
next()方法获取元素时,才会真正生成元素占用内存;而非迭代器模式,则运行时就会生成完整的数据。