代码之家  ›  专栏  ›  技术社区  ›  ollyc

Python迭代器如何在新样式类中动态分配self.next?

  •  12
  • ollyc  · 技术社区  · 17 年前

    作为某些WSGI中间件的一部分,我想编写一个python类,该类封装一个迭代器,以在迭代器上实现一个close方法。

    当我用旧式类尝试时,这可以很好地工作,但当我用新式类尝试时会抛出TypeError。我需要做什么才能让这个与新型类一起工作?

    例子:

    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'
    
    4 回复  |  直到 17 年前
        1
  •  9
  •   Glenn Maynard    17 年前

    你试图做的事情是有道理的,但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
    

    我不熟悉Python的内部,但似乎一个对象的“next”方法被缓存在 tp_iternext http://docs.python.org/c-api/typeobj.html#tp_iternext ),然后当类更改时,它不会更新。

    你可以通过保留原始的next函数并显式地包装它来解决这个问题:

    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
    

    不

        2
  •  6
  •   David Wolever    14 年前

    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的实现:

    #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类型时(即解释器首次计算 class __new__ 方法被调用),类型上的值 PyTypeObject 类型已创建,否 tp_iternext NULL ,导致 PyIter_Check

    现在,正如Glenn指出的那样,这几乎可以肯定是CPython中的一个错误。特别是考虑到只有当被测试的对象不可迭代或动态分配时,纠正它才会影响性能 下一个 非常 大约):

    #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 将存在,但情况不一定如此(即,因为 下一个 函数存在于实例上,而不是类型上)。

    所以!这就是为什么当你试图迭代一个动态分配的新型实例时,会发生令人惊讶的事情 下一个

        3
  •  4
  •   Piotr Dobrogost    14 年前

    iter next 可在实例中调用,但在类中调用 IteratorWrapper2 下一个 。下面是您问题的简单版本

    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
    

    所以解决办法就是回去 otheriter 在 __iter__

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

    下一个 ,包装内部迭代器

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

    虽然我不明白为什么不 只需使用 self.next

        4
  •  3
  •   Lennart Regebro    17 年前

    __iter__

    编辑:现在有两种方法。一次,monkey修补包装好的迭代器,第二次,kitty包装迭代器。

    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()
    

    这两种方法都适用于新旧风格的类。