2009-07-20 30 views
12

由於一些WSGI中間件的一部分,我想編寫一個包裝迭代器來實現的迭代器關閉方法Python類。Python的迭代器 - 如何動態一種新的風格類中分配self.next?

,當我和一箇舊式類試試這工作得很好,但拋出當我嘗試用一​​種新的風格類類型錯誤。我需要做些什麼才能使這個課程適合新式課程?

實施例:

class IteratorWrapper1: 

    def __init__(self, otheriter): 
     self._iterator = otheriter 
     self.next = otheriter.next 

    def __iter__(self): 
     return self 

    def close(self): 
     if getattr(self._iterator, 'close', None) is not None: 
      self._iterator.close() 
     # other arbitrary resource cleanup code here 

class IteratorWrapper2(object): 

    def __init__(self, otheriter): 
     self._iterator = otheriter 
     self.next = otheriter.next 

    def __iter__(self): 
     return self 

    def close(self): 
     if getattr(self._iterator, 'close', None) is not None: 
      self._iterator.close() 
     # other arbitrary resource cleanup code here 

if __name__ == "__main__": 
    for i in IteratorWrapper1(iter([1, 2, 3])): 
     print i 

    for j in IteratorWrapper2(iter([1, 2, 3])): 
     print j 

給出以下輸出:

1 
2 
3 
Traceback (most recent call last): 
    ... 
TypeError: iter() returned non-iterator of type 'IteratorWrapper2' 

回答

9

你想做的事情是有道理的,但是Python裏面有一些壞事發生在這裏。

class foo(object): 
    c = 0 
    def __init__(self): 
     self.next = self.next2 

    def __iter__(self): 
     return self 

    def next(self): 
     if self.c == 5: raise StopIteration 
     self.c += 1 
     return 1 

    def next2(self): 
     if self.c == 5: raise StopIteration 
     self.c += 1 
     return 2 

it = iter(foo()) 
# Outputs: <bound method foo.next2 of <__main__.foo object at 0xb7d5030c>> 
print it.next 
# 2 
print it.next() 
# 1?! 
for x in it: 
    print x 

FOO()是改變對飛它的下一個方法的迭代器 - 完全合法的其他地方在Python。我們創建的迭代器具有我們期望的方法:it.next是next2。當我們直接使用迭代器時,通過調用next(),我們得到2.然而,當我們在for循環中使用它時,我們得到原來的下一個,我們已經清楚地覆蓋了原來的。我不熟悉Python的內部,但它似乎像一個對象的「下一個」方法被緩存在tp_iternexthttp://docs.python.org/c-api/typeobj.html#tp_iternext),然後它不更新時,類更改。

這絕對是一個Python錯誤。也許這是在生成器PEP中描述的,但它不在Python核心文檔中,並且與正常的Python行爲完全不一致。

你可以變通的作法是保持原有的下一個功能,並明確其包裝:

class IteratorWrapper2(object): 
    def __init__(self, otheriter): 
     self.wrapped_iter_next = otheriter.next 
    def __iter__(self): 
     return self 
    def next(self): 
     return self.wrapped_iter_next() 

for j in IteratorWrapper2(iter([1, 2, 3])): 
    print j 

...但是這顯然是低效率的,你應該必須這樣做。

3

只返回迭代器。這就是__iter__的用途。這是沒有意義的嘗試猴子修補對象爲在迭代是,並返回它時,你已經有一個迭代器。

編輯:現在有兩種方法。一次,猴子修補包裝的迭代器,其次,小貓包裝迭代器。

class IteratorWrapperMonkey(object): 

    def __init__(self, otheriter): 
     self.otheriter = otheriter 
     self.otheriter.close = self.close 

    def close(self): 
     print "Closed!" 

    def __iter__(self): 
     return self.otheriter 

class IteratorWrapperKitten(object): 

    def __init__(self, otheriter): 
     self.otheriter = otheriter 

    def __iter__(self): 
     return self 

    def next(self): 
     return self.otheriter.next() 

    def close(self): 
     print "Closed!" 

class PatchableIterator(object): 

    def __init__(self, inp): 
     self.iter = iter(inp) 

    def next(self): 
     return self.iter.next() 

    def __iter__(self): 
     return self 

if __name__ == "__main__": 
    monkey = IteratorWrapperMonkey(PatchableIterator([1, 2, 3])) 
    for i in monkey: 
     print i 
    monkey.close() 

    kitten = IteratorWrapperKitten(iter([1, 2, 3])) 
    for i in kitten: 
     print i 
    kitten.close() 

這兩種方法都適用於新式和舊式的類。

+0

謝謝你的回答!也許我應該給出更多的上下文:我有一個包裝類的原因是,我可以爲返回的迭代器對象添加一個close方法,並且只是返回原始迭代器不會給我一個這樣的機會。 – ollyc 2009-07-20 08:36:24

