在python中,迭代对象(Iterable)是指可以使用迭代器协议进行遍历的对象。迭代对象可以是序列类型(如列表、元组、字符串),也可以是映射类型(如字典),还可以是自定义的实现了迭代器协议的类的实例等。

1. 迭代器的认识

迭代器的设计意图

迭代对象的设计意图是为了抽象出数据遍历的过程,使得数据的存储和访问方式分离,这样开发者就可以专注于数据的处理逻辑,而不必关心数据是如何存储和组织的,同时迭代器协议也为python的许多内置功能提供了支持。

可迭代对象何迭代器的介绍

  • 可迭代对象是实现了__iter__()方法的对象,该方法返回一个迭代器(假设可迭代对象为a,那么得通过iter(a)来返回一个迭代器,才能通过next迭代来访问该迭代器内部的元素),迭代对象本身不支持直接使用next()方法进行迭代。
  • 迭代器是实现了__iter__()__next__()方法的对象,__iter__()方法返回迭代器自身, __next__()方法用于返回下一个元素。

注意迭代器是一个实现了迭代协议的对象,该迭代器协议就是要求该对象必须实现两个魔法方法: __iter__()__next__(),那么该对象就满足迭代协议,后续也会介绍这两个魔法方法的功能。

迭代器的性质

  • 统一访问接口:迭代对象提供了一种统一的方式来遍历不同类型的数据结构,无论是列表、元组、字典还是自定义的数据结构,都可以使用相同的迭代语法(如for循环),这大大提高代码的可读性和可维护性。
  • 节省内存:对于大型数据集,迭代对象可以实现*按需生成数据*,而不是一次性将所有数据加载到内存中,这种惰性求值的方式可以显著减少内存的使用,特别是在大规模数据时非常有用。
  • 代码复用性高:迭代器协议使得代码可以更加模块化,可以将迭代逻辑封装在迭代对象中,然后在不同的地方使用相同的迭代逻辑来处理不同的数据。

可迭代协议

一个对象要成为可迭代对象,需要实现__init__方法或者__getitem__方法。__iter__方法应该返回一个迭代器对象,而__getitem__方法则允许对象通过索引进行访问,当使用for循环等进行迭代时,python会尝试调用这些方法来获取迭代器或通过索引获取元素,从而实现迭代。

迭代过程

当对一个迭代对象使用for循环或其他需要迭代的操作时,实际上是在背后进行一系列操作。首先会调用迭代对象的__iter__方法获取一个迭代器,然后通过不断调用迭代器的__next__方法来逐个获取元素,知道所有元素都被访问完或遇到StopIteration异常。

常见的迭代对象

  • 序列类型

    列表、元组、字符串这三者都可以看作是序列数据类型,我们可以通过for循环来按照这三个数据类型,以序列的形式去遍历这个迭代数据。

a = [1, 2, 3] #列表
b = (4, 5, 6) #元组
c = 'hello'   #字符串

a_list = [i + 10 for i in a]
print(f'a_list的for循环进行迭代输出的结果:{a_list}') #[11, 12, 13]
b_tuple = (i + 10 for i in b)
b_tuple = tuple(b_tuple) #由于上面那行代码的输出是一个生成器,为了显示对应的数值,需要借助tuple函数将生成器转换为元组
print(f'b_tuple的for循环进行迭代输出的结果:{b_tuple}') #(14, 15, 16)
c_string = [i for i in c]
print(f'c_string的for循环进行迭代输出的结果:{c_string}') #['h', 'e', 'l', 'l', 'o']
  • 映射类型

    字典是迭代对象的一种映射类型的数据结构,由于字典本身具有键值对两个元素,故以代码展示最为直观。

dict_example = {'a':1, 'b':2} #字典数据

dict_default = {i for i in dict_example}  #字典的迭代会默认输出字典的键, 这里选择字典的输出结构是集合,集合内部的数据是不允许出现重复元素的
print(dict_default) #输出字典的键 {'a', 'b'}

