Python yield关键字是什么意思?从可迭代对象(Iterable),迭代器(Iterator),生成器 (Generator) 说起

刚接触到迭代器,生成器的时候常常分不清这些概念的区别,下面的一张图很好的展示了他们的关系:

也就是:

父类子类
iterableiterator,一些container
containerlist, set, dict
iteratorgenerator

 这里不对container展开讨论

一、iterable

很多容器都是可迭代对象,但凡是可以返回一个迭代器的对象都可称之为可迭代对象,有了迭代器才能迭代嘛。

那怎么可以返回一个迭代器呢?只要是实现__iter__()魔法方法的类,调用it = iter(该类对象),返回的就是一个迭代器(it是一个迭代器)。

所以实现了__iter__方法的类对象就是一个可迭代对象(iterable)。

二、iterator

那么什么是迭代器呢?它是一个带状态的对象,它能在你调用next()方法的时候返回容器中的下一个值,任何实现了__iter____next__()方法的对象都是迭代器。

a = [1,2,3]
#列表对象本身已实现__iter__方法
iterator = iter(a)
next(iterator)
#1
next(iterator)
#2

先来看一下iterable和iterator的关系

也就是iterable和iterator是分离的,可以这么理解,

iterable是一般来讲是存放数据本身的类

iterator存放着一些状态(比如当前访问下标),和指向iterable对象的指针(虽然python没有暴露出来),每一次调用next()方法,就返回一个iterable对象的值。

#模拟一下,不代表真实实现
class myIterable:
    ...   #表省略
    def __iter__():
        return myIterator(self)
    ...

class myIterator:
    def __init__(self, myIterable):
        self.myIterable = myIterable  
        self.index = 0
    def __iter__(self):
        return self
    def __next__(self):
        index = index + 1
        return self.myIterable[self.index]
    ...

1.这样做的好处:一开始我很好奇为什么要把这两个分开?直接让iterable实现next()方法不行吗?

这样做其中一个好处就是所谓的松耦合,比如我们可以定义多种iterator, next()可以是取下一个,又或者是下下一个,即给iterable对象提供了很多的遍历方法。python这方面也有一个成熟的库叫做:itertools。包含可以循环next()的迭代器等等。

from itertools import cycle
num = cycle([1,2])
print(next(num))
# >>1
print(next(num))
# >>2
print(next(num))
# >>1

2.iterator要实现__iter__的原因:也就是为什么iterator是iterable的子类?

根据上面那张图,很明显iterator和iteratable是分离开的,iterable类对象调用__iter__()方法得到 iterator,iterator为什么还要实现__iter__方法?,在我的理解里面,其中一个好处就是操作方便,同时提供了iterator的子类generator能工作的基础。

比如, iterator实现了__iter__()方法可以用for  in来遍历,不用一直next,next,可以更方便。

iterator = iter([1,2,3])
for i in iterator:
    print(i)

>>1
>>2
>>3

当然我们可以通过下面的方式,更简单:

a = [1,2,3]
for i in a:
    print(i)

>>1
>>2
>>3

 对于第一种情况,你可能会有些疑惑,为什么是这样子的?

分解此for in类型代码时(见下图),您可以看到每次循环是对GET_ITER的显式调用,这本质上类似于调用iter(x)。 FOR_ITER是一条指令,等效于重复调用next()以获取每个元素,但这在字节码指令中并未显示,因为它已针对解释器中的速度进行了优化。

>>> import dis
>>> x = [1, 2, 3]
>>> dis.dis('for _ in x: pass')
  1           0 SETUP_LOOP              14 (to 17)
              3 LOAD_NAME                0 (x)
              6 GET_ITER
        >>    7 FOR_ITER                 6 (to 16)
             10 STORE_NAME               1 (_)
             13 JUMP_ABSOLUTE            7
        >>   16 POP_BLOCK
        >>   17 LOAD_CONST               0 (None)
             20 RETURN_VALUE

也就是当我们每次循环的时候,解释器会先取得in后面的可迭代对象的迭代器,再执行迭代器的next方法,自然返回下一个元素,所以当in后面是iterator时,取得它的迭代器仍然是该迭代器本身(return self),再执行该迭代器的next方法,返回下一个元素,所以这样设计,就使得iterator可以用于for in语句。

class myIterator:
    ...
    def __iter__(self):
         return self
    ...

但是我觉得更多的是用于它的子类generator,因为我们本身可以直接for in 作用于iterator关联的存储数据的iterable对象上,来实现遍历。然后下面看看这个__iter__()方法是怎么让generator用于for in的。

三、generator生成器

生成器,顾名思义可以用来生成数据,它一般不像普通的iterator一样会关联一个容器什么的,它是利用现有的数据来生成另外的数据,而且是要使用访问的时候才生成,对内存优化有一定的帮助。

举个例子,生成斐波那契数列,如果我们想获得一串斐波那契数列,我们可以用生成器。

class Fib:
    def __init__(self):
        self.prev = 0
        self.curr = 1

    def __iter__(self):
        return self

    def __next__(self):
        value = self.curr
        self.curr += self.prev
        self.prev = value
        return value

fib = Fib()
for i in fib:
    if(i<30):
        print(i)
    else:
        break

Fib就是一个生成器,看到这里的for in语句,因为生成器也是一个可迭代对象了,每次for循环的时候,生成器就会调用iter方法,返回生成器本身,然后再调用自己的next方法,这样他就可以不断地用for 循环来生成数据了。不然的话,只能不断地调用next语句来生成。而且当你不知道生成器生成数据的多少时,很难精确地获得生成的全部数据。

当然,一般的生成器有更简单的定义方法,不用像上面那样定义一个完整的规范的类。

1.括号法

numbers = [1,2,3,4,5,6]
squares = (x * x for x in numbers)
print(type(squares))
#>><class 'generator'>
print(next(squares))
#>>1
print(list(squares))
#>>[4, 9, 16, 25, 36]

这里我们看到这样的生成器是不可回头的,后面数组没有包括1,因为前面已经next()了一次。

2.yield关键字法

yield关键字可以嵌套在函数里面,有点类似于return,只不过它先返回的是一个生成器,当我们遍历这个生成器的时候,它才把每一次yield的数据返回。

def fib_gen():
    prev = 0
    curr = 1
    while(True):
        value = curr
        curr += prev
        prev = value
        yield value

fib = fib_gen()
for i in fib:
    if(i<30):
        print(i,end=" ")
    else:
        break

输出:
1 1 2 3 5 8 13 21

参考文章:

https://blog.csdn.net/mieleizhi0522/article/details/82142856/

https://nvie.com/posts/iterators-vs-generators/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值