【问题标题】:Where do you use generators feature in your python code?您在 python 代码中在哪里使用生成器功能?
【发布时间】:2010-03-29 06:58:18
【问题描述】:

我研究过生成器功能,我想我明白了,但我想了解我可以在我的代码中应用它的地方。

我想到了我在“Python 基本参考”一书中读到的以下示例:

# tail -f
 def tail(f):
  f.seek(0,2) 
  while True:
   line = f.readline() 
   if not line: 
     time.sleep(0.1)
     continue
   yield line

你有没有其他有效的例子表明生成器是完成这项工作的最佳工具,比如 tail -f?

您多久使用一次生成器功能,您通常将它应用到程序的哪类功能\部分?

【问题讨论】:

  • 请将此问题设为社区 wiki 问题。

标签: python generator


【解决方案1】:

当我实现扫描器(标记器)或迭代数据容器时,我经常使用它们。

编辑:这是我用于 C++ 语法高亮程序的演示标记器:

whitespace = ' \t\r\n'
operators = '~!%^&*()-+=[]{};:\'"/?.,<>\\|'

def scan(s):
    "returns a token and a state/token id"
    words = {0:'', 1:'', 2:''} # normal, operator, whitespace
    state = 2 # I pick ws as first state
    for c in s:
        if c in operators:
            if state != 1:
                yield (words[state], state)
                words[state] = ''
            state = 1
            words[state] += c
        elif c in whitespace:
            if state != 2:
                yield (words[state], state)
                words[state] = ''
            state = 2
            words[state] += c
        else:
            if state != 0:
                yield (words[state], state)
                words[state] = ''
            state = 0
            words[state] += c
    yield (words[state], state)

使用示例:

>>> it = scan('foo(); i++')
>>> it.next()
('', 2)
>>> it.next()
('foo', 0)
>>> it.next()
('();', 1)
>>> it.next()
(' ', 2)
>>> it.next()
('i', 0)
>>> it.next()
('++', 1)
>>> 

【讨论】:

  • 你能发布一些简单的标记器sn-p吗?
  • @systempuntoout,好的,我发布了一个示例。
【解决方案2】:

每当您的代码生成无限数量的值或更一般地说,如果首先生成整个列表会消耗太多内存

或者如果您可能迭代整个生成的列表(并且列表非常大)。我的意思是,如果不使用它,首先生成每个值(并等待生成)是没有意义的。

我最近一次接触生成器是在我实现线性循环序列 (LRS) 时,例如斐波那契数列。

【讨论】:

  • -1:在我看来,这更像是对一般迭代器的描述,而不是生成器函数,所以它没有抓住重点。为什么这个答案得到任何赞成?
  • @nikow:是的,它更通用,但我不会说它是迭代器的描述。它是关于在哪些情况下生成器可能有用的抽象描述。生成器是某种迭代器.. :)
【解决方案3】:

在我拥有可以读取任何内容的算法的所有情况下,我只使用生成器。

为什么?

在多个生成器的上下文中,过滤、映射和缩减规则的分层要容易得多。

例子:

def discard_blank( source ):
    for line in source:
        if len(line) == 0:
            continue
        yield line

def clean_end( source ):
    for line in source:
        yield line.rstrip()

def split_fields( source ):
    for line in source;
        yield line.split()

def convert_pos( tuple_source, position ):
    for line in tuple_source:
        yield line[:position]+int(line[position])+line[position+1:]

with open('somefile','r') as source:
    data= convert_pos( split_fields( discard_blank( clean_end( source ) ) ), 0 )
    total= 0
    for l in data:
        print l
        total += l[0]
    print total

我的偏好是使用许多小型生成器,这样小的变化就不会破坏整个流程链。

【讨论】:

  • 因此,您只是将生成器函数用作迭代器装饰器的方便表示法。我认为 Nick D 的例子要好得多,因为它突出了延续方面。
  • @J. T. Hurley:我不知道“同样好”是什么意思,但是生成器不会创建中间结果,而函数通常会这样做。嵌套生成器是一种 map-reduce 管道。
  • @nikow:我对延续不感兴趣。我有兴趣将一个大的(可能是混乱的)操作分解为小的 map-reduce 样式步骤。
  • @S.Lott:延续是一个有趣的特性(尤其是新的协程功能),所以你对它们不感兴趣太糟糕了;-)
  • @nikow:我对它们不感兴趣为了这个答案的目的。它们是一个有趣的功能。除了作为练习之外,我从未使用过它们。我一直使用有状态的、可调用的对象而不是延续。但这超出了这个具体的答案。我的意思不是涵盖生成器的所有可能用途。我的意思是回答这个问题:“生成器是完成这项工作的最佳工具的任何其他有效示例”。
【解决方案4】:

一般来说,将数据采集(可能很复杂)与消费分开。特别是:

  • 为了连接多个 b-tree 查询的结果 - db 部分生成并执行查询 yield-ing 从每个查询记录,消费者只看到单个数据项到达。
  • 缓冲(预读) - 生成器以块为单位获取数据并从每个块中生成单个元素。同样,消费者与血腥细节分开。

生成器也可以作为协程工作。您可以使用“消费者”端的nextval=g.next(data) 和生成器端的data = yield(nextval) 将数据传入它们。在这种情况下,生成器及其消费者“交换”值。您甚至可以让 yield 在生成器上下文中抛出异常:g.throw(exc) 会这样做。

【讨论】:

    猜你喜欢
    • 2021-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-19
    • 1970-01-01
    • 1970-01-01
    • 2011-02-09
    相关资源
    最近更新 更多