Python中的可迭代对象:容器、迭代器与生成器

相互关系

关系图

让我们先看两张图,

  • 这张图展示了它们得从属关系
    在这里插入图片描述

  • 这张图解释了他们之间的从属关系和转换关系
    在这里插入图片描述

定义

(1)可迭代对象:其中实现了__iter__方法的对象就叫做可迭代对象。
( 严格来说,可迭代对象是任何实现了 iter() 方法或者实现了序列(Sequence)语义中的 getitem() 方法的任意自定义类对象。
(2)迭代器:既实现了__iter__,也实现了__next__()方法的对象叫做迭代器;
(3)生成器:具有yield关键字的函数都是生成器;

各种可迭代对象(容器、可迭代对象,迭代器,生成器)

一、容器(container)

容器就是一个用来存储多个元素的数据结构,常见的容器包括【列表】、【元组】、【字典】、【集合】、【字符串】。例如:

  • 序列(sequence)类型: 如 list, str, tuple, bytes 等
  • 非序列类型类型如:dict, 文件对象等
容器的两个特点
  1. 容器中的元素可通过迭代获取。
  2. 所有容器中的元素被存储在内存中。(不同于其它迭代器得地方)

二、可迭代对象(Iterable)

严格来说,可迭代对象是任何实现了__iter__() 方法或者实现了序列(Sequence)语义中的__getitem__() 方法的任意自定义类对象。

以通俗简单的方式来讲,可迭代对象就是能够逐一返回其成员项的对象,其可用于 for 循环。
通过使用iter()方法,我们能将可迭代对象返回成迭代器。例如:

 1 from collections import Iterable
 2 # 定义一个列表,其本身是可迭代对象
 3 list_a = ['a', 'b', 'c']
 4 isinstance(list_a, Iterable) # True
 5 new_a = iter(list_a)
实现可迭代对象的两种方式

那么下面我们分别来讨论下这两种实现方案。

  1. 实现__iter__
    这种方式是你必须为你的类实现__iter__()方法,该方法返回的必须是一个迭代器对象,下面会讲到。
    注意:可迭代对象得__iter__方法,返回的必须是迭代器,但不一定是self
    (这与迭代器的__iter__不同,因为迭代器的self一定是迭代器,而可迭代对象的self不一定是迭代器)

__iter__方法说明
该方法返回的是当前对象的迭代器类的实例。因为可迭代对象与迭代器都要实现这个方法,因此有以下两种写法。
写法一:用于可迭代对象类的写法,返回该可迭代对象的迭代器类的实例。
写法二:用于迭代器类的写法,直接返回self(即自己本身),表示自身即是自己的迭代器。

例如:pytorch的Dataloader类,其__iter__方法如下:

    def __iter__(self):
        if self.num_workers == 0:
            return _SingleProcessDataLoaderIter(self)
        else:
            return _MultiProcessingDataLoaderIter(self)
  1. 实现__getitem__
    序列:序列(Sequence)的迭代通过实现__getitem__() 方法来使用整数索引进行高效的元素访问。同时,你必须为其定义一个返回序列长度的__len__() 方法。例如上面例子中的 list, str, tuple, bytes。序列数据结构的存储是一段连续的内存空间,其直接使用整数索引进行寻址,查找元素非常高效,但是插入删除元素时效率低下。
    字典:有的童鞋会说,dict 也实现了__getitem__ 和__len__ 协议,为毛 dict 不属于序列?原因是它并不是通过整数索引来顺序迭代,而是通过任意的不可变键(immutable key) 来进行逐个查找的。所以 dict 的迭代还是因为其实现了__iter__。
工作方式(转换为迭代器的方式)
  • 第一种工作方式:当把一个可迭代对象 x 作为参数传给内置函数 iter() 时,它会返回该对象的迭代器以供迭代。
  • 第二种工作方式:但通常我们并不需要通过iter()函数,而是直接使用 for 对可迭代对象进行遍历,for 语句会为你自动处理那些操作:
    (1)如果实现了__iter__,则自动调用 iter(x) 来获取迭代器。
    (2) 如果实现了 getitemlen 协议,其会自动创建一个迭代器,并尝试从索引 0 开始获取元素,若尝试失败,会引发一个 TypeError。
类型断言

判断一个对象是否是一个可迭代对象,可以使用 collections.abc.Iterable。

In [1]: from collections.abc import Iterable
In [2]: isinstance([1, 2, 3], Iterable)
Out[2]: True

迭代器(iterator)

即实现了迭代器协议的对象就是一个迭代器,迭代器协议由 iter() 和 next() 共同组成。

  • __iter__必须返回迭代器对象本身。
  • _next_ 应从容器中返回下一项,如果已经没有数据项可返回时,则需引发 StopIteration 异常,继续调用其__next__应再次引发 StopIteration 异常。
迭代器的实现(__iter__与__next__方法说明)

一个迭代器应有的样子应该是这样的:

class Iterator:
    def __iter__(self):
        return self
    def __next__(self):
        pass

_iter__与__next__方法说明
1)_iter

该方法返回的是当前对象的迭代器类的实例。因为可迭代对象与迭代器都要实现这个方法,因此有以下两种写法。
写法一:用于可迭代对象类的写法,返回该可迭代对象的迭代器类的实例。
写法二:用于迭代器类的写法,直接返回self(即自己本身),表示自身即是自己的迭代器。
2)_next_
返回迭代的下一步,实现该方法时注意最后超出边界要抛出StopIteration异常。

