作为一些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'
答案 0 :(得分:9)
你想要做的事情是有道理的,但这里有一些邪恶的东西在这里。
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_iternext
(http://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
......但这显然效率较低,你应该不必须这样做。
答案 1 :(得分:6)
有很多地方CPython基于 class 属性而非实例属性采用令人惊讶的快捷方式。这是其中一个地方。
以下是一个演示此问题的简单示例:
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 foo很弱,所以我不得不在这里开始做一些有根据的猜测......但我相信它们是准确的。
当创建CPython类型时(也就是说,当解释器首先评估class
块并且调用类'metaclass'__new__
方法时),类型{{1}上的值} struct已初始化...因此,如果在创建PyTypeObject
类型时,不存在DynamicNext
方法,则next
字段将设置为tp_iternext
,从而导致{{1返回false。
现在,正如Glenn指出的那样,这几乎肯定是CPython中的一个错误...特别是考虑到正在测试的对象不可迭代或动态分配NULL
方法时,纠正它只会影响性能( 非常大约):
PyIter_Check
编辑:经过一些挖掘后,修复程序就不那么简单了,因为至少代码的某些部分会假设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
函数,而不是类型)。
SO!这就是为什么当您尝试使用动态分配的*it->ob_type->tp_iternext
方法迭代新样式实例时会发生令人惊讶的事情。
答案 2 :(得分: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
所以解决方案是在otheriter
__iter__
class IteratorWrapper2(object):
def __init__(self, otheriter):
self.otheriter = otheriter
def __iter__(self):
return self.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
实例。
答案 3 :(得分: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()
这两种方法都适用于新旧类。