python快速入门学习笔记(进阶篇)二十六:迭代器

  • 原创
  • 作者:程序员三丰
  • 发布时间:2026-05-18 10:10
  • 浏览量:2
Python入门第二十六课,主要是学习了迭代器,迭代器提供按需逐个访问元素的方式,统一了序列的遍历接口,实现惰性计算与内存优化。

什么是迭代器

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

可迭代对象(iterable)

概念:能被for循环遍历的对象,就是可迭代对象(iterable)。

例如,列表、元组、字符串等都是可迭代对象,但是数字intfloat就不是迭代对象。

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

迭代器(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 = 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()方法获取元素时,才会真正生成元素占用内存;而非迭代器模式,则运行时就会生成完整的数据。

声明:本文为原创文章,51blog.xyz和作者拥有版权,如需转载,请注明来源于51blog.xyz并保留原文链接:https://www.51blog.xyz/article/128.html

文章归档

推荐文章

buildadmin logo
Thinkphp8 Vue3 Element PLus TypeScript Vite Pinia

🔥BuildAdmin是一个永久免费开源,无需授权即可商业使用,且使用了流行技术栈快速创建商业级后台管理系统。

热门标签

PHP ThinkPHP ThinkPHP5.1 Go Mysql Mysql5.7 Redis Linux CentOS7 Git HTML CSS CSS3 Javascript JQuery Vue LayUI VMware Uniapp 微信小程序 docker wiki Confluence7 学习笔记 uView ES6 Ant Design Pro of Vue React ThinkPHP6.0 chrome 扩展 翻译工具 Nuxt SSR 服务端渲染 scrollreveal.js ThinkPHP8.0 Mac webman 跨域CORS vscode GitHub ECharts Canvas vue3 three.js 微信支付 PHP全栈开发 Python AI 人工智能 AI生成 工作经验 实战笔记