dict_values = [i for i in dict_example.values()] #字典的迭代若需要输出字典的值,则需要在遍历字典时为其添加上.values()的访问
print(dict_values) #输出字典的值[1, 2]

dict_all = {i:j for i,j in dict_example.items()} #遍历字典的键值对则需通过字典对.items()的访问,同时确保字典键值的格式
print(dict_all) #输出字典键值对{'a':1, 'b':2}
  • 其他迭代对象

    文件对象:当打开一个文件后,文件对象也是可迭代的,可以逐行读取文件内容,如for line in open('file.txt'),会逐行返回文件中的内容。

​ 生成器:生成器是一种特殊的迭代器,它可以通过生成器表达式或yield关键字来创建。生成器在迭代时按需生成数据,而不是一次性将所有数据都生成出来,这种处理大量数据或需要惰性求值的场景中非常有用。例如(i**2 for i in range(10))就是一个生成器表达式。

2.迭代对象的适用场景

  • 数据遍历:我们通常使用的for循环等进行数据遍历的方式,背后就是由于完善的迭代封装机制,使得我们可以来遍历各种数据结构,如列表、元组、字典等。
my_list = [1, 2, 3, 4, 5]
for num in my_list:
    print(num) #1, 2, 3 ,4, 5 #这里是直接在列表这个数据结构内直接提取了元素!!!

my_tuple = (1, 2, 3, 4)
for num in my_tuple:
	print(num) #1, 2,3,4 这里是直接在元组这个数据结构内提取出了元素
	
my_dict = {'a': 1, 'b': 2}
for v in my_dict.values():
	print(v) #1, 2  这里是直接在字典这个数据结构内进行遍历提取出了字典的值
  • 大数据处理:在处理大规模数据时,迭代对象可以按需生成数据,避免一次性加载大量数据到内存中,例如在处理大型文件时,可以逐行读取文件内容,而不是将整个文件加载到内存中。
with open('large_file.txt', 'r') as file:
    for line in file:
        # 处理每一行数据
        print(line)
  • 生成器表达式:生成器表达式是一种特殊的迭代对象,它可以用于生成数据序列,生成器表达式在需要生成大量数据但又不想一次性占用过多内存时非常有用。
squares = (x**2 for x in range(1000)) #生成了一个生成器对象squares
print(squares) #<generator object <genexpr> at 0x000001D1A1DDCAD0> 
#<generator object <genexpr>这个表示一个生成器的标记, at 0x000001D1A1DDCAD0表示该生成器的内存地址
for square in squares:
    print(square)

3. 自定义迭代对象

迭代对象也可以自己按照自己面对的需求来自定义生成,主要通过__iter____next__方法来创建自定义的迭代对象并且当类实现了__getitem___方法也可以视为可迭代对象。

class MyIterator:
    def __init__(self, data):
        self.data = data
        self.index = 0

    def __iter__(self):
        return self
        
    def __getitem__(self,index):
    	return self.data[index]

    def __next__(self):
        if self.index >= len(self.data):
            raise StopIteration
        value = self.data[self.index]
        self.index += 1
        return value
    

my_iterator = MyIterator([1, 2, 3, 4, 5])
for num in my_iterator:
    print(num)

此处再介绍下这三种魔法方法。

__iter__方法是迭代器协议的核心方法之一,它用于返回一个迭代器对象,当使用for循环或其他迭代操作遍历一个迭代对象时,python会首先调用该对象的__iter__方法来获取迭代器。

__next__方法用于返回迭代器的下一个元素,当没有更多元素时就会抛出StopIteration异常。__next__方法通常与__iter__方法一起使用,__iter__方法返回迭代器对象,而__next__方法用于迭代器对象中,实现元素的逐个返回。

