生成器 (计算机编程)

生成器计算机科学中特殊的子程序。实际上,所有生成器都是迭代器[1]生成器非常类似于返回数组的函数,都是具有参数、可被调用、产生一系列的值。但是生成器不是构造出数组包含所有的值并一次性返回,而是每次产生一个值,因此生成器看起来像函数,但行为像迭代器。

生成器可以用更有表达力的控制流结构实现,如协程或头等計算續體。[2] 生成器,也被称作半协程(semicoroutine),[3]是特殊的、能力更弱的协程,总是在传回一个值时把控制交还给调用者,而不是像协程那样指出另一个协程继续执行。

使用

生成器通常在一个循环内部被调用。[4] 生成器第一次被调用是在进入这个循环结构时,创建一个对象以封装生成器的状态、绑定的实参。生成器的实体接着被执行直至遇到一个特别的yield动作,在这里提供给yield动作的值被返回给调用者。在下一次调用同一个生成器的时候,生成器从yield动作之后恢复执行,直到遇上另一个yield动作。生成器的执行也可以遇到finish动作而终止。

由于生成器在需要的才计算它的要被yield的值,因此可以用来代表一个串流,这种序列要一次性全部计算出来是昂贵的甚至是不可能的,这种情况还包括无穷序列或现场数据串流。

如果需要及早求值(主要是在序列是有限的时候,否则求值将永不终止),可以使用列表或使用并行结构来创建一个列表替代生成器。例如,Python的一个生成器g,通过l = list(g),可被求值成列表l。而在F#中,序列表达式seq { ... },将除了[ ... ]之外的惰性的(生成器或序列)求值为及早的(列表)。

在有生成器出现的情况下,语言的循环构造,比如forwhile,可以归约成一个单一的loop ... end loop构造;可以用合适的生成器以正确的方式,顺畅的模拟所有常见的循环构造。例如,一个按范围的循环如for x = 1 to 10,可以通过生成器而被实现为迭代,比如Python的for x in range(1, 10)。进一步的,break可以被实现为向生成器发送finish,而接着在循环中使用continue

历史

生成器最早出现于CLU语言(1975年)[5],是字符串操纵语言Icon(1977年)的显著特征 。它现在出现在Python[6]C#[7]Ruby、最新版本的ECMAScript(ES6/ES2015)与其他语言之中。在CLU与C#中,生成器称作迭代器,在Ruby称作枚举器。

CLU使用yield语句来在用户定义数据抽象上进行迭代[8]

string_chars = iter (s: string) yields (char);
  index: int := 1;
  limit: int := string$size (s);
  while index <= limit do
    yield (string$fetch(s, index));
    index := index + 1;
    end;
end string_chars;

for c: char in string_chars(s) do
   ...
end;

语言实例

C++

使用宏预处理的例子见[9]:

$generator(descent)
{
   // place for all variables used in the generator
   int i; // our counter

   // place the constructor of our generator, e.g. 
   // descent(int minv, int maxv) {...}
   
   // from $emit to $stop is a body of our generator:
    
   $emit(int) // will emit int values. Start of body of the generator.
      for (i = 10; i > 0; --i)
         $yield(i); // a.k.a. yield in Python,
                    // returns next number in [1..10], reversed.
   $stop; // stop, end of sequence. End of body of the generator.
};

可迭代:

int main(int argc, char* argv[])
{
  descent gen;
  for(int n; gen(n);) // "get next" generator invocation
    printf("next number is %d\n", n);
  return 0;
}

C++11提供的foreach loop可用于任何具有beginend成员函数的类。还需要有operator!=, operator++operator*。例如:

#include <iostream>
int main()
{
    for (int i: range(10))
    {
        std::cout << i << std::endl;
    }
    return 0;
}

一个基本实现:

class range
{
private:
    int last;
    int iter;

public:
    range(int end):
        last(end),
        iter(0)
    {}

    // Iterable functions
    const range& begin() const { return *this; }
    const range& end() const { return *this; }

    // Iterator functions
    bool operator!=(const range&) const { return iter < last; }
    void operator++() { ++iter; }
    int operator*() const { return iter; }
};

C#

C# 2.0开始可以利用yield构造生成器。

// Method that takes an iterable input (possibly an array)
// and returns all even numbers.
public static IEnumerable<int> GetEven(IEnumerable<int> numbers) {
    foreach (int i in numbers) {
        if ((i % 2) == 0) {
            yield return i;
        }
    }
}

可以使用多个yield return语句:

public class CityCollection : IEnumerable<string> {
    public IEnumerator<string> GetEnumerator() {
        yield return "New York";
        yield return "Paris";
        yield return "London";
    }
}

Python

Python自2001年的2.2版开始支持生成器[6]。下面是生成器一个例子,它是个计数器:

def countfrom(n):
    while True:
        yield n
        n += 1

# 例子使用: 打印出从10到20的整数
# 注意这个迭代通常会终止,尽管 
# countfrom()被写为了无限循环

for i in countfrom(10):
    if i <= 20:
        print(i)
    else:
        break

另一个生成器例子,它按需要无尽的产生素数:

import itertools
def primes():
    yield 2
    n = 3
    p = []
    while True:
        # 这有效于Python 2.5+ 
        if not any(n % f == 0 for f in 
                     itertools.takewhile(lambda f: f*f <= n, p)): 
            yield n
            p.append(n)
        n += 2

Python的生成器可以认为是一个迭代器包含了冻结的栈帧。当用next()方法调用迭代器,Python恢复冻结的栈帧,继续执行至下一次的yield语句。生成器的栈帧再一次冻结,被yield的值返回给调用者。

PEP 380 (Python 3.3开始)增加了yield from表达式,允许生成器将它的一部份操作委托给另一个生成器。[10]

生成器表达式

Python拥有建模于列表推导式的一种语法,叫做生成器表达式,用来辅助生成器的创建。下面的例子扩展了上面第一个例子,使用生成器表达式来计算来自countfrom生成器函数的数的平方:

squares = ( n*n  for n in countfrom(2) )

for j in squares:
    if j <= 20:
        print(j)
    else:
        break

参见

注释

  1. https://stackoverflow.com/questions/1022564/what-is-the-difference-between-an-iterator-and-a-generator
  2. Kiselyov, Oleg. . January 2004.
  3. Anthony Ralston. . Nature Pub. Group. 2000 [11 May 2013]. ISBN 978-1-56159-248-7.
  4. The Icon Programming Language utilizes generators to implement its goal directed evaluation. In Icon, generators can be invoked in contexts outside of the normal looping control structures.
  5. Liskov, Barbara. (PDF). April 1992 [2008-03-08]. (原始内容 (pdf)存档于2003-09-17).
  6. Python Enhancement Proposals: PEP 255: Simple Generators, PEP 289: Generator Expressions, PEP 342: Coroutines via Enhanced Generators
  7. yield (C# Reference)
  8. Liskov, B.; Snyder, A.; Atkinson, R.; Schaffert, C. . Communications of the ACM. 1977, 20 (8). doi:10.1145/359763.359789.
  9. http://www.codeproject.com/KB/cpp/cpp_generators.aspx
  10. PEP 380 -- Syntax for Delegating to a Subgenerator

参考文献

  • Stephan Murer, Stephen Omohundro, David Stoutamire and Clemens Szyperski: Iteration abstraction in Sather. ACM Transactions on Programming Languages and Systems, 18(1):1-15 (1996)
This article is issued from Wikipedia. The text is licensed under Creative Commons - Attribution - Sharealike. Additional terms may apply for the media files.