如何编写能够返回Python迭代器对象的Python代码(Python模块)?

问题描述:

后,我已经在短期内用C写一个简约Python3.6扩展模块成功++(see here)我打算提供一个Python模块,它不一样的下面的Python功能iterUniqueCombos()如何编写能够返回Python迭代器对象的Python代码(Python模块)?

def iterUniqueCombos(lstOfSortableItems, sizeOfCombo): 
    lstOfSortedItems = sorted(lstOfSortableItems) 
    sizeOfList = len(lstOfSortedItems) 

    lstComboCandidate = [] 

    def idxNextUnique(idxItemOfList): 
     idxNextUniqueCandidate = idxItemOfList + 1 

     while (
       idxNextUniqueCandidate < sizeOfList 
        and 
       lstOfSortedItems[idxNextUniqueCandidate] == lstOfSortedItems[idxItemOfList] 
     ): # while 
      idxNextUniqueCandidate += 1 

     idxNextUnique = idxNextUniqueCandidate 

     return idxNextUnique 

    def combinate(idxItemOfList): 
     if len(lstComboCandidate) == sizeOfCombo: 
      yield tuple(lstComboCandidate) 
     elif sizeOfList - idxItemOfList >= sizeOfCombo - len(lstComboCandidate): 
      lstComboCandidate.append(lstOfSortedItems[idxItemOfList]) 
      yield from combinate(idxItemOfList + 1) 
      lstComboCandidate.pop() 
      yield from combinate(idxNextUnique(idxItemOfList)) 

    yield from combinate(0) 

我有一些基本的了解Python和C++编程,但绝对不知道如何将Pythons 产生转换为Python扩展模块的C++代码。所以我的问题是:

How to write C++ code (of a Python module) able to return a Python iterator object?

让我开始任何提示的欢迎。

UPDATE(状态2017年5月7日):

两个注释:产量没有C++当量。我首先在Python中手动实现迭代器协议,以摆脱思维模式下的收益率和收益率。 - user2357112 4月26日1:16danny在回答这个问题的答案的提示是一样的问:“我该如何实现,而无需使用产量迭代器”,但在C++的扩展,而不是纯Python。为了消除yield并从头开始编写Python扩展模块的C代码(导致下雨Segmentation Fault错误),我通过重写算法代码的方式重新编写代码,从而使我的编程工作陷入了错误的方向。

The state-of-the-art of my current knowledge on the subject of the question is that using Cython it is possible to translate the above Python code (which is using yield) directly into C code of a Python extension module.

这不仅可以使用Python代码只是因为它是(无需重写任何东西),但除了通过用Cython使用yield运行在算法创建的扩展模块的速度至少两倍快从所创建的使用__iter____next__重写算法一个迭代类的扩展模块(如果没有用Cython特定速度优化代码被添加到Python脚本后者是有效的)。

+1