+0

我可以想到幾種我可能想要這樣做的情況。它們都是人爲的(也就是說,我從來沒有真的需要這樣做),所以我不打算列出它們,但這是一個完全有效的想要做的事情。 – 2009-07-20 08:44:24

+0

另外,這不是猴子補丁。猴子修補正在從類的外部修改類的方法,而不是修改自己方法的類。 – 2009-07-20 08:47:43

4

看起來像內置iter不檢查next可調用的實例,但在一類和IteratorWrapper2沒有任何next。下面是你的問題的簡化版本

class IteratorWrapper2(object): 

    def __init__(self, otheriter): 
     self.next = otheriter.next 

    def __iter__(self): 
     return self 

it=iter([1, 2, 3]) 
myit = IteratorWrapper2(it) 

IteratorWrapper2.next # fails that is why iter(myit) fails 
iter(myit) # fails 

這樣的解決辦法是在__iter__

class IteratorWrapper2(object): 

    def __init__(self, otheriter): 
     self.otheriter = otheriter 

    def __iter__(self): 
     return self.otheriter 

返回otheriter或寫自己的next,包裹內迭代

class IteratorWrapper2(object): 

    def __init__(self, otheriter): 
     self.otheriter = otheriter 

    def next(self): 
     return self.otheriter.next() 

    def __iter__(self): 
     return self 

雖然我做的不明白爲什麼不iter只是使用實例的self.next

6

還有一堆的地方CPython的利用基於類,而不是性能例如性能令人驚訝的快捷方式。這是其中的一個地方。

下面是一個簡單的例子,說明了這個問題:

def DynamicNext(object): 
    def __init__(self): 
     self.next = lambda: 42 

而這裏發生了什麼:

 
>>> instance = DynamicNext() 
>>> next(instance) 
… 
TypeError: DynamicNext object is not an iterator 
>>> 

現在,挖掘到CPython的源代碼(來自2.7.2),這裏的實施next()內建:

static PyObject * 
builtin_next(PyObject *self, PyObject *args) 
{ 
    … 
    if (!PyIter_Check(it)) { 
     PyErr_Format(PyExc_TypeError, 
      "%.200s object is not an iterator", 
      it->ob_type->tp_name); 
     return NULL; 
    } 
    … 
} 

而這裏的實現PyIter_Check的N:

#define PyIter_Check(obj) \ 
    (PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \ 
    (obj)->ob_type->tp_iternext != NULL && \ 
    (obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented) 

第一行,PyType_HasFeature(…),就是擴大所有的常量和宏之類的東西,相當於DynamicNext.__class__.__flags__ & 1L<<17 != 0後:

 
>>> instance.__class__.__flags__ & 1L<<17 != 0 
True 

使檢查顯然不是失敗...這必須意味着下一次檢查 - (obj)->ob_type->tp_iternext != NULL-失敗。

在Python中,這條線是大致(大約!),相當於hasattr(type(instance), "next")

 
>>> type(instance) 
__main__.DynamicNext 
>>> hasattr(type(instance), "next") 
False 

因爲DynamicNext型沒有next方法,這顯然失敗 - 只有該類型的實例做。

現在,我的CPython foo很弱,所以我將不得不在這裏開始一些有教育意義的猜測......但我相信它們是準確的。

當創建CPython的類型(即,當解釋器首先計算class塊和類的元類__new__方法被調用),上式的PyTypeObject結構中的值被初始化...因此,如果,當DynamicNext類型創建,則不存在next方法,tp_iternext字段將設置爲NULL,導致PyIter_Check返回false。現在

,因爲格倫指出,這幾乎可以肯定是在CPython的一個錯誤......特別是考慮到糾正它只會當被測試無論是物撞擊性能不迭代或動態分配next方法(非常大約):

#define PyIter_Check(obj) \ 
    (((PyType_HasFeature((obj)->ob_type, Py_TPFLAGS_HAVE_ITER) && \ 
     (obj)->ob_type->tp_iternext != NULL && \ 
     (obj)->ob_type->tp_iternext != &_PyObject_NextNotImplemented)) || \ 
     (PyObject_HasAttrString((obj), "next") && \ 
     PyCallable_Check(PyObject_GetAttrString((obj), "next")))) 

編輯:挖一點點後,修復不會這麼簡單,因爲代碼的至少某些部分假設,如果PyIter_Check(it)回報true,然後*it->ob_type->tp_iternext將存在...這不是必需的一般情況下(即因爲next功能存在於實例上,而不是類型)。

SO!這就是爲什麼當您嘗試使用動態分配的next方法遍歷新樣式實例時出現令人驚訝的事情。