一篇文章看懂 Python iterable, iterator 和 generato

本文从演变角度介绍Python中iterable、iterator和generator概念。先讲简单循环及早期Python利用getitem实现循环,接着引入惰性求值解决内存和时间问题,介绍iterator协议,还阐述iter协议让iterator可用于for循环,最后讲解generator函数和表达式,总结了它们的定义、机制和关联。

Python 中的 iterable, iterator 以及 generator,一直是非常亲密但是难以区分的概念。nvie 有一个很好的 帖子 阐述了它们之间的关系,但是内容偏向于概括和总结,对于新手来说仍然难以理解。Fluent Python 的第 14 章也有非常好的演绎,但是我认为它对「为什么要有这种语言特性」缺乏阐释。我试图从演变的角度,总结这些概念的来源和演化,以得到一个符合逻辑和容易理解的版本。

Simple Loop
几乎每一个 Python 入门教程,都会用类似下面的代码来讲述最简单的 for 循环:

l = [2, 1, 3]
for i in l:
… print(i)
2
1
3
在 Python 中,执行 l[i] 实际上是调用了 l 的 getitem 函数。list 类型中会实现了这个函数,用来返回某个 index 下的元素。而早期 Python 的实现上利用了这个操作。上面的 for 循环实际上是从 l[0] 开始取元素,等价于这段代码:

i = 0
while True:
try:
print(l[i]) # 亦可是 print(l.getitem(i))
i += 1
except IndexError:
break
Python 内置的大多数容器类型(list, tuple, set, dict)都支持 getitem,因此它们都可以用在 for … in 循环中。如果你有个自定义类型也想用在循环中,你需要在类里面实现一个 getitem 函数,Python 就会识别到并使用它。Fluent Python 一书提供了一个例子。

Lazy Evaluation
在上面的代码例子中,l 的值是全部被加载到内存中,再在循环中被一个一个取出来的。设想这样一个场景,你要从数据库中查询出一千万条数据做处理,

如果全部加载到内存,可能会将内存撑满
在处理第一条数据前,需要等待大量时间从数据库中取出这些数据
一些特殊的场景下,你可能并不需要对全部的数据做处理,比如处理到第五百万条数据时即可以结束
前辈们提出了惰性求值( Lazy Evaluation )来解决这个问题。有些地方也叫它「延迟加载」「懒加载」等。它的基本理念是「按需加载」,在上面的例子中,可以将取数据过程变成一页页取,比如先取 100 条数据进行处理,处理完后再取下一个 100 条,直至全部取完。

The Iterator Protocol
Python 为了在语言层面支持 lazy evaluation,给出了 iterator 协议。如果一个类实现了 next 函数,并且:

每次调用该函数,都可以返回一个新的数据
没有新的数据时,调用它抛出 StopIteration 异常(当然如果序列是无限长,那么可以不抛)
那么这个类即支持 iterator 协议。于是「按需加载」,即可以通过每次 next 被调用时去实现。Python 的内置函数 next(iterator) 实际上是调用 iterator.next。下面的例子给出一个 iterator 的实现,用来按需地计算出下一个斐波那契数:

class FibonacciIterator:
… def init(self, maximum):
… # 为了简单,将初始值设为 1, 2 而不是 0, 1
… self.a, self.b = 1, 2
… self.maximum = maximum
… def next(self):
… fib = self.a
… if fib > self.maximum:
… raise StopIteration
… self.a, self.b = self.b, self.a + self.b
… return fib

f = FibonacciIterator(5)
next(f)
1

next(f)
2

next(f)
3

next(f)
5

next(f)

StopIteration occured

Enhanced iterable
上文中的 FibonacciIterator 已经实现了按需加载,那可以直接将它用在 for 循环中吗?试试:

for i in FibonacciIterator(5):
… print(i)

Traceback (most recent call last):
File “”, line 1, in
TypeError: ‘FibonacciIterator’ object is not iterable
可以看到有 is not iterable 的报错。按上一节的描述,早期的 Python 仅在一个类有 getitem 函数时 Python 才将它当成 iterable,同时为了配合新的 iterator 的机制,Python 在 2.2 版本中将 iter 协议加入了进来:

