1. 前言
迭代是一种遍历容器类型对象(例如字符串、列表、字典等等)的方式。例如,我们说迭代一个字符串“abc”,指的就是从左往右依次地、逐个地取出它的全部字符的过程(可以将迭代理解为遍历)。Python的迭代机制依赖于两个特殊方法:__iter__
和__next__
。__iter__
方法返回一个迭代器对象,而__next__
方法则负责返回迭代器的下一个值。当没有更多的值可返回时,__next__
会抛出StopIteration异常。(这一段看不懂没关系!!!继续往后看就明白了)
我觉得可迭代对象(iterable)和迭代器(iterator)的概念有点抽象,容易把我搞懵,那怎么办呢。其实我们可以使用python的内置函数isinstance(obj, classinfo)
来查询某个对象(obj)是否为指定的类型(classinfo),该函数的返回值为True或False。也就是说,我们可以使用内置函数isinstance(obj, classinfo)
来判断该对象是否为可迭代对象或迭代器对象。代码示例如下(代码1):
# 代码1
from collections.abc import Iterable, Iterator
# 需要导入Iterable, Iterator
obj_list = [1,2,3,4]
obj_tuple = (1,2,3)
print(isinstance(obj_list, Iterable)) # 输出:True
print(isinstance(obj_list, Iterator)) # 输出:False
print(isinstance(obj_tuple, Iterable)) # 输出:True
print(isinstance(obj_tuple, Iterator)) # 输出:False
由上面代码可知:列表、元组都是可迭代对象(iterable),都不是迭代器对象(iterator)。下面介绍两个和迭代器相关的内置函数:iter()
和next()
。
(1)iter()
:该函数可以将可迭代对象(iterable)转换为迭代器(iterator),即iter()
函数用于创建一个迭代器对象。iter()
函数实际映射到类中的__iter__
方法,即执行iter()
函数会调用类中的__iter__
方法。
(2)next()
:该函数可以返回迭代器(iterator)中的下一个元素,如果没有更多的元素,则引发StopIteration 异常。如果我们使用内置函数next()
对迭代器进行遍历,在这个过程中,是在调用迭代器的__next__
方法。即:next()
函数内部调用迭代器的__next__
方法。
from collections.abc import Iterable, Iterator
my_list = [1,2,3]
iter_list = iter(my_list) # 将列表转换成迭代器对象
print(isinstance(iter_list, Iterable)) # 输出:True
print(isinstance(iter_list, Iterator)) # 输出:True
print(next(iter_list)) # 输出:1
print(next(iter_list)) # 输出:2
print(next(iter_list)) # 输出:3
print(next(iter_list)) # 输出:StopIteration
2. for循环遍历的原理
大家对for循环不陌生吧,其实在for循环中就使用了iter()
和next()
这两个内置函数。Python的for循环本质上就是通过不断调用迭代器的next()
函数实现的,直到捕获 StopIteration 异常为止。在进行for循环遍历过程中就做两件事:(1)第一件:使用iter()
函数将可迭代对象(iterable)转为迭代器对象(iterator);(2)第二件:反复对迭代器对象(iterator)使用next()
函数,直到迭代器中的元素被获取完。最后捕获StopIteration异常,退出循环。对了,对于这个for循环语句for ... in ...
,关键字in
后面需要跟可迭代对象(iterable)。顺便再说一句:迭代器(iterator)一定是可迭代对象(iterable),即迭代器也是可迭代的。
my_list = [1,2,3]
for i in my_list:
print(i)
# python 内部是这样转化的:
my_list = [1,2,3]
for i in iter(my_list ): # 将列表(可迭代对象)转化迭代器(iterator)
print(i)
# for循环会自动处理这个StopIteration异常,以便结束for循环。
我们也可以通过next()
函数来遍历列表中的元素,代码示例如下(代码2)。由下面代码可知:next()
函数依次取出迭代器中的元素,直到抛出StopIteration异常(如果不将列表(可迭代对象)通过iter()
函数转换为迭代器对象,是没法使用next()
函数来取出列表中的元素)。
# 代码2
my_list = [1,2,3]
iter_list = iter(my_list) # 将列表转换成迭代器对象
print(next(iter_list)) # 输出:1
print(next(iter_list)) # 输出:2
print(next(iter_list)) # 输出:3
print(next(iter_list)) # 输出:StopIteration
3. 可迭代对象(iterable)
如果一个对象实现了__iter__
方法,那么这个对象就是可迭代对象。常见的可迭代对象有:列表(list)、元组(tuple)、字典(dict)和文件对象等等。下面代码中(代码3),在MyList类中实现了__iter__方法,所以对象obj_list是可迭代对象(iterable),但对象obj_list不是迭代器(iterator)。虽然对象obj_list是可迭代对象(iterable),但它却不能被迭代(遍历)。
# 代码3
from collections.abc import Iterable, Iterator
class MyList:
def __init__(self,*args) -> None:
self.data = list(args)
def __iter__(self):
print("__iter__被执行了...")
return self
obj_list = MyList(2,4,6)
print(isinstance(obj_list, Iterable)) # 输出:True
print(isinstance(obj_list, Iterator)) # 输出:False
4. 迭代器(iterator)
在Python中,如果一个对象同时实现了__iter__
方法和__next__
方法,那它就是迭代器。也就是说,迭代器是一个实现了__iter__
和__next__
方法的对象。__iter__
方法返回迭代器对象自身,而 __next__
方法返回下一个元素。换句话说,迭代器是一个可以逐个返回元素的对象。如果在python的类中定义了__next__
和__iter__
方法,生成的实例对象可以通过for循环遍历来取,并且先调用__iter__
方法,再调用__next__
方法。示例代码如下所示(代码4):
# 代码4
from collections.abc import Iterable, Iterator
class MyList:
def __init__(self,*args) -> None:
self.data = list(args)
def __iter__(self):
print("__iter__被执行了...")
return self
def __next__(self):
pass
obj_list = MyList(2,4,6)
print(isinstance(obj_list, Iterable)) # 输出:True
print(isinstance(obj_list, Iterator)) # 输出:True
由于我们在类中实现了__iter__
和__next__
方法,所以对象obj_list既是可迭代对象(iterable),又是一个迭代器(iterator)。由此可得出一个结论:迭代器(iterator)一定是可迭代对象(iterable)。
我们知道iter()
函数可以将可迭代对象(iterable)转换为迭代器(iterator),而迭代器(iterator)又是个可迭代对象(iterable),那我们也可以将迭代器(iterator)转为迭代器(iterator)。听起来有点绕,干脆直接看下面的代码把。由下面代码可知:我们可以将迭代器再次转为迭代器,并且转换后的迭代器(iter_list_to_iter)和之前的迭代器(iter_list)内存地址(id)是一样的。
my_list = [1,2,3]
iter_list = iter(my_list) # 将列表转为迭代器
iter_list_to_iter = iter(iter_list) # 将迭代器转为迭代器
print(type(iter_list)) # 输出:<class 'list_iterator'>
print(type(iter_list_to_iter)) # 输出:<class 'list_iterator'>
print(id(iter_list)) # 输出:2370538873032
print(id(iter_list_to_iter)) # 输出:2370538873032
print(next(iter_list_to_iter)) # 输出:1
print(next(iter_list_to_iter)) # 输出:2
print(next(iter_list_to_iter)) # 输出:3
5. 自定义迭代器
代码如下所示(代码5):
from collections.abc import Iterable,Iterator
class MyList:
def __init__(self,*args) -> None:
self.data = list(args)
self.start = 0
def __iter__(self):
print("__iter__被执行了...")
return self
def __next__(self):
print("__next__被执行了...")
if self.start >= len(self.data):
# raise StopIteration用于提前终止一个迭代器中的循环
raise StopIteration
item = self.data[self.start]
self.start += 1
return item
obj_list = MyList(2,4,6)
print(isinstance(obj_list,Iterable)) # 输出:True
print(isinstance(obj_list,Iterator)) # 输出:True
for i in obj_list:
print(i)
输出:
True
True
__iter__被执行了...
__next__被执行了...
2
__next__被执行了...
4
__next__被执行了...
6
__next__被执行了...
小结
(1)迭代器(iterator): 如果一个对象同时实现了__iter__
方法和__next__
方法,那它就是迭代器;
(2)可迭代对象(iterable): 如果一个对象实现了__iter__
方法,那么这个对象就是可迭代对象。通常__iter__
方法需要返回一个实现了__next__
方法的对象,如果自己实现了,可以返回self
,当然这个返回值不是必须的。
(3)两者之间的关系:迭代器(iterator)一定是可迭代对象(iterable),反之则不成立。可迭代对象的__iter__
方法必须返回一个迭代器。
参考文章:一文看懂python的迭代器和可迭代对象
6. 生成器(generator)
生成器就是一种特殊的迭代器。如果某个函数中存在关键字yield
,那么该函数就是生成器函数。生成器函数是一种特殊的函数,可以在迭代过程中逐步产生值,而不是一次性返回所有结果。生成器的关键在于yield
语句。yield
语句的作用和return
语句有几分相似,都可以将结果返回。不同在于,生成器函数执行到yield
语句,返回结果的同时记录下函数内的状态,下次执行这个生成器函数,将从上次退出的位置(yield的下一句代码)继续执行。当生成器函数中的所有代码被执行完毕时,自动抛出 StopIteration
异常。
(1)生成器函数:下面代码中函数my_generator
包含关键字yield
,所以my_generator
就是一个生成器函数。我们调用生成器函数my_generator()
会返回一个生成器对象,并将该生成器对象赋给变量obj_generator。还有要记住一点:我们执行代码my_generator()
的时候,只是生成了一个生成器对象obj_generator,但并没有运行my_generator
这个生成器函数,当运行next(obj_generator)
的时候才会执行生成器函数my_generator。生成器的用法和迭代器相似,我们可以使用 next()
函数或__next__
方法来进行生成器对象的迭代。这是因为生成器其实就是创建迭代器的便捷方法,生产器会在背后自动定义 __iter__
和 __next__
方法。
# 生成器函数
def my_generator():
for i in range(4):
yield i
obj_generator = my_generator()
print(obj_generator) # 输出:<generator object gen at 0x0000029481B899C8>
print(type(obj_generator)) # 输出:<class 'generator'>
print(next(obj_generator)) # 输出:0
print(next(obj_generator)) # 输出:1
print(next(obj_generator)) # 输出:2
print(next(obj_generator)) # 输出:3
print(next(obj_generator)) # 输出:StopIteration
# print(obj_generator.__next__())和 print(next(obj_generator))等价
(2)生成器表达式:生成器对象 = (关于item的表达式 for item in 可迭代对象)
,是()
,不是[]
。利用生成器表达式可以产生一个生成器对象。
my_generator = (item*2 for item in [2,4,6])
print(type(my_generator)) # 输出:<class 'generator'>
# 使用for循环遍历生成器
for i in my_generator:
print(i) # 输出:4,8,12
print(next(my_generator)) # 输出:4
print(next(my_generator)) # 输出:8
print(next(my_generator)) # 输出:12
print(next(my_generator)) # 输出:StopIteration
参考文章:Python进阶干货速递!【超详细迭代器、生成器、装饰器使用教程】
7. 关键字yield和send()方法
7.1 关键字yield
上面只是简单介绍了关键字yield
,但说实话我对这个yiled
的具体用法还是不太了解,还是认为它比较抽象。下面就详细介绍一下关键字yield
。
首先,我们可以将yield
看成return
,即它的作用是在程序中返回某个值。废话少说,直接看下面代码。
def my_generator(): #1
for i in range(3): #2
temp = yield 6 #3
print(f"temp = {temp}") #4
obj_generator = my_generator() #5 生成一个生成器对象
print(next(obj_generator)) #6
print("-"*20) #7
print(next(obj_generator)) #8
print("-"*20) #9
print(next(obj_generator)) #10
输出:
6
--------------------
temp = None
6
--------------------
temp = None
6
我们可以将上面代码中的yield 6
看作是return 6
,也就是将值6
返回到函数调用处。下面介绍代码的执行流程(其实大家可以通过调试来了解代码的执行顺序)。
(1)首先我们在#1~#4
中定义了一个生成器函数my_generator
。然后在#5
处生成了一个生成器对象obj_generator
,但此时并没有真正的执行my_generator
这个生成器函数,只有当运行next(obj_generator)
的时候才会执行生成器函数my_generator
。
(2)在#6
处调用了next()函数,即next(obj_generator)
,此时生成器函数my_generator
才会真正的被执行。当执行至#3
处,遇见了关键字yield
(看成return就行),随后将值6
返回到#6
处,结束本次调用,此时并没有给temp赋值。
(3)在#6
处输出返回值6,接着在#7
处输出20个-
。
(4)接下来又要执行#8
处的语句print(next(obj_generator))
,和步骤(2)类似。只不过是从步骤(2)结束的位置开始执行,也就是要开始执行给temp赋值的操作。由于步骤(2)通过关键字yield
将值6
返回出去了,所以并没有值可赋给temp,即temp为空(None)。因此在#8
处会先输出temp = None
。接着继续执行for循环语句,当执行至#3
处,遇见了关键字yield
(看成return就行),结束本次调用,并将值6
返回到#8
处。因此在#8
处又会输出值6.
(5)接着执行#9
和#10
处的代码,和上面步骤类似,就不再说了。
结论:yield
和return
的区别是:return
用于返回一个值,结束函数的执行,常用语普通函数。yield用于定义生成器函数,它也可以返回一个值。但关键字yield
在返回值的同时,又记录生成器函数的状态,下次执行这个生成器函数,将从上次退出的位置(yield的下一句代码)继续执行。简而言之:yield
是一个暂停键,return
是个终止键(或结束键)。
7.2 send()方法
下面再介绍一下生成器中的send()
方法。还是直接看下面的代码吧,通过代码来理解该方法。
def my_generator(): #1
for i in range(3): #2
temp = yield 6 #3
print(f"temp = {temp}") #4
obj_generator = my_generator() #5 生成一个生成器对象
print(next(obj_generator)) #6
print("-"*20) #7
print(obj_generator.send(666)) #8
print("-"*20) #9
print(obj_generator.send(888)) #10
输出:
6
--------------------
temp = 666
6
--------------------
temp = 888
6
通过上面介绍关键字yield
可知:遇见了关键字yield(看成return就行),结束本次调用,此时并没有给temp赋值,所以temp为空(None)。那如果我们想给temp赋给一个指定的值怎么办呢,此时就需要send()
方法了。send()
方法会先给temp发送一个指定的值,然后执行next()
的作用,遇见下一个yield
,结束本次调用。上面代码的执行流程如下所示:
(1)上面说了在#6
处执行语句print(next(obj_generator)),
会输出值6,此时temp为None。
(2)我们执行#8
处的语句print(obj_generator.send(666))
,会通过send()
方法将值666
传递给变量temp,此时执行#4
处的语句会输出temp = 666
。接着继续执行for循环语句体,直到遇见关键字yield
,结束本次调用,并将值6
返回到#8
处,输出值6
。
(3)#10
和#8
处的语句执行过程一样,语句print(obj_generator.send(888))
将值888
传递给temp,此时在#4
处输出temp = 888
。遇到下一个yield
,结束本次调用,并将值6
返回到#10
处,输出值6
。
结论:send()
函数和next()
函数极其类似,唯一的区别在于send()
函数可以传入值,而next()
函数不能传值。当我们在send()
函数中发送值None
时,就等价于直接使用next()
函数,即:next(生成器对象) = 生成器对象.send(None)
。第一次调用send()
函数,不能发送一个非None
值,否则会出错,因为没有yield
语句(变量temp)来接收这个值。也就说说第一次使用send()
函数必须这样写生成器对象.send(None)
。