Return a non iterator from __iter__

后端 未结 4 992
离开以前
离开以前 2021-01-03 14:14
class test(object):
    def __init__(self):
        pass
    def __iter__(self):
        return \"my string\"

o = test()
print iter(o)

Why does th

相关标签:
4条回答
  • 2021-01-03 14:24

    The code can be repaired by adding an iter() call:

    class test(object):
        def __init__(self):
            pass
        def __iter__(self):
            return iter("my string")
    

    Here is a sample run:

    >>> o = test()
    >>> iter(o)
    <iterator object at 0x106bfa490>
    >>> list(o)
    ['m', 'y', ' ', 's', 't', 'r', 'i', 'n', 'g']
    

    The reason for the original error is that the API for __iter__ purports to return an actual iterator. The iter() function checks to make sure the contract is fulfilled.

    Note, this kind of error checking occurs in other places as well. For example, the len() function checks to make sure a __len__() method returns an integer:

    >>> class A:
            def __len__(self):
                return 'hello'
    
    >>> len(A())
    Traceback (most recent call last):
      File "<pyshell#4>", line 1, in <module>
        len(A())
    TypeError: 'str' object cannot be interpreted as an integer
    
    0 讨论(0)
  • 2021-01-03 14:33

    To answer your specific question. Python2 appears to check for the presence of a .next class attribute:

    >>> class test(object):
    ...     next = None
    ...     def __iter__(self):
    ...         return self
    ... 
    >>> print iter(test())
    <__main__.test object at 0x7fcef75c2f50>
    

    An instance attribute won't do:

    >>> class test(object):
    ...    def __init__(self):
    ...        self.next = None
    ...    def __iter__(self):
    ...        return self
    ... 
    >>> print iter(test())
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: iter() returned non-iterator of type 'test'
    
    0 讨论(0)
  • 2021-01-03 14:36

    str is an iterable but not an iterator, subtle but important difference. See this answer for an explanation.

    You want to return an object with __next__ (or just next if py2) which is what str returns when is iterated.

    def __iter__(self):
          return iter("my string")
    

    str does not implement __next__

    In [139]: s = 'mystring'
    
    In [140]: next(s)
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-140-bc0566bea448> in <module>()
    ----> 1 next(s)
    
    TypeError: 'str' object is not an iterator
    

    However calling iter returns the iterator, which is what looping calls:

    In [141]: next(iter(s))
    Out[141]: 'm'
    

    You get the same problem returning anything without a __next__ (or next in py2) method

    You can use a generator, which itself has __iter__ that returns self:

    def gen():
        yield 'foo'
    
    gg = gen()
    
    gg is gg.__iter__()
    True
    
    gg.__next__()
    'foo'
    
    class Something:
         def __iter__(self):
             return gen()
    
    list(Something())
    ['foo']
    

    Or a class where you implement __next__ yourself, like this class similar to the one on the Ops post (you also have to handle StopIteration which stops the loop)

    class test:
        def __init__(self, somestring):
            self.s = iter(somestring)
    
        def __iter__(self):
            return self
    
        def __next__(self):
            return next(self.s) ## this exhausts the generator and raises StopIteration when done.
    
    In [3]: s = test('foo')
    
    In [4]: for i in s:
       ...:     print(i)
       ...:
    f
    o
    o
    
    0 讨论(0)
  • 2021-01-03 14:41

    The purpose of __iter__ magic function is to return something that you can iterate (e.g. loop) through. The most common solution is to return iter(something) where something could be a list, a tuple, set, dictionary, a string... anything that we can iterate through. Take a look at this example:

    class Band:
        def __init__(self):
            self.members = []
    
        def add_member(self, name):
            self.members.append(name)
    
        def __iter__(self):
            return iter(self.members)
    
    if __name__ == '__main__':
        band = Band()
        band.add_member('Peter')
        band.add_member('Paul')
        band.add_member('Mary')
    
        # Magic of __iter__:
        for member in band:
            print(member)
    

    Output:

    Peter
    Paul
    Mary
    

    In this case, the __iter__ magic function allows us to loop through band as if it is a collection of members. That means in your case, return "my string" will not do. If you want a list of chars in "my string":

    def __iter__(self):
        return iter("my string")  # ==> m, y, ' ', s, t, r, i, n, g
    

    However, if you want to return a list with a single element "my string", then:

    def __iter__(self):
        return iter(["my string"])
    
    0 讨论(0)
提交回复
热议问题