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

oll*_*lyc 12 python iterator

作为一些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
Run Code Online (Sandbox Code Playgroud)

给出以下输出:

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

Gle*_*ard 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
Run Code Online (Sandbox Code Playgroud)

foo()是一个迭代器,它可以动态修改它的下一个方法 - 在Python中的其他地方完全合法.我们创建的迭代器,它有我们期望的方法:it.next是next2.当我们直接使用迭代器时,通过调用next(),我们得到2.然而,当我们在for循环中使用它时,我们得到原始的next,我们已经明确地覆盖了它.

我不熟悉Python内部,但似乎对象的"下一个"方法正在缓存tp_iternext(http://docs.python.org/c-api/typeobj.html#tp_iternext),然后它没有更新当班级改变时.

这绝对是一个Python bug.也许这在生成器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
Run Code Online (Sandbox Code Playgroud)

......但是这显然是低效率的,你应该不是必须这样做.


Dav*_*ver 6

有很多地方CPython基于属性而不是实例属性采用令人惊讶的快捷方式.这是其中一个地方.

这是一个演示该问题的简单示例:

def DynamicNext(object):
    def __init__(self):
        self.next = lambda: 42
Run Code Online (Sandbox Code Playgroud)

以下是发生的事情:

>>> 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;
    }
    …
}
Run Code Online (Sandbox Code Playgroud)

这是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)
Run Code Online (Sandbox Code Playgroud)

第一行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"))))
Run Code Online (Sandbox Code Playgroud)

编辑:挖一点点后,修复不会这么简单,因为代码的至少某些部分认为,如果PyIter_Check(it)回报率true,那么*it->ob_type->tp_iternext将存在......这不一定的情况下(即,由于next功能上存在实例,而不是类型).

所以!这就是当您尝试使用动态分配的next方法迭代新样式实例时出现令人惊讶的事情的原因.