迭代器的特点
  • 迭代器是一个带状态的对象,迭代器内部持有一个状态,该状态用于记录当前迭代所在位置,以便于下次迭代的时候获取正确的元素。迭代器可以通过next()方法来迭代获取下一个值。
  • 迭代器不会一次性吧所有元素都加载到内存,而是需要的时候才返回结果
工作方式

迭代器用来表示一连串数据流的对象,当 for 语句自动返回可迭代对象的迭代器时,for 将重复调用其 next() 方法将逐个返回流中的项,直到迭代器引发 StopIteration 异常后终止循环。此时该迭代器数据项已耗尽,不能再使用。

四、生成器

具有yield关键字的函数都是生成器
生成器是一种特殊的迭代器,生成器自动实现了“迭代器协议”(即__iter__和next方法),不需要再手动实现两方法。(用关键字yield来实现)

yield可以理解为return,返回后面的值给调用者。不同的是return返回后,函数会释放,而生成器则不会。在直接调用next方法或用for语句进行下一次迭代时,生成器会从yield下一句开始执行,直至遇到下一个yield。
生成器特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行f=fab()返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。

python中yield的用法详解——最简单,最清晰的解释

带yield的函数是一个生成器,而不是一个函数了,这个生成器有一个函数就是next函数,next就相当于“下一步”生成哪个数,这一次的next开始的地方是接着上一次的next停止的地方执行的,然后遇到yield后,返回出要生成的数,这一次next就结束。(如果return会退出整个函数,但生成器遇到yeild返回结果,只是暂停,没有结束函数)

一张图理解yield 瞬间明白yield用法

在这里插入图片描述

生成器主要的特点

1、生成器通过关键字yield自动实现了“迭代器协议”(即__iter__和next方法),不需要再手动实现两方法。
生成器特殊的地方在于函数体中没有return关键字,函数的返回值是一个生成器对象。当执行f=fab()返回的是一个生成器对象,此时函数体中的代码并不会执行,只有显示或隐示地调用next的时候才会真正执行里面的代码。

2、生成器可以传入数据进行计算(而修改普通迭代器的当前迭代值往往会发生异常),并根据变量内容计算结果后返回。

3、迭代器不会一次把所有的元素加载到内存,而是调用的时候才生成返回结果(这点相同于迭代器)

4、可以通过for循环进行迭代(因为生成器是迭代器)

实现生成器
生成器函数

和普通函数的 return 返回不同,生成器函数使用 yield。

  • 第一个例子
# coding=utf-8    
#1. 定义生成器  
def myList(num):     
    now = 0           # 当前迭代值,初始为0  
    while now < num:  
        val = (yield now)                      # 返回当前迭代值,并接受可能的send发送值;
        now = now + 1 if val is None else val  # val为None,迭代值自增1,否则重新设定当前迭代值为val  

#2. 测试代码
my_list = myList(5)   # 得到一个生成器对象  
print my_list.next()  # 返回当前迭代值  
print my_list.next()  
my_list.send(3)       # 重新设定当前的迭代值  
print my_list.next()  
print dir(my_list)    # 返回该对象所拥有的方法名,可以看到__iter__与next在其中

在这里插入图片描述

  • 第二个例子
def odd_func(start=1, end=10):
...     for val in range(start, end + 1):
...         if val % 2 == 1:
...             yield val
...
>>> of = odd_func(1, 5)
>>> of
<generator object odd_func at 0x101a14200>
>>> iter(of)
<generator object odd_func at 0x101a14200>
>>> next(of)
1
>>> next(of)
3
>>> next(of)
5
>>> next(of)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
  • 第三个例子
    yeild后面不跟任何值,这时yeild返回的值只能通过send()送进去。但此时首先要调用一次send(None)或者next()方法.
def gen():
     while True:
         s = yield
         print(s)

>>> g = gen()   

>>> g.send('hello')     #这里很重要,向一个刚开始的生成器直接send一个值,会报错,所以我们得先调用next方法
                        #让生成器向后移动一个位置后再send值            
Traceback (most recent call last):
  File "<ipython-input-169-9d017dfb1443>", line 1, in <module>
    g.send('hello')
TypeError: can't send non-None value to a just-started generator

# 下面才是send函数正确的使用方法
>>> next(g)  # 或者调用send(None)

>>> g.send('hello')
hello

TypeError: can’t send non-None value to a just-started generator 告诉我们,刚开始的生成器只能send(None)

第三个例子的解释:
generator其实有第2种调用方法(恢复执行),即通过send(value)方法将value作为yield表达式的当前值,你可以用该值再对其他变量进行赋值,这一段代码就很好理解了。
当我们调用send(value)方法时,generator正由于yield的缘故被暂停了。此时,send(value)方法传入的值作为yield表达式的值,函数中又将该值赋给了变量s,然后print函数打印s,循环再遇到yield,暂停返回。

调用send(value)时要注意,要确保,generator是在yield处被暂停了,如此才能向yield表达式传值,否则将会报错(如上所示),可通过next()方法或send(None)使generator执行到yield。

还有更复杂的yeild用法,具体请见链接:https://www.jianshu.com/p/d68f8cfabcd9

生成器表达式(generator expression)

生成器表达式是列表推倒式的生成器版本,看起来像列表推导式,但是它返回的是一个生成器对象而不是列表对象。

a = (x for x in range(10))
print(a)
>>>> <generator object <genexpr> at 0x000000000289D8E0>
Python中return和yield的区别

参考文献

python迭代对象,迭代器,生成器,以及yield用法详解(转)
Python中的可迭代对象,迭代器与生成器
Python中可迭代对象、迭代器、生成器详解
https://zhuanlan.zhihu.com/p/43762081
https://zhuanlan.zhihu.com/p/293981060

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值