各种语言的Python生成器

时间:2009-09-20 15:42:09

标签: c++ python programming-languages lisp

如何用您喜欢的语言模拟Python样式生成器?我在Scheme中找到了this个。看到其他实现一定很有趣,特别是那些没有一流延续的语言。

9 个答案:

答案 0 :(得分:11)

我不会在Lisp / Scheme中使用yield。

'产量'需要某种语言的常规或继续设施。屈服的许多用途可以用更简单的功能方式实现。

YIELD基本上与着名的COME-FROM运算符相关。 ;-)在这里,某个地方的调用可能会导致某些其他例程中的不同位置,具体取决于其执行上下文。因此,例程突然有多个入口点,其顺序在运行时确定。对于简单的用途,这可能没问题,但我认为对于更复杂的代码,对代码的推理会变得更难。

采取问题中链接的Scheme示例:

(define/y (step) 
  (yield 1)
  (yield 2)
  (yield 3)
  'finished)

(list (step) (step) (step))

多次调用(步骤)会返回不同的值。

我只想创建一个闭包:

(define step
  (let ((state '(1 2 3 finished)))
    (lambda ()
      (pop state))))

这将上面的函数分解为两个不同的东西:一个携带状态的变量和一个改变状态的简单函数。状态不再隐式编码到执行序列中。

 (list (step) (step) (step))))

人们可以想象其他用途的类似解决方案。

将其与generators from the Common Lisp SERIES library

进行比较
(let ((x (generator (scan '(1 2 3 finished)))))
  (list (next-in x)
        (next-in x)
        (next-in x)))

如果我们从另一个答案看这个Python示例

def simpleRange(n):
    for i in xrange(n):
        yield i

for n in simpleRange(5):
     print(n)

我们可以看到它复制了控制结构。调用位置和生成器都使用FOR迭代控制结构。使用闭包,我们可以通过仅提供状态转换代码来摆脱生成器内部控制结构的使用。

答案 1 :(得分:8)

以下是C ++中使用光纤模拟生成器的示例:

Yield Return Iterator for Native C++ Using Fibers

  

“yield return”迭代器是一个   为...创建的语言功能   一个原因:简单。它是   通常更容易迭代   整个集合,存储所有   局部变量需要的上下文,   而不是制作一个复杂的,   存储它的自定义迭代器对象   随后的检索状态   操作

还有原语 C例程setjmp, longjmp来实现类似的结果 (Lua coroutines使用上述方法实现)

答案 2 :(得分:3)

在JavaScript 1.7+中,我通常只需添加一些括号和括号。其他一切都差不多。 JavaScript 1.7引入了pythonic生成器和迭代器等。

生成器表达式:

# Python
(x + 1 for x in y if x > 100)

// JavaScript 1.8+
(x + 1 for (x in y) if (x > 100))

发电机

# Python
def simpleRange(n):
    for i in xrange(n):
        yield i

for n in simpleRange(5):
     print(n)


// JavaScript 1.7+
function simpleRange(n) {
    for (let i = 0; i < n; i++)
        yield i;
}

for (n in simpleRange(5))
    print(n);

列表/数组理解

# Python
[x + 1 for x in y if x > 100]

// JavaScript 1.7+
[x + 1 for (x in y) if (x > 100)]

答案 3 :(得分:3)

C ++,使用Generators

简单范围发生器的声明:

$generator(range)
{
   int i;
   int _max;
   int _min;

   range(int minv, int maxv):_max(maxv),_min(minv) {}

   $emit(int) // will emit int values. Start of body of the generator.
      for (i = _min; i <= _max; ++i)
         $yield(i); 
   $stop;
};

用法:

range r10(1,10);
for(int n; r10(n);) 
   printf("%d\n",n);

将输出

1
2 
...
10

答案 4 :(得分:3)

关于Common Lisp的@dmitry_vk答案我会在Lisp中添加它,实际上,并不真正需要生成器。它们的用例完全由闭包,特殊变量和宏的不同应用程序覆盖 - 没有学习新构造的额外概念开销。

有时甚至内置构造也会起作用。让我们看一下Python wiki的例子:

# add squares less than 100
from itertools import count, takewhile

sum = 0
square = (i*i for i in count())
bounded_squares = takewhile(lambda x: x < 100, square)
for i in bounded_squares:
   sum += i

使用loop可以更直接的方式实现它:

CL-USER> (loop :for i :from 0 :while (< i 100) :sum (expt i 2))
328350

因为loop比Python for更通用,所以不需要在这里引入特殊语法。

让我们考虑另一个用例 - 迭代某些自定义树。假设树由node s表示,指向children

(defstruct node
  data
  children)

我们可以使用一个相当小而简单的宏遍历任何树/子树。

(defmacro dotree ((var root &optional result-form) &body body)
  `(block nil
     (labels ((traverse (node)
                (let ((,var node))
                  ,@body
                  (dolist (child (children node))
                    (traverse child))
                  ,result-form)))
       (when-it ,root
         (traverse it)))))

(警告:为了更清晰起见,我没有使用gensym,但你应该这样做。

以下是其用法示例 - 获取所有叶节点的列表:

(let (rez)
  (dotree (node tree (reverse rez))
    (when (leafp node)
      (push node rez))))

这看起来和标准dolist宏一样有用。并且,与dolist一样,您可以随时通过调用return来停止迭代。

总的来说,我还没有看到一个实用的生成器使用示例,这个示例无法以一些不太复杂的方式在Lisp中实现。

P.S。您还可以查看SERIES Lisp库,它实现了与90年代生成器类似的概念。或CLAZY - 从2000年末开始。

答案 5 :(得分:1)

Monads可用于表示生成器(即使语义稍有不同)。

因此,可以在此处使用允许我们在特殊语法中定义monadic操作的任何语言。

  • VB.NET / C#(Linq - 但C#已经得到yield return
  • Scala(For-comprehensions)
  • Haskell(do-notation)
  • F#/ OCaml(计算表达式/执行)

Ruby可以通过其内置的延续功能来模拟生成器。

答案 6 :(得分:1)

Common Lisp虽然没有原生延续,但允许使用cl-cont之类的CPS变换器创建分隔连续。所以Common Lisp中的生成器可以用与Scheme生成器几乎相同的方式编写。

顺便说一下,基于continuation的生成器有一个Python和C#生成器缺少的特性:yield可以在生成器函数调用的动态范围内调用。 Python和C#生成器允许yield仅放置在生成器的主体内。

答案 7 :(得分:1)

红宝石:

生成器功能:

def simple_range(n)
    Enumerator.new do |y|    
        (0..n).each { |v| y.yield(v) }
    end
end

答案 8 :(得分:1)

通常,yield在具有第一类功能的语言中是多余的。 例如,在TIScript中,您可以通过这种方式执行生成器:

发电机。注意,它返回内部函数。

function range( from, to )
{
  var idx = from - 1;
  return function() { if( ++idx <= to ) return idx; } // yields value on call
}

及其用法:

for( var item in range(12,24) )
  stdout << item << " ";
TIScript中的

for(elem in source)与JS略有不同。如果source是一个函数,则调用它并将其返回值赋给elem,直到函数不返回void(空函数的默认返回值)。