一个类如果实现了 iter 并返回一个 iterator,那么它是 iterable 的
如果没有实现 iter,但是有 getitem,那么它仍然是 iterable 的
那么对于一个 iterator,如果你想能在 for 循环中使用,只需要实现一个 iter 函数返回自己就可以了:

class FibonacciIterator:
… # **函数实现省略,见上文
… def iter(self):
… return self

for i in FibonacciIterator(5):
… print(i)
1
2
3
5
Generator Function
上面虽然花了挺大篇幅讲述 iterator 的机制,但是事实上 Python 中以 next 方式来实现 iterator 的并不多。Python 在 2.2 版本中支持了 iterator,但是也同时给出了另外一种更灵活方便,也更重要的机制 —— generator。

识别 generator 的标志在 yield 关键字。上文的斐波那契数列,用 generator 来实现是:

def fib(maximum):
… a, b = 1, 2
… while a <= maximum:
… yield a
… a, b = b, a+b

for i in fib(5):
… print(i)
1
2
3
5
上面的 fib() 虽然也是用 def 定义,但是它的函数体中有 yield 关键字,因此它不是个普通函数,而是个 generator function。它返回的是一个 generator object,即 fib(5) 处。generator 是一种在语言层面被支持的 iterator,它的规则是:

next() 调用一个 generator 时,Python 会执行函数体到下一个 yield 语句,并将 yield 后的值作用 next() 的返回值;然后该函数的执行暂停,直至下一次 next() 调用时,继续执行到下一个 yield
当整个函数体被执行完毕时,抛出 StopIteration 异常
这套规则清晰直观,可以将它套用在上面代码中验证一下。yield 及 generator 是非常重要的机制,不仅仅在于它比 iterator 更简单直观,而在于它同时引入了一种控制语言运行的机制。对于普通函数,一旦执行则必须从函数头执行到函数尾,之后才把控制权交给调用方;但是对于 generator function,你可以只执行一小段代码,即把控制权交回调用方(yield 时)。这种机制也对后面提出 coroutine 及 asyncio 中起到了重要的作用。

Generator Expression
试试运行下面的代码:

sum([i**2 for i in range(11)])
385

sum((i**2 for i in range(11)))
385

sum(i**2 for i in range(11))
385
后两种写法,跟第一种有什么区别呢?后两种即是 generator expression,是一种方便生成 generator 的语法糖,形式上是用括号包裹的 list comprehension。背后的理念大概是这样:list comprehension 是用来生成元素的,generator 也是用来生成元素的,那为什么不提供一种类似 list comprehension 语法的 expression 来表示 generator 呢?它跟下面的代码是等价的:

def gen():
for i in range(11):
yield i**2

sum(gen())
至于第三种写法为啥不用括号包起来,是 Python 为了可读性故意设计的,如果作为唯一的函数参数使用,则可以省略。

总结
定义上:

实现了 itergetitem,并满足一定规则的类型是 iterable 的,它的实例是个 iterable
实现了 next 并满足一定规则的类型,它是一种 iterator,它的实例是个 iterator
在函数体中使用了 yield 的函数是 generator function ;使用了括号包裹的类 list comprehension 是 generator expression。它们都会产生 generator
语言机制上:

for … in 循环所消费的对象,需要是个 iterable
它们之间的关联:

