python中的迭代器、可迭代对象、生成器和yield

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处的代码,和上面步骤类似,就不再说了。

结论yieldreturn的区别是: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)

参考文章:
python中yield的用法详解——最简单,最清晰的解释

python中yield的用法(生成器的讲解)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值