当前位置:首页>python>Python快速入门学习笔记二十六:迭代器

Python快速入门学习笔记二十六:迭代器

  • 2026-10-11 07:47:14
Python快速入门学习笔记二十六:迭代器
❝

Python入门第二十六课,主要是学习了迭代器,迭代器提供按需逐个访问元素的方式,统一了序列的遍历接口,实现惰性计算与内存优化。

  • 什么是迭代器
    • 可迭代对象(iterable)
    • 迭代器(iterator)
    • 迭代器协议
  • 迭代器的应用
  • 迭代器的优势

什么是迭代器

知道什么是迭代器之前,需要先区分清楚两个概念:可迭代对象(iterable)、迭代器(iterator)。

可迭代对象(iterable)

概念:能被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 = 100deftest():pass# 主动调用__iter__()方法names.__iter__()cities.__iter__()lang.__iter__()# age.__iter__() # 报错:AttributeError: 'int' object has no attribute '__iter__'# 通过hasattr()检测对象是否有__iter__()方法print(hasattr(names, '__iter__'))   # Trueprint(hasattr(cities, '__iter__'))  # Trueprint(hasattr(lang, '__iter__'))    # Trueprint(hasattr(age, '__iter__'))     # Falseprint(hasattr(test, '__iter__'))    # False

迭代器(iterator)

可迭代对象调用__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 = 0whileTrue:    loop_index += 1try:# 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异常# 如果想重新依次获取元素,需要使用新的迭代器it2print(next(it2)) # 乔丹print(next(it2)) # 科比print(next(it2)) # 詹姆斯

迭代器的应用

❝

需求:让for循环可以遍历Person的实例对象。

➊ 实现方式一:Person借助额外的迭代器实现。

classPerson:def__init__(self, name, age, gender, address):        self.name = name        self.age = age        self.gender = gender        self.address = addressdef__iter__(self):return PersonIterator(self)classPersonIterator: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# 返回valuereturn valuep1 = Person('远方', 41, '男', '山西太原')for attr in p1:    print(attr)print('--' * 10)for attr in p1:    print(attr)# 运行结果如下:# 远方# 41# 男# 山西太原# --------------------# 远方# 41# 男# 山西太原

➋ 实现方式二:Person自身扩展成为迭代器。

classPerson: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 = 0return selfdef__next__(self):# 如果指针的位置超出了范围,则抛出StopIteration异常if self.__index >= len(self.__attrs):raise StopIteration# 获取要返回的内容        value = self.__attrs[self.__index]# 更新迭代器的状态(指针位置)        self.__index += 1# 返回valuereturn valuep2 = Person('远方', 41, '男', '山西太原')for item in p2:    print(item)print('--' * 10)for item in p2:    print(item)# 运行结果如下:# 远方# 41# 男# 山西太原# --------------------# 远方# 41# 男# 山西太原

重点:迭代器的关键应用就是__next__,在__next__方法中可以加工处理迭代的元素。

from cn2an import an2cnclassPerson: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 = 0return selfdef__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# 返回valuereturn valuep2 = Person('yuanfang', 41, '男', '山西太原')for item in p2:    print(item)# 运行结果如下:# YUANFANG# 四十一# 男# 山西太原

迭代器的优势

迭代器是惰性计算,不会一次性生成所有结果,所以能显著降低内存占用。

当数据量很大,不确定要用多少结果时,推荐使用迭代器。

➊ 使用迭代器实现【斐波那契额数列】:

classFibo:def__init__(self, total):# 要生成多少个数        self.total = total# 当前生成到第几个了(计数器,指针)        self.index = 0# 初始的两个值        self.pre = 1        self.cur = 1def__iter__(self):return selfdef__next__(self):# 当生成足够数量后,抛出StopIteration异常if self.index >= self.total:raise StopIteration# 前两项都是1if self.index < 2:            value = 1else:# 新的结果等于前两项的和            value = self.pre + self.cur# 更新pre和cur            self.pre = self.cur            self.cur = value# 移动迭代器指针+1        self.index += 1# 返回valuereturn valuef = Fibo(10)print(f)result = []for n in f:    result.append(n)print(result) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

➋ 不使用迭代器实现【斐波那契额数列】:

deffibo(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 numsresult2 = fibo(10)print(result2) # [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

对比分析上面两种实现方式内存占用情况:

import tracemalloctracemalloc.start()f1 = Fibo(100000)m1 = tracemalloc.get_traced_memory()[1]print(f'迭代器实现方式占用内存:{m1 / 1024 / 1024}MB')# 迭代器实现方式占用内存:0.00030517578125MBtracemalloc.stop()tracemalloc.start()f2 = fibo(100000)m2 = tracemalloc.get_traced_memory()[1]print(f'非迭代器实现方式占用内存:{m2 / 1024 / 1024}MB')# 非迭代器实现方式占用内存:444.99246978759766MBtracemalloc.stop()

通过运行结果,对于数据量比较大的情况下,会发现迭代器实现明显节约内存。

❝

原理是:迭代器创建实例对象后,并未真正生成数据,只有遍历迭代器或者通过next()方法获取元素时,才会真正生成元素占用内存;而非迭代器模式,则运行时就会生成完整的数据。

最新文章

随机文章