python迭代器(Iterator)、生成器(Generator)和生成器表达式(Generator expressions)
一、迭代器(iterator)
官方定义(https://docs.python.org/zh-cn/3/glossary.html#term-generator):用来表示一连串数据流的对象。重复调用迭代器的 __next__() 方法(或将其传给内置函数 next())将逐个返回流中的项。当没有数据可用时则将引发 StopIteration 异常。到这时迭代器对象中的数据项已耗尽,继续调用其 __next__() 方法只会再次引发 StopIteration 异常。迭代器必须具有 __iter__() 方法用来返回该迭代器对象自身,因此迭代器必定也是可迭代对象,可被用于其他可迭代对象适用的大部分场合。
官方迭代器指导(tutorial) https://docs.python.org/zh-cn/3/tutorial/classes.html#iterators
Python 支持在容器(container)中进行迭代(iteration)的概念。容器对象(例如 list)在你每次向其传入 iter() 函数或是在 for 循环中使用它时都会产生一个新的迭代器。
下面解析之。
迭代(iteration)是 Python 最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
可迭代对象必须实现__iter__, 迭代器必须实现__iter__和__next__
如何判断可迭代对象(iterable)和迭代器(iterator)呢?
可迭代对象(iterable)
官方定义(https://docs.python.org/zh-cn/3/glossary.html#term-iterable ):能够逐一返回其成员项的对象。 可迭代对象的例子包括所有序列类型 (例如 list, str 和 tuple) 以及某些非序列类型例如 dict, 文件对象 以及定义了 __iter__() 方法或是实现了 序列 语义的 __getitem__() 方法的任意自定义类对象。
可迭代对象可用于 for 循环以及许多其他需要一个序列的地方(zip()、map() ...)。当一个可迭代对象作为参数传给内置函数 iter() 时,它会返回该对象的迭代器。这种迭代器适用于对值集合的一次性遍历。在使用可迭代对象时,你通常不需要调用 iter() 或者自己处理迭代器对象。for 语句会为你自动处理那些操作,创建一个临时的未命名变量用来在循环期间保存迭代器。
☆使用Iterable模块可以判断对象是否是可迭代的:
from collections.abc import Iterable #引入
s="hello" #定义字符串
l=[1,2,3,4] #定义列表
t=(1,2,3) #定义元组
d={'a':1} #定义字典
set1={1,2,3,4} #定义集合
f=open("d://a.txt") #定义文本,注意a.txt需要存在
# 查看是否都是可迭代的
print(isinstance(s,Iterable)) #运行结果True
print(isinstance(l,Iterable)) #运行结果True
print(isinstance(t,Iterable)) #运行结果True
print(isinstance(d,Iterable)) #运行结果True
print(isinstance(set1,Iterable)) #运行结果True
print(isinstance(f,Iterable)) #运行结果True
运行之,参见下图:
☆使用Iterator模块可以判断对象是否是迭代器:
from collections.abc import Iterator
s="hello"
l=[1,2,3,4]
t=(1,2,3)
d={'a':1}
set1={1,2,3,4}
f=open("d://a.txt") #定义文本,注意a.txt需要存在
# 查看是否都是可迭代的
print(isinstance(s,Iterator)) #运行结果False
print(isinstance(l,Iterator)) #运行结果False
print(isinstance(t,Iterator)) #运行结果False
print(isinstance(d,Iterator)) #运行结果False
print(isinstance(set1,Iterator)) #运行结果False
print(isinstance(f,Iterator)) #运行结果True
运行之,参见下图:
由此可知,只有文件是迭代器,所以可以直接使用next(),而不需要转换成迭代器。
迭代器可以使用函数next()来取值;使用内置函数iter可以将列表转换成一个列表迭代器,使用next()获取值。
需要注意的是,可迭代对象(Iterable)不一定是⼀个迭代器(Iterator),如字符串对象是⼀个可迭代对象,但不是⼀个迭代器,这意味着它⽀持迭代(Iteration),但我们不能直接对其进⾏迭代操作——不能直接使用内置函数next(),怎样才能对它实施迭代操作呢?使用⼀个内置函数iter(),它将根据⼀个可迭代对象返回⼀个迭代器对象。例如:
my_string = "Hello"
next(my_string) # 出错
my_iter = iter(my_string) #可用内置函数iter()将?个可迭代对象返回?个迭代器对象
next(my_iter) # 不会出错
参见下图:
迭代器的创建
☆可以利用内置函数iter()和一个序列(如字符串等)来创建。例如:
i = iter("abcd")
from collections.abc import Iterator #使用Iterator模块可以判断对象是否是迭代器
print(isinstance(i,Iterator)) # True,是迭代器
next(i) #可以使用内置函数next()来遍历迭代器的元素
参见下图:
下面使用内置函数iter可以将列表转换成一个列表迭代器,使用next()获取值,一次值取一个值,当值取完了,再使用一次next()的时候,会报异常StopIteration,可以通过异常处理的方式来避免,try-except-else语句就是一个最常用的异常处理结构,例如:
my_list=[1,2,3]
li=iter(my_list) #将列表转换成一个列表迭代器
while True:
try:
print(next(li))
except StopIteration:
print("Over")
break
else:
print("get!")
运行之,参见下图:
☆自定义迭代器类
把一个类作为一个迭代器使用需要在类中实现两个方法 __iter__() 与 __next__() 。
下面给出一个数数的例子:
class MyCounter():
def __init__(self, start, stop):
self.start = start
self.stop = stop
def __iter__(self):
return self
def __next__(self):
if self.start <= self.stop:
x = self.start
self.start += 1
else:
raise StopIteration
return x
C = MyCounter(10,12)
print(next(C))
print(next(C))
请注意,有了def __iter__(self)和 def __next__(self),类 MyCounter 就是迭代器了。
运行输出:
10
11
从字面意思,迭代就是重复反馈过程的活动,其目的通常是为了比较所需目标或结果,在python中可以用迭代器来实现。
优点:
迭代器在取值的时候是不依赖于索引的,这样就可以遍历那些没有索引的对象,比如字典和文件;
迭代器与列表相比,迭代器是延迟计算(惰性求值:azy evaluation),更节省内存。
【迭代器与列表的区别在于,构建迭代器的时候,不像列表把所有元素一次性加载到内存,而是以一种延迟计算(lazy evaluation)方式返回元素,比如列表含有中一千万个整数,需要占超过400M的内存,而迭代器只需要几十个字节的空间。因为它并没有把所有元素装载到内存中,而是等到调用 next 方法时候才返回该元素(按需调用 call by need 的方式,本质上 python的for 语句循环就是不断地调用迭代器的next方法)】
缺点:
无法获取迭代器的长度,没有列表灵活;
只能往后取值,不能倒着取值。
二、生成器(generator)
官方定义(https://docs.python.org/zh-cn/3/glossary.html#term-generator):返回一个 generator iterator 的函数。它看起来很像普通函数,不同点在于其包含 yield 表达式以便产生一系列值供给 for-循环使用或是通过 next() 函数逐一获取。
通常是指生成器函数,但在某些情况下也可能是指 生成器迭代器。如果需要清楚表达具体含义,请使用全称以避免歧义。
官方生成器指导(tutorial)https://docs.python.org/zh-cn/3/tutorial/classes.html#generators
在 Python 中,使用了 yield语句 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值。并在下一次执行 next() 方法时从当前位置继续运行。
yield 表达式和语句仅在定义 generator 函数时使用,并且仅被用于生成器函数的函数体内部。 在函数定义中使用 yield 就足以使得该定义创建的是生成器函数而非普通函数。
当调用生成器(generator)函数时,它会返回一个称为生成器的迭代器。然后,该生成器控制生成器函数的执行。当调用生成器的一个方法时执行开始。此时,执行进行到第一个yield表达式,在那里它再次挂起,将表达式列表的值返回给生成器的调用者,或者如果省略表达式列表,则返回None。所谓挂起,意思是保留所有局部状态,包括局部变量的当前绑定等。当调用生成器的一个方法来恢复执行时,函数可以继续执行,就像yield表达式只是另一个外部调用一样。恢复后yield表达式的值取决于恢复执行的方法。如果使用__next__()(通常通过for或next()内建函数),则结果为None。否则,如果使用send()方法,那么结果将是传递给该方法的值。
yield 语句在语义上等同于 yield 表达式。官方说明如下:
yield 语句https://docs.python.org/zh-cn/3/reference/simple_stmts.html#the-yield-statement
yield 表达式https://docs.python.org/zh-cn/3/reference/expressions.html#yieldexpr
下面解析之。
下面来看一个简单的生成器
def my_yield():
print('first')
yield 1
g=my_yield()
print(g)
运行之,参见下图:
生成器也是一个迭代器,测试上面简单的生成器是否是迭代器代码如下:
from collections.abc import Iterator
def my_yield():
print('first')
yield 1
g=my_yield() #
print(isinstance(g,Iterator)) #运行结果True
运行之,参见下图:
生成器的执行过程
下面给出一个长点的例子,由此了解执行流程
def my_yield():
print('first')
yield 1
print('second')
yield 2
print('Third')
yield 3
g=my_yield()
next(g)
next(g)
next(g)
运行之,参见下图:
1.定义生成器my_yield,并将其赋值给了g
def my_yield():
g=my_yield()
2.开始第一次执行next(),开始执行生产器函数 ,打印第一语句,遇到yileld的时候暂停,并返回一个1,如果你想打印返回值的话,这里会显示1
print('first')
yield 1
3.再执行2次,打印字符串(每执行一次都会暂停一下)
print('second')
yield 2
print('Third')
yield 3
4.如果再加一次next()就会报出StopIteration异常了
下面给出生成器的例子,分别使用next()函数、__next()__方法和for语句读取元素值的示例:
def test(n): # 定义生成器函数
for i in range(n): # 迭代列表
yield i*i # 函数定义中使用了 yield
g = test(5) # 调用生成器函数,生成一个生成器对象
print(next(g)) # 读取第1个元素值
print(g.__next__()) # 读取第2个元素值
for i in g: # 读取后面3个元素值
print(i)
运行之,参见下图:
生成器中使用send()方法的示例
def down(n): # 定义生成器函数
while n >= 0: # 设置递减循环的条件
m = yield n # 注意这句有yield,每次迭代生成的值并返回
if m: # 当条件为True,则改写递减变量的值
n = m
else: # 正常情况下,m为None,则递减
n -= 1
d = down(5) # 调用生成器函数
for i in d:
print(i) # 打印元素
#if i == 5: # 当打印完第一个元素后,
# d.send(3) # 注意这句,修改yield表达式的值为3
运行之,参见下图:
如果不设置
if i == 5: # 当打印完第一个元素后,
d.send(3) # 注意这句,修改yield表达式的值为3
或注释掉,则
运行结果如下:
总之,用简单的话讲,迭代(Iteration)就是从某个地方(比如一个列表)取出一个元素的过程。当我们使用一个循环来遍历某个东西时,这个过程本身就叫迭代。
生成器(Generators)也是一种迭代器,但是你只能对其迭代一次。这是因为它们并没有把所有的值存在内存中(这样做会消耗大量资源),而是在运行时生成值。若想通过遍历来使用它们,要么用一个"for"循环,要么将它们传递给任意可以进行迭代的函数或结构。大多数时候生成器是以函数来实现的。然而,它们并不返回一个值,而是yield(生出、产生)一个值。
下面,以计算斐波那契数列为例,说明是否用生成器的区别
先看使用生成器
#计算斐波那契数列,使用生成器版
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b
#现在可以这样使用生成器
for x in fibon(5000):
print(x)
用这种使用生成器方式,我们可以不用担心它会使用大量资源(内存)。下面是不使用生成器方式
#计算斐波那契数列,不用生成器版
def fibon(n):
a = b = 1
result = []
for i in range(n):
result.append(a)
a, b = b, a + b
return result
#现在可以这样使用生成器,思考当调用时输入的参数很大时可能发生的问题
for x in fibon(5000):
print(x)
思考当调用时输入的参数很大时可能发生的问题——它会使用大量资源(内存)。
三、生成器表达式(Generator expressions)
也有人称为生成器推导式
生成器表达式会产生一个新的生成器对象。 其句法与推导式相同,区别在于它是用圆括号而不是用方括号或花括号括起来的。例如: (x*y for x in range(10) for y in range(x, x+10))
【关于推导式(comprehension)可参见 https://blog.youkuaiyun.com/cnds123/article/details/117395558 】
列表推导和列表生成器表达式之间的差异十分重要,但很微妙。生成器推导式的结果是一个生成器对象。生成器对象类似于迭代器对象,具有惰性求值的特点,只在需要时生成新元素,比列表推导式具有更高的效率,空间占用非常少,尤其适合大数据处理的场合。
下面看一下列表推导和生成器表达式之间的差异:
L = [(i+2)**2 for i in range(10)] #列表推导式
print(L)
G = ((i+2)**2 for i in range(10)) #列表生成器表达式
print(G)
运行结果如下
使用生成器对象的元素时,可以使用生成器对象的__next__()方法或者内置函数next()进行遍历,或者直接使用for循环来遍历其中的元素。
列表生成器表达式对象元素的遍历示例:
G = ((i+2)**2 for i in range(10)) #列表生成器表达式
print(next(G)) # 读取第一个元素
print(G.__next__()) # 读取第二个元素
print(next(G)) # 读取第三个元素
for i in G: # 读取剩余的元素
print(i)
运行结果如下:
还可以根据需要将生成器表达式转化为列表或元组
将生成器对象转换为列表或元组示例:
G = ((i+2)**2 for i in range(10)) #列表生成器表达式
L=list(G) #将生成器对象转换为列表
print(L)
G = ((i+2)**2 for i in range(10)) #列表生成器表达式
T=tuple(G) #将生成器对象转换为元组
print(T)
运行结果如下:
进一步学习:
Python迭代器详解 https://blog.51cto.com/nxlhero/2480488
python可迭代对象、迭代器与生成器 https://mp.weixin.qq.com/s/--zEhMcQ6nzwzFguOVxaVg
生成器(generator) https://www.cnblogs.com/mswei/p/9283386.html