Python iterators – how to dynamically assign self.next within a new style class?

前端 未结 4 1940
感动是毒
感动是毒 2021-02-05 20:44

As part of some WSGI middleware I want to write a python class that wraps an iterator to implement a close method on the iterator.

This works fine when I try it with an

4条回答
  •  遇见更好的自我
    2021-02-05 21:06

    What you're trying to do makes sense, but there's something evil going on inside Python here.

    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: >
    print it.next
    # 2
    print it.next()
    # 1?!
    for x in it:
        print x
    

    foo() is an iterator which modifies its next method on the fly--perfectly legal anywhere else in Python. The iterator we create, it, has the method we expect: it.next is next2. When we use the iterator directly, by calling next(), we get 2. Yet, when we use it in a for loop, we get the original next, which we've clearly overwritten.

    I'm not familiar with Python internals, but it seems like an object's "next" method is being cached in tp_iternext (http://docs.python.org/c-api/typeobj.html#tp_iternext), and then it's not updated when the class is changed.

    This is definitely a Python bug. Maybe this is described in the generator PEPs, but it's not in the core Python documentation, and it's completely inconsistent with normal Python behavior.

    You could work around this by keeping the original next function, and wrapping it explicitly:

    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
    

    ... but that's obviously less efficient, and you should not have to do that.

提交回复
热议问题