Python生成器(generators) 和yield关键字

看起来,yield语句是用于定义生成器(generators),从而代替使用一个方法来返回结果,同时还能记忆已定义的局部变量。与方法不同的是,一般方法在每次调用时,都会重新定义方法中所出现的变量,而生成器则会从上次停止的地方继续往下执行。

有关Python中的Generators

因为yield关键字只用与定义生成器,首先我们先来回顾一下生成器是非常有必要的。
生成器的主意来自于一个一个的去计算一系列的结果。简单来说,一个生成器可以用于使用懒惰的(简单的)方法去计算列表里面的每一个值。下面我们可以对比一下使用一个列表和使用一个生成器去做同样的事,他们所付出的努力却是截然不同。

# python 2.x

>>> # First, we define a list
>>> the_list = [2**x for x in range(5)]
>>>
>>> # Type check: yes, it's a list
>>> type(the_list)
<type 'list'>
>>>
>>> # Iterate over items and print them
>>> for element in the_list:
...     print(element)
...
1
2
4
8
16
>>>
>>> # How about the length?
>>> len(the_list)
5
>>>
>>> # Ok, now a generator.
>>> # As easy as list comprehensions, but with '()' instead of '[]':
>>> the_generator = (x+x for x in range(3))
>>>
>>> # Type check: yes, it's a generator
>>> type(the_generator)
<type 'generator'>
>>>
>>> # Iterate over items and print them
>>> for element in the_generator:
...     print(element)
...
0
2
4
>>>
>>> # Everything looks the same, but the length...
>>> len(the_generator)
Traceback (most recent call last):
  File "", line 1, in
TypeError: object of type 'generator' has no len()


# python3.x

>>> # First, we define a list
>>> the_list = [2**x for x in range(5)]
>>>
>>> # Type check: yes, it's a list
>>> type(the_list)
<class 'list'>
>>>
>>> # Iterate over items and print them
>>> for element in the_list:
...     print(element)
...
1
2
4
8
16
>>>
>>> # How about the length?
>>> len(the_list)
5
>>>
>>> # Ok, now a generator.
>>> # As easy as list comprehensions, but with '()' instead of '[]':
>>> the_generator = (x+x for x in range(3))
>>>
>>> # Type check: yes, it's a generator
>>> type(the_generator)
<class 'generator'>
>>>
>>> # Iterate over items and print them
>>> for element in the_generator:
...     print(element)
...
0
2
4
>>>
>>> # Everything looks the same, but the length...
>>> len(the_generator)
Traceback (most recent call last):
  File "", line 1, in
TypeError: object of type 'generator' has no len()

迭代列表和生成器看起来完全是一样的。然而,虽然生成器是可迭代的,但他不是一个集合,因此没有长度这个属性,集合(包括列表,元组,set等)会将所有的属性保存到内存中以便我们在需要的时候去调用。然而生成器在我们需要的时候才去计算并生成我们需要的值,且过后就忘了,因此他没有任何关于自己的结果集的记录。
生成器对于内存密集型任务特别有用,在同一时间没有必要保存所有的,特别耗费内存的列表元素,在不需要完整结果的情况下,逐个计算一系列的值将会很有用,向调用者产生中间结果,直到满足某些要求并且进一步处理后停止。

使用Python的yield关键字

一个较好的例子就是一个查找任务,通常不需要等待所有的结果都被找到。在文件系统中进行检索时,用户更喜欢快速的得到结果,而不是等着系统去查找整个文件系统,然后才返回结果。试问一下有哪个人会将Google的搜索结果全部浏览一遍呢?
由于不能使用列表推倒来实现搜索功能,我们可以使用带有yield关键字的函数来定义一个迭代器,yield放在指定的地方,当迭代其调用到yield语句时,则就返回当前的中间结果,然后等待,直到下一次调用再返回。下面让我们来定义一个迭代其,他的功能是在一个巨大的文本中一行一行的搜索某些关键字。

def search(keyword, filename):
    print('generator started')
    f = open(filename, 'r')
    # Looping through the file line by line
    for line in f:
        if keyword in line:
            # If keyword found, return it
            yield line
    f.close()

好了,假设 “directory.txt”文件包含了一个巨大的列表,其中包含了姓名和电话,让我们来找到名为“Python”的名字。

the_generator = search('Python', 'directory.txt')

当我们调用这个搜索方法时,他的核心代码并没有运行,迭代器对象只返回迭代其对象,作为构造函数。

>>> type(search)
<class 'function'>
>>> type(the_generator)
<class 'generator'>

这可能有点难,因为关于 def search(keyword, filename):通常是在调用他之后就执行,但这是它没有在迭代器中的情况,事实上,这值得我们去深入讨论,推荐使用“gen”或其他关键字来定义迭代器,但Guido决定坚持用“def”,关于具体内容你可以看PEP-255.
为了使新创建的迭代器计算某些东西,我们需要通过迭代器协议访问它,即调用它的next方法

>>> print(next(the_generator))
generator started
Anton Pythonio 111-222-333

通过打印调试字符串,我们得到第一个搜索结果,而不查看整个文件。
生成器在yield关键字/语句上返回后,不是停止,而是继续运行循环,直到它再次遇到yield关键字/语句。

更多关于迭代器的细节和例子

正如你可能注意到,第一次运行该函数,它将从头开始直到达到yield关键字/语句,将第一个结果返回给调用者,然后恢复到其返回的地方,继续运行。如果迭代器函数不再遇到yield关键字/语,它会引发一个StopIteration异常(就像所有可迭代对象在完成时所做的那样)。
要在后续调用中运行yield,生成器可以包含一个循环或多个yield语句:

def hold_client(name):
    yield 'Hello, %s! You will be connected soon' % name
    yield 'Dear %s, could you please wait a bit.' % name
    yield 'Sorry %s, we will play a nice music for you!' % name
    yield '%s, your call is extremely important to us!' % name

使用迭代器返回函数返回值通常更有意义,连续的功能能更有效地处理某些序列,一个很好的例子是缓冲:以大块获取数据并以小块处理:

def buffered_read():
    while True:
        buffer = fetch_big_chunk()
        for small_chunk in buffer:
            yield small_chunk

这种方法可以模拟出任何缓冲问题,可以从缓冲中一个一个的取值。
即使简单的任务也可以通过使用迭代器从而使问题更有效。在Python 2.X中,Python中常见的range()函数通常被xrange()代替,它迭代的产生值,而不是一次创建整个列表:

>>> # "range" returns a list
>>> type(range(0, 3))
<class 'list'>
>>> # xrange does not exist in Python 3.x

最后,一个“经典”的迭代器示例:计算前N个给定的斐波纳契数:

def fibonacci(n):
    curr = 1
    prev = 0
    counter = 0
    while counter < n:
        yield curr
        prev, curr = curr, prev + curr
        counter += 1

计数器计数直到“n”,这个例子是如此流行,因为斐波纳契序列是无限的,其适合存储器中的问题。到目前为止已经描述了Python生成器的最实用的方面。有关更详细的信息和一个有趣的讨论,请查看Python Enhancement Proposal 255,其中详细讨论了该语言的功能。

原文地址:http://pythoncentral.io/python-generators-and-yield-keyword/
我的自建博客: http://blog.dreamchasinger.cn
文章链接: http://blog.dreamchasinger.cn/?p=610

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值