'yield'没有C++等价物。我会从[手动实现迭代器协议]开始(https://www.python。org/dev/peps/pep-0234 /),以摆脱'yield'和'yield from'的思维模式。 – user2357112

这比一个完整的回答你的问题编辑响应 - 我同意丹尼的回答的要点,你需要在一个类来实现这个一个__next__/next方法(取决于Python的版本)。在你的编辑中你断言它一定是可能的,因为Cython可以做到这一点。我认为值得看看Cython究竟是怎么做的。

开始与一个基本的例子(拾取,因为它具有一些不同的yield语句和一个循环):

def basic_iter(n): 
    a = 0 
    b = 5 
    yield a 
    a+=3 
    yield b 
    b+=2 

    for i in range(n): 
     yield a+b+n 
     a = b+1 
     b*=2 
    yield 50 

的第一件事用Cython所做的是限定与实现__next__一个__Pyx_Generator_Next方法的__pyx_CoroutineObject C级/ next。该__pyx_CoroutineObject的几个相关属性:

  • body - 实现你所定义的逻辑C函数指针。
  • resume_label - 一个整数用来记住你多远通过body
  • closure定义的功能得到了 - 存储内body使用的所有变量的自定义创建的C类。

在一个稍微迂回的方式,__Pyx_Generator_Next调用body属性,这是你已经定义了Python代码的翻译。

让我们再看看功能如何分配给body作品 - 在我的例子称为__pyx_gb_5iters_2generator的情况。它做的第一件事就是用resume_label跳转到正确的yield声明:

/*  a = 0    # <<<<<<<<<<<<<< */ 
__pyx_cur_scope->__pyx_v_a = __pyx_int_0 

yield设置resume_label和:

switch (__pyx_generator->resume_label) { 
    case 0: goto __pyx_L3_first_run; 
    case 1: goto __pyx_L4_resume_from_yield; 
    case 2: goto __pyx_L5_resume_from_yield; 
    case 3: goto __pyx_L8_resume_from_yield; 
    case 4: goto __pyx_L9_resume_from_yield; 
    default: /* CPython raises the right error here */ 
    __Pyx_RefNannyFinishContext(); 
    return NULL; 
    } 

任何变量赋值通过closure结构(本地命名__pyx_cur_scope完成(resume_label允许您下次直接跳回):

__pyx_generator->resume_label = 1; 
return __pyx_r; 

循环稍微复杂一些,但基本上是一样的 - 它使用goto跳转到C循环(这是合法的)。

最后,一旦它到达最终它提出了一个StopIteration错误:

PyErr_SetNone(PyExc_StopIteration); 

总之,用Cython做你已经被告知该怎么做:它定义了__next__next类方法并使用该类来跟踪状态。因为它是自动化的,所以它能很好地跟踪引用计数,从而避免您遇到的错误Segmentation Fault。使用goto返回到前一个执行点是有效的,但需要小心。

我可以看到,为什么在一个__next__/next功能方面重写你生成函数C是吸引人,而用Cython明确提供这样做没有用C写你自己的一个简单的方法,但它不使用任何特殊技术在你已经被告知的情况下做翻译。

+0

你写道:“Cython显然提供了一种直接的方式来完成它,而不用自己写C,但它并没有使用任何特殊的技术来完成你已经被告知的翻译。”并且同时如果我已经理解你的答案了),请解释一下Cython用来将Python'yield'翻译成一个合适的C-equivalent的特殊技术:我使用了goto语句来切换迭代状态保持在一个特殊的“封闭”结构中。 – Claudio

+1

你的理解是正确的。我认为我的观点是,这不是你自己写的。我怀疑如果你写了一个可以被多次调用的函数来像Python生成器一样为C程序员工作,他们可能会想出类似的东西(也许避免使用goto ...)。无论如何 - 我大多认为值得关注一下Cython是如何做到这一点的。它会显示一个有用的机制来复制,或者说服你,你宁可不自己写它,你应该只使用Cython(这两个都是好结果) – DavidW

+0

在这种情况下,知道Python解释器在它自己的C代码中执行它。如果Cython只是复制Python本身的功能,我不会感到惊讶。 – Claudio

在Python迭代器是发电机的一种特殊形式,并且由含有方法__iter__next类实现其中__iter__返回selfnext返回反过来每个值,在迭代结束提高StopIteration - see PEP

为了提供C++等价物,C++代码需要实现那些相同的python函数以符合协议。生成的扩展类型是一个迭代器。

换句话说,在这个问题的答案是一样的问:“如何实现一个迭代器,而无需使用yield”,但在C++的扩展,而不是纯Python。这个堆栈溢出有几个现有的答案。

NB - next__next__关于Python 3

+0

答案直接回答了如何实现一个迭代器的问题,它比'在这里实现PEP中描述的协议'的评论更详细。所以我觉得它更清晰,更合适。如果你的问题不再适用,或者你觉得已经被其他人考虑过了,那么考虑锁定/更新它。 – danny

+0

查看答案的最后一行。 – danny

+0

我同意审稿人的结论。答案依然有效,答案中不包含上述答案中尚未提供的其他相关信息。 – danny