Python--迭代器,生成器,yield函数的作用

主要分两部分介绍:

  1. 迭代、迭代器、可迭代
  2. 生成器、yield 表达式

1. 迭代、迭代器、可迭代
(1)迭代概念:很多数据就是容器,里面包含很多其他类型的元素。实际使用容器时,我们需要逐个获取容器中的元素。逐个获取容器中的元素的过程就叫做迭代。

迭代demo:
list_a = [1,2,3]
for i in list_a:
      print(i)

上述逐个取list_a对象中元素并打印的过程就叫做迭代,其中迭代的对象是list_a,可以看出其为列表,因此列表是可迭代的;Python中其他的数据类型:dict,tuple,string,set,file等都是可迭代的。对于用户自己自定义的数据类型,如果提供了__iter__()或者__getitem__()方法,那么该类方法实例化后的对象也是可迭代的;
(2)迭代器----是一种对象。
迭代器抽象的是一个数据流,是只允许迭代一次的对象。对迭代器不断调用A().next()方法(Python2.7)或者next(A())(python3),则可以依次获得下一个元素;当迭代器中没有元素时,调用next()方法会抛出StopIteration异常。迭代器的 iter() 方法返回迭代器自身;因此迭代器也是可迭代的。
(3)迭代器协议 ----是指容器类需要包含的一个特殊方法 iter()
如果一个容器类提供了 iter() 方法,并且该方法能返回一个能够逐个访问容器内所有元素的迭代器,则我们说该容器类实现了迭代器协议。
Python 中的迭代器协议和 Python 中的 for 循环是紧密相连的。

# iterator protocol and for loop
for x in something:
    print(x)

Python 处理 for 循环时,首先会调用内建函数 iter(something),它实际上会调用 something.iter(),返回 something 对应的迭代器。而后,for 循环会调用内建函数 next(),作用在迭代器上,获取迭代器的下一个元素,并赋值给 x。此后,Python 才开始执行循环体。

2. 生成器、yield表达式
(1)生成器函数----是一种特殊的函数,是指包含了yield表达式的函数,调用它会返回特殊的迭代器,称为生成器;

def func():
    return 1

def gen():
    yield 1

print(type(func))   # <class 'function'>
print(type(gen))    # <class 'function'>

print(type(func())) # <class 'int'>
print(type(gen()))  # <class 'generator'>

如上,生成器 gen 看起来和普通的函数没有太大区别。仅只是将 return 换成了 yield。用 type() 函数打印二者的类型也能发现,func 和 gen 都是函数。然而,二者的返回值的类型就不同了。func() 是一个 int 类型的对象;而 gen() 则是一个迭代器对象。
(2)yield表达式
(2.1) yield 仅能用于定义生成器函数;
(2.2)生成器函数被调用后,其函数体内的代码并不会立即执行,而是返回一个生成器(generator-iterator)。当返回的生成器调用成员方法时,相应的生成器函数中的代码才会执行;
详情: 如前所述,如果一个函数定义中包含 yield 表达式,那么该函数是一个生成器函数(而非普通函数)。实际上,yield 仅能用于定义生成器函数。
与普通函数不同,生成器函数被调用后,其函数体内的代码并不会立即执行,而是返回一个生成器(generator-iterator)。当返回的生成器调用成员方法时,相应的生成器函数中的代码才会执行。

def square():
    for x in range(4):
        yield x ** 2
square_gen = square()
for x in square_gen:
    print(x)

前面说到,**for 循环会调用 iter() 函数,获取一个生成器;而后调用 next() 函数,将生成器中的下一个值赋值给 x;再执行循环体。**因此,上述 for 循环基本等价于:

genitor = square_gen.__iter__()
while True:
    x = geniter.next() # Python 3 是 __next__()
    print(x)

注意到,square 是一个生成器函数;作为它的返回值,square_gen 已经是一个迭代器;迭代器的 iter() 返回它自己。因此 geniter 对应的生成器函数,即是 square。

每次执行到 x = geniter.next() 时,square 函数会从上一次暂停的位置开始,一直执行到下一个 yield 表达式,将 yield 关键字后的表达式列表返回给调用者,并再次暂停。注意,每次从暂停恢复时,生成器函数的内部变量、指令指针、内部求值栈等内容和暂停时完全一致。

(2.3)yield函数的好处:
Python 的老用户应该会熟悉 Python 2 中的一个特性:内建函数 range 和 xrange。其中,range 函数返回的是一个列表,而 xrange 返回的是一个迭代器。

在 Python 3 中,range 相当于 Python 2 中的 xrange;而 Python 2 中的 range 可以用 list(range()) 来实现。

Python 之所以要提供这样的解决方案,是因为在很多时候,我们只是需要逐个顺序访问容器内的元素。大多数时候,我们不需要「一口气获取容器内所有的元素」。比方说,顺序访问容器内的前 5 个元素,可以有两种做法::
方法1:获取容器内的所有元素,然后取出前 5 个;
方法2:从头开始,逐个迭代容器内的元素,迭代 5 个元素之后停止。

显而易见,如果容器内的元素数量非常多(比如有 10 ** 8 个),或者容器内的元素体积非常大,那么后一种方案能节省巨大的时间、空间开销。

现在假设,我们有一个函数,其产出(返回值)是一个列表。而若我们知道,调用者对该函数的返回值,只有逐个迭代这一种方式。那么,如果函数生产列表中的每一个元素都需要耗费非常多的时间,或者生成所有元素需要等待很长时间,则使用 yield 把函数变成一个生成器函数,每次只产生一个元素,就能节省很多开销了。

#Python3
def f1():
    a =[1,2]
    yield a
f2 = f1()
print(f1())
print(dir(f1()))
print(next(f2))
# print(next(f2)) #取消注释,让其运行会报StopIteration错误;
结果为:
<generator object f1 at 0x7fbf63840468>
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
[1, 2]
可以看出,yield可迭代对象本身是会还原可迭代列表本身的;但是yield本身的优势及更主要的作用是:当列表较大的时候,如果我们只需去列表的前5个用于运算的时候,不需要等待上层程序将结果列表全部计算出来,再选择前5个,而是可以直接用yield的函数,运行一次,计算一次;提高运行效率;
f1().next()
AttributeError: 'generator' object has no attribute 'next'
在python2.7中,实例化对象用next:f1().next();
在Python3中,实例化对象用next:next(f1())

3.generator生成器方法

生成器有一些方法。调用这些方法可以控制对应的生成器函数;不过,若是生成器函数已在执行过程中,调用这些方法则会抛出 ValueError 异常。

  1. generator.next():从上一次在 yield 表达式暂停的状态恢复,继续执行到下一次遇见 yield 表达式。当该方法被调用时,当前 yield 表达式的值为 None,下一个 yield 表达式中的表达式列表会被返回给该方法的调用者。若没有遇到 yield 表达式,生成器函数就已经退出,那么该方法会抛出 StopIterator 异常。
  2. generator.send(value):和 generator.next() 类似,差别仅在与它会将当前 yield 表达式的值设置为 value。
  3. generator.throw(type[, value[, traceback]]):向生成器函数抛出一个类型为 type 值为 value 调用栈为 traceback 的异常,而后让生成器函数继续执行到下一个 yield 表达式。其余行为与 generator.next() 类似。
  4. generator.close():告诉生成器函数,当前生成器作废不再使用。
    参考:https://liam.page/2017/06/30/understanding-yield-in-python/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章