容器类型(list, dict, etc.)大部分是 iterable ;dict 有多个不同函数生成不同用途的 iterator
iterator 大部分情况下是个 iterable
generator 是个 iterator,同时是个 iterabl
---------------------- 作者:giy.hkv (http://siligence.ai/column/index.php?page=1)

先看效果: https://renmaiwang.cn/s/jkhfz Hue系列产品将具备高度的个性化定制能力,并且借助内置红、蓝、绿三原色LED的灯泡,能够混合生成1600万种不同色彩的灯光。 整个操作流程完全由安装于iPhone上的应用程序进行管理。 这一创新举措为智能照明控制领域带来了新的启示,国内相关领域的从业者也积极投身于相关研究。 鉴于Hue产品采用WiFi无线连接方式,而国内WiFi网络尚未全面覆盖,本研究选择应用更为普及的蓝牙技术,通过手机蓝牙与单片机进行数据交互,进而产生可调节占空比的PWM信号,以此来控制LED驱动电路,实现LED的调光功能以及DIY调色方案。 本文重点阐述了一种基于手机蓝牙通信的LED灯设计方案,该方案受到飞利浦Hue智能灯泡的启发,但考虑到国内WiFi网络的覆盖限制,故而选用更为通用的蓝牙技术。 以下为相关技术细节的详尽介绍:1. **智能照明控制系统**:智能照明控制系统允许用户借助手机应用程序实现远程控制照明设备,提供个性化的调光及色彩调整功能。 飞利浦Hue作为行业领先者,通过红、蓝、绿三原色LED的混合,能够呈现1600万种颜色,实现了全面的定制化体验。 2. **蓝牙通信技术**:蓝牙技术是一种低成本、短距离的无线传输方案,工作于2.4GHz ISM频段,具备即插即用强抗干扰能力。 蓝牙协议栈由硬件层软件层构成,提供通用访问Profile、服务发现应用Profile以及串口Profiles等丰富功能,确保不同设备间的良好互操作性。 3. **脉冲宽度调制调光**:脉冲宽度调制(PWM)是一种高效能的调光方式,通过调节脉冲宽度来控制LED的亮度。 当PWM频率超过200Hz时,人眼无法察觉明显的闪烁现象。 占空比指的...
<think>好的,用户现在想了解PythonIterableIterator的概念及区别。根据用户提供的引用内容,我需要先理清这两个术语的定义以及它们之间的关系。 首先,引用[1]提到IterableIterator是不同的概念,Iterable是可迭代对象,而Iterator是迭代器。引用[2]指出生成器也是一种迭代器。引用[3]解释了Iterator必须实现__iter__方法,这样它们就可以被当作Iterable使用,例如在for循环中。引用[4]详细说明了如何判断对象是Iterable还是Iterator,以及for循环的工作原理。引用[5]提到Iterable需要包含__iter__或__getitem__方法,而Iterator必须实现__next____iter__。 用户的问题集中在定义区别上,所以需要明确两者的核心差异。Iterable是可以被迭代的对象,比如列表、元组,而Iterator是实际执行迭代的对象,每次产生一个值。关键点在于IteratorIterable的子集,所有Iterator都是Iterable,但反之不成立。 需要解释为什么Python要区分这两者。引用[4]提到,Iterator维护迭代状态,而Iterable不保存状态。例如,列表是可迭代的,但每次迭代时会生成新的迭代器,这样多个循环可以独立进行。而迭代器只能遍历一次,因为状态会改变。 还要注意如何判断一个对象是否是IterableIterator。通过isinstancecollections.abc中的类来判断。例如,使用isinstance(obj, Iterable)检查是否为可迭代对象,而Iterator需要同时有__next____iter__方法。 最后,用户可能想知道实际应用中的例子,比如自定义迭代器或生成器的使用。根据引用[5],生成器是创建迭代器的简便方式,可以用yield语句实现。 总结回答结构:先定义两者,再对比区别,接着解释设计原因,最后给出实际应用示例。确保引用相关引用内容支持每个观点,并在末尾添加相关问题。</think>#### Python IterableIterator 的深度解析 --- ### 一、核心定义 1. **Iterable(可迭代对象)** - 定义:任何实现了 `__iter__()` 方法 **或** `__getitem__()` 方法的对象[^4][^5] - 特点:可被多次遍历,每次遍历会生成新的迭代器 - 示例:列表、元组、字符串、字典 ```python my_list = [1, 2, 3] # 列表是 Iterable ``` 2. **Iterator(迭代器)** - 定义:实现了 `__iter__()` `__next__()` 方法的对象[^2][^3] - 特点: - 保存迭代状态(如当前遍历位置) - 只能单向遍历一次(遍历结束需重置) - 示例:文件对象、生成器 ```python my_iter = iter([1, 2, 3]) # 通过 iter() 获得 Iterator ``` --- ### 二、关键区别 | 特性 | Iterable(可迭代对象) | Iterator(迭代器) | |---------------------|-----------------------------|--------------------------------| | **是否保存状态** | 否(每次迭代生成新迭代器) | 是(记录当前位置) | | **能否重复遍历** | 是 | 否(耗尽后需重新生成迭代器) | | **核心方法** | `__iter__()` 或 `__getitem__()` | `__iter__()` `__next__()` | | **与生成器的关系** | 非生成器 | 生成器是 Iterator 的子类[^2] | --- ### 三、设计逻辑解析 #### 为什么区分 Iterable Iterator? 1. **职责分离原则** - Iterable:仅声明可迭代能力,不关心具体迭代逻辑[^4] - Iterator:负责实现具体迭代逻辑(如 `__next__()`) ```python # Iterable 示例:列表本身不保存状态 lst = [1, 2, 3] iter1 = iter(lst) # 生成新迭代器 iter2 = iter(lst) # 生成另一个新迭代器 ``` 2. **资源优化** - 大型数据集(如文件流)通过 Iterator 按需加载数据,避免内存爆炸[^3] ```python with open("large_file.txt") as f: # f 是 Iterator for line in f: # 逐行读取,不一次性加载全部内容 process(line) ``` --- ### 四、类型判断方法 1. **代码验证** ```python from collections.abc import Iterable, Iterator # 判断是否为 Iterable isinstance([1, 2, 3], Iterable) # True isinstance(iter([1, 2, 3]), Iterable) # True(IteratorIterable 的子类) # 判断是否为 Iterator isinstance([1, 2, 3], Iterator) # False(列表是 Iterable 但不是 Iterator) isinstance(iter([1, 2, 3]), Iterator) # True ``` --- ### 五、`for` 循环的底层机制 1. **执行步骤** - 调用 `iter(obj)` 获取 Iterator - 循环调用 `__next__()` 直到抛出 `StopIteration` ```python # 等价于: my_iter = iter([1, 2, 3]) while True: try: item = next(my_iter) print(item) except StopIteration: break ``` --- ### 六、自定义 Iterator 的两种方式 1. **经典实现** ```python class Counter: def __init__(self, start, end): self.current = start self.end = end def __iter__(self): # 返回 Iterator 自身 return self def __next__(self): # 实现迭代逻辑 if self.current > self.end: raise StopIteration else: self.current += 1 return self.current - 1 # 使用 counter = Counter(1, 3) for num in counter: print(num) # 输出 1, 2, 3 ``` 2. **生成器实现(更简洁)** ```python def counter_generator(start, end): current = start while current <= end: yield current current += 1 gen = counter_generator(1, 3) # gen 是生成器(属于 Iterator) for num in gen: print(num) # 输出 1, 2, 3 ``` --- ### 七、常见误区 #### 误区1:所有 Iterable 都是 Iterator **反例**:列表是 Iterable,但不是 Iterator ```python lst = [1, 2, 3] next(lst) # 报错:TypeError(列表没有 __next__() 方法) ``` #### 误区2:Iterator 遍历后不可恢复 **验证**: ```python my_iter = iter([1, 2, 3]) list(my_iter) # [1, 2, 3] list(my_iter) # [](已耗尽) ``` --- ### 八、相关问题 1. 为什么文件对象遍历一次后内容就“消失”了? 2. 如何让自定义类同时支持 `for` 循环索引访问?[^4] 3. 生成器表达式与列表推导式有何性能差异?[^2] 4. 为什么 `dict.keys()` 返回的是视图对象而非列表?[^5] 5. 如何实现反向迭代器(如 `reversed()` 的原理)?[^3] --- [^1]: Iterable Iterator 的分离设计遵循单一职责原则,提升代码可维护性。 [^2]: 生成器通过 `yield` 简化迭代器实现,支持惰性求值。 [^3]: Iterator 必须实现 `__iter__()` 以兼容需要 Iterable 的接口(如 `for` 循环)。 [^4]: 通过 `__getitem__()` 方法可使对象成为 Iterable,但优先使用 `__iter__()`。 [^5]: 判断 Iterable 时需注意 `isinstance(obj, Iterable)` 可能漏判 `__getitem__()` 实现的类。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值