【问题标题】:Python generators in various languages [closed]各种语言的Python生成器[关闭]
【发布时间】:2009-09-20 15:42:09
【问题描述】:

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

【问题讨论】:

    标签: c++ python programming-languages lisp


    【解决方案1】:

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

    'yield' 需要语言中的某种协同程序或延续设施。可以通过更简单的函数方式实现 yield 的许多用途。

    YIELD 基本上与著名的 COME-FROM 运算符有关。 ;-) 在这里,某个地方的调用可能会导致某个其他例程中的不同地方,具体取决于其执行上下文。所以一个例程突然有多个入口点,它们的顺序是在运行时确定的。对于简单的用途,这可能很好,但我认为对于更复杂的代码,关于代码的推理会变得更加困难。

    以问题中链接的 Scheme 示例为例:

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

    多次调用(step)然后返回不同的值。

    我只想创建一个闭包:

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

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

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

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

    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 迭代控制结构。使用闭包,我们可以摆脱对生成器内部控制结构的使用,只提供状态转换代码。

    【讨论】:

      【解决方案2】:

      下面是一个 C++ 示例,它使用光纤模拟生成器:

      Yield Return Iterator for Native C++ Using Fibers

      “收益回报”迭代器是 为创建的语言功能 原因之一:简单。这是 通常更容易迭代 跨越整个收藏,存储所有 局部变量中需要的上下文, 而不是制作一个复杂的, 存储其的自定义迭代器对象 跨越后续检索的状态 操作。

      还有原始 C 例程setjmp, longjmp 来实现类似的结果。
      (luacoroutines都是用上面的方法实现的)

      【讨论】:

      • 不是只有 Windows 的解决方案吗?
      • @Paweł:使用 Fibers 是一种仅限 Windows 的解决方案,是的。
      • 谢谢,这就是我害怕的
      • 这里是 C++ 中生成器的实现,它不需要特定于平台的代码,但它非常短:codeproject.com/Articles/29524/Generators-in-C
      • 干得好@c-smile。你应该发布一个答案。
      【解决方案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)]
      

      【讨论】:

      • (x + 1 for x in y if x > 100) 不是生成器...它是一个列表理解表达式。
      • Jean-Lou:在定义一个可迭代的y 对象后,将其键入 Python shell。表示为&lt;generator object &lt;genexpr&gt; at 0x7fa5b310ec30&gt;。告诉我这不是生成器表达式。
      【解决方案4】:

      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
      

      【讨论】:

        【解决方案5】:

        对于@dmitry_vk 关于 Common Lisp 的回答,我想补充一点,在 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 更加通用,因此这里无需介绍特殊语法。

        让我们考虑另一个用例 - 对一些自定义树进行迭代。假设树由指向其childrennodes 表示。

        (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 中以某种不太复杂的方式实现的生成器使用的实用示例。

        附:您还可以查看SERIES Lisp 库,它实现了与 90 年代生成器类似的概念。或者 CLAZY - 从 2000 年代后期开始。

        【讨论】:

          【解决方案6】:

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

          因此,任何允许我们在特殊语法中定义一元操作的语言都可以在这里使用。

          • VB.NET/C#(Linq - 但 C# 已经获得 yield return
          • Scala(用于理解)
          • Haskell(do-notation)
          • F#/OCaml(计算表达式/执行)

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

          【讨论】:

            【解决方案7】:

            Common Lisp 虽然没有原生延续,但允许使用像 cl-cont 这样的 CPS 转换器创建定界延续。因此,Common Lisp 中的生成器可以用与 Scheme 生成器几乎相同的方式编写。

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

            【讨论】:

              【解决方案8】:

              鲁比:

              生成器函数:

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

              【讨论】:

                【解决方案9】:

                一般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(空函数的默认返回值)。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2020-11-19
                  • 2011-09-17
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-05-15
                  • 2022-08-23
                  • 2010-09-13
                  相关资源
                  最近更新 更多