Python for loop and iterator behavior

前端 未结 6 1792
迷失自我
迷失自我 2020-12-01 09:23

I wanted to understand a bit more about iterators, so please correct me if I\'m wrong.

An iterator is an object which has a pointer to the next object a

6条回答
  •  情歌与酒
    2020-12-01 09:40

    Excerpt from the Python Practice book:


    5. Iterators & Generators

    5.1. Iterators

    We use for statement for looping over a list.

    >>> for i in [1, 2, 3, 4]:
    ...     print i,
    ...
    1
    2
    3
    4
    

    If we use it with a string, it loops over its characters.

    >>> for c in "python":
    ...     print c
    ...
    p
    y
    t
    h
    o
    n
    

    If we use it with a dictionary, it loops over its keys.

    >>> for k in {"x": 1, "y": 2}:
    ...     print k
    ...
    y
    x
    

    If we use it with a file, it loops over lines of the file.

    >>> for line in open("a.txt"):
    ...     print line,
    ...
    first line
    second line
    

    So there are many types of objects which can be used with a for loop. These are called iterable objects.

    There are many functions which consume these iterables.

    >>> ",".join(["a", "b", "c"])
    'a,b,c'
    >>> ",".join({"x": 1, "y": 2})
    'y,x'
    >>> list("python")
    ['p', 'y', 't', 'h', 'o', 'n']
    >>> list({"x": 1, "y": 2})
    ['y', 'x']
    

    5.1.1. The Iteration Protocol

    The built-in function iter takes an iterable object and returns an iterator.

        >>> x = iter([1, 2, 3])
    >>> x
    
    >>> x.next()
    1
    >>> x.next()
    2
    >>> x.next()
    3
    >>> x.next()
    Traceback (most recent call last):
      File "", line 1, in 
    

    StopIteration

    Each time we call the next method on the iterator gives us the next element. If there are no more elements, it raises a StopIteration.

    Iterators are implemented as classes. Here is an iterator that works like built-in xrange function.

    class yrange:
        def __init__(self, n):
            self.i = 0
            self.n = n
    
        def __iter__(self):
            return self
    
        def next(self):
            if self.i < self.n:
                i = self.i
                self.i += 1
                return i
            else:
                raise StopIteration()
    

    The iter method is what makes an object iterable. Behind the scenes, the iter function calls iter method on the given object.

    The return value of iter is an iterator. It should have a next method and raise StopIteration when there are no more elements.

    Lets try it out:

    >>> y = yrange(3)
    >>> y.next()
    0
    >>> y.next()
    1
    >>> y.next()
    2
    >>> y.next()
    Traceback (most recent call last):
      File "", line 1, in 
      File "", line 14, in next
    

    StopIteration

    Many built-in functions accept iterators as arguments.

    >>> list(yrange(5))
    [0, 1, 2, 3, 4]
    >>> sum(yrange(5))
    10
    

    In the above case, both the iterable and iterator are the same object. Notice that the iter method returned self. It need not be the case always.

    class zrange:
        def __init__(self, n):
            self.n = n
    
        def __iter__(self):
            return zrange_iter(self.n)
    
    class zrange_iter:
        def __init__(self, n):
            self.i = 0
            self.n = n
    
        def __iter__(self):
            # Iterators are iterables too.
            # Adding this functions to make them so.
            return self
    
        def next(self):
            if self.i < self.n:
                i = self.i
                self.i += 1
                return i
            else:
                raise StopIteration()
    

    If both iteratable and iterator are the same object, it is consumed in a single iteration.

    >>> y = yrange(5)
    >>> list(y)
    [0, 1, 2, 3, 4]
    >>> list(y)
    []
    >>> z = zrange(5)
    >>> list(z)
    [0, 1, 2, 3, 4]
    >>> list(z)
    [0, 1, 2, 3, 4]
    

    5.2. Generators

    Generators simplifies creation of iterators. A generator is a function that produces a sequence of results instead of a single value.

    def yrange(n):
       i = 0
        while i < n:
            yield i
            i += 1
    

    Each time the yield statement is executed the function generates a new value.

    >>> y = yrange(3)
    >>> y
    
    >>> y.next()
    0
    >>> y.next()
    1
    >>> y.next()
    2
    >>> y.next()
    Traceback (most recent call last):
      File "", line 1, in 
    

    StopIteration

    So a generator is also an iterator. You don’t have to worry about the iterator protocol.

    The word “generator” is confusingly used to mean both the function that generates and what it generates. In this chapter, I’ll use the word “generator” to mean the generated object and “generator function” to mean the function that generates it.

    Can you think about how it is working internally?

    When a generator function is called, it returns a generator object without even beginning execution of the function. When next method is called for the first time, the function starts executing until it reaches yield statement. The yielded value is returned by the next call.

    The following example demonstrates the interplay between yield and call to next method on generator object.

    >>> def foo():
    ...     print "begin"
    ...     for i in range(3):
    ...         print "before yield", i
    ...         yield i
    ...         print "after yield", i
    ...     print "end"
    ...
    >>> f = foo()
    >>> f.next()
    begin
    before yield 0
    0
    >>> f.next()
    after yield 0
    before yield 1
    1
    >>> f.next()
    after yield 1
    before yield 2
    2
    >>> f.next()
    after yield 2
    end
    Traceback (most recent call last):
      File "", line 1, in 
    

    StopIteration

    Lets see an example:

    def integers():
        """Infinite sequence of integers."""
        i = 1
        while True:
            yield i
            i = i + 1
    
    def squares():
        for i in integers():
            yield i * i
    
    def take(n, seq):
        """Returns first n values from the given sequence."""
        seq = iter(seq)
        result = []
        try:
            for i in range(n):
                result.append(seq.next())
        except StopIteration:
            pass
        return result
    
    print take(5, squares()) # prints [1, 4, 9, 16, 25]
    

提交回复
热议问题