__getitem__方法用于通过索引访问对象的元素,当一个对象实现了__getitem__方法时,python会尝试通过索引从0开始依次调用__getitem__方法来遍历对象的元素,知道抛出IndexError异常。

4.迭代器使用的一个案例

在回顾SC3K这篇论文的代码时我看到了一个使用迭代器的案例,这个使用案例的场景代码如下,他的输入参数camera_param_np是一个包含npz(numpy)数据格式的列表,于是便记录来分享一下。

self.camera_param_np = list(itertools.chain.from_iterable(camera_param_np))

首先逐步拆解这个代码,在list这个将元素转化为列表这个命令的括号内依次解释:

itertools是python标准库的一个模块,提供了一系列用于高效创建迭代器的工具函数,其内部函数可以用于排列组合、迭代器合并、切片等操作,有利于我们方便处理迭代对象。

chain函数本身的作用是将多个可迭代对象连接成一个单一的迭代器,依次迭代这些可迭代对象中的元素,注意下方代码案例的输出就纯纯是元素,脱离了内部列表的影响(为什么说是内部列表呢,因为我们在外部还是用了list函数将其转为列表结构,因为如果我们不转为list的话,那么输出结果就是<itertools.chain at 0x1d1a40dfee0>,at后面的内容就是在我自己电脑内的内存地址)。

list(itertools.chain([1, 2], [3, 4])) #输出为 [1, 2, 3, 4] 这儿就是直接对两个列表进行迭代,将其元素一一取出

from_iterablechain的一个类方法,它接受一个可迭代对象(camera_param_np)作为参数,该可迭代对象的每个元素(camera_param_np这个列表内的每个umpy数组)也必须是可迭代的,from_iterable会将这个可迭代对象中的所有元素“展开“并连接成一个单一的迭代器。

所以根据上面抽象的代码,我们这里实例化一个代码进行分析来加以理解:

import itertools
import numpy as np

# 模拟 camera_param_np 列表,包含多个 numpy 数组
camera_param_np = [np.array([1, 2, 3]), np.array([4, 5, 6]), np.array([7, 8, 9])]

# 使用 itertools.chain.from_iterable 进行扁平化处理
flattened_iterator = itertools.chain.from_iterable(camera_param_np)

# 将迭代器转换为列表以便查看结果
flattened_list = list(flattened_iterator)

print(flattened_list) #输出 [1, 2, 3, 4, 5, 6, 7, 8, 9]

这里充分的体现出from_iterable对可迭代对象的展开操作,将camera_param_np变为了[1, 2, 3, 4, 5, 6, 7, 8, 9],也就是直接将不同的numpy数组直接首尾拼接于一起,随后又有self的功能,从如下文章可得该处内容为实例属性,俗话说就是self为实例对象本身,然后self.camera_param_np就为该实例赋予了该属性(这段话不是这段案例代码,而是第四节开始的那个分享的源代码的含义解释)。

itertools.chain.from_iterable的应用场景而言,如果我们需要对这些数组中的所有元素进行统一处理,如求和、 查找最大值等操作,使用itertools.chain.from_iterable便可避免嵌套循环,使代码更加简洁和高效,个人觉着这是在编程中比较有用的一个点!!!

camera_param_np = [np.array([1, 2, 3]), np.array([4, 5, 6]), np.array([7, 8, 9])]

total_sum = sum(itertools.chain.from_iterable(camera_param_np))
total_max = max(itertools.chain.from_iterable(camera_param_np))
print(total_sum) #输出 45
print(total_max) #输出 9
Logo

GitCode 天启AI是一款由 GitCode 团队打造的智能助手,基于先进的LLM(大语言模型)与多智能体 Agent 技术构建,致力于为用户提供高效、智能、多模态的创作与开发支持。它不仅支持自然语言对话,还具备处理文件、生成 PPT、撰写分析报告、开发 Web 应用等多项能力,真正做到“一句话,让 Al帮你完成复杂任务”。

更多推荐