Python yield使用理解

Admin 2018-07-12 11:38:54 Python

通常在for...in...循环中,in后面是一个数组,这个数组是一个可迭代对象,类似的还有字符串,文件及其他可迭代对象。这种方式的缺陷是数据都存在内存中,如果数据非常庞大的话会非常消耗内存的。因此在Python可以使用生成器(generator)来迭代较大数据。生成器是在用的时候才生成,如:gen = (x*x for i in range(10)) ,注意这里用的是()。生成器(generator)可以迭代的关键是有一个next()方法,工作原理就是通过重复的条用next()方法,直到捕获到异常(StopIteration)。

那么什么是yield?

带有yield的函数不在是一个普通的函数而是一个生成器(generator)。yield是一个类似return的关键字,迭代一次遇到yield就返回,不同return的是,下次迭代将从上次遇到yield的后面开始执行,简单的说就是:yield就是return一个值,并记住这个返回的位置,下次迭代就从这个位置开始。

例子1:普通斐波那契数列

def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        print b 
        a, b = b, a + b 
        n = n + 1
fab(5)

结果:

1 
1
2 
3 
5

值越大内存占用也会增大所以最好不要用list,可以通过iterable对象迭代

列子2:iterable对象迭代(Python2.x)

for i in range(1000): pass

for i in xrange(1000): pass

range会生成一个1000个元素的list,而xrange不会生成list,返回的是一个iterable对象,是在每次迭代的时候返回下一个值,内存占用会很小。

我们可以利用iterable写一个class实现斐波那契数列

例子3:

class Fab(object): 
 
    def __init__(self, max): 
        self.max = max 
        self.n, self.a, self.b = 0, 0, 1 
 
    def __iter__(self): 
        return self 
 
    def next(self): 
        if self.n < self.max: 
            r = self.b 
            self.a, self.b = self.b, self.a + self.b 
            self.n = self.n + 1 
            return r 
        raise StopIteration()
 for n in Fab(5): 
    print n

Fab类通过next()不断返回数列的下一个数,内存占用始终为常数:

结果:

1 
1 
2 
3 
5

使用class虽然可以节省内存,但代码并不简洁,那么如何保证代码简洁又能实现iterable。答案自然是yield。

例子4:

def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b
        a, b = b, a + b 
        n = n + 1
 for n in fab(5): 
    print n

结果:

1 
1 
2 
3 
5

yield不仅仅用于for循环中,也可以用于某个函数的参数,只要这个函数的参数允许迭代。如:array.extend

yield中最重要的函数是send()和next()。

send和next区别在于send可以传递参数给yield表达式,传递的参数会作为yield表达式的值。就是可以修改yield表达式的值,如:a = yield 10 第一次迭代返回的是10,a还没赋值,第二次使用send(20),那么就修改了a的值,a=20。

第一次调用时必须先next()或send(None),负责会报错,send之所以为None是因为没有上一个yield。

>>> def gen():
...   print "a"
...   x=yield 'abc'
...   print "b","x=",x
...   y=5+(yield x)
...   print "c","y=",y
... 
>>> p = gen()
>>> p
<generator object gen at 0x10107d9b0>
>>> p.next()
a
'abc'
>>> p.send(20)
b x= 20
20
>>> p.send(10)
c y= 15
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

使用isgeneratorfunction判断generator函数

>>>from inspect import isgeneratorfunction 
>>> isgeneratorfunction(fab) 
True

注意区分 fab和fab(),fab是generator function,fab(2)是generator 好比类的定义和类的实例

类的定义和类的实例

>>>import types 
>>> isinstance(fab, types.GeneratorType) 
False 
>>> isinstance(fab(5), types.GeneratorType) 
True

fab是无法迭代的,fab(2)是可迭代的

>>>from collections import Iterable 
>>> isinstance(fab, Iterable) 
False 
>>> isinstance(fab(5), Iterable) 
True

return的作用:在generator function中,如果没有return,则默认执行至函数完毕,如果在执行过程中return则直接抛出StopIteration终止迭代

实例:读取一个大的文件

def read_file(fpath): 
    BLOCK_SIZE = 1024 
    with open(fpath, 'rb') as f: 
        while True: 
            block = f.read(BLOCK_SIZE) 
            if block: 
                yield block 
            else: 
                return

结论:

  • yield是一个类似return的关键字,只是这个函数返回的生成器

  • 当你调用这个函数的时候,函数内部并不会立即执行,只会返回一个生成器对象

  • 当你for进行迭代的时候,函数中的代码才会执行

相关文章
最新推荐