The iterator

Iteration is a way of accessing elements of a collection. An iterator is an object that remembers the position of the iterator. Iterator objects are accessed from the first element of the collection until all elements have been accessed. Iterators can only move forward and not backward.


iterable

We already know that we can use for… for list, tuple, STR, etc. in… The loop syntax from which to get the data in turn for use, we call such a process called traversal, also called iteration.

However, whether all data types can be placedfor... in...“, and then letfor... in...Is taking one piece of data out of it for us to use at a time for us to iterate?

>>> for i in 100:
.    print(i)
...
Traceback (most recent call last):
  File "<stdin>", line 1.in <module>
TypeError: 'int' object is not iterable
>>>
Copy the code

It is obvious that ints are not iterable, that is, ints are not iterable


MyList = MyList = MyList = MyList = MyList = MyList = MyList
>>> class MyList(object) :..    def __init__(self) :
.        self.container = []
...
.    def add(self, item) :
.        self.container.append(item)
...
>>> mylist = MyList()
>>> mylist.add(1)
>>> mylist.add(2)
>>> mylist.add(3)
>>> for num in mylist:
.    print(num)
...
Traceback (most recent call last):
  File "<stdin>", line 1.in <module>
TypeError: 'MyList' object is not iterable
>>>
Objects in the # MyList container are also non-iterable
Copy the code

MyList = MyList; MyList = MyList; MyList = MyList; in… Find for… in… There is no way to take one piece of data in turn and return it to us, which means that we simply encapsulate a type that can hold multiple pieces of data but cannot be used iteratively.

We can pass for… in… This type of statement iterates through a single piece of data for our use and is called an Iterable.


How do you tell if an object is iterable

We can use isinstance() to determine if an object is an Iterable:

In [1] :from collections import Iterable

In [2] :isinstance([], Iterable)
Out[2] :True

In [3] :isinstance({}, Iterable)
Out[3] :True

In [4] :isinstance('abc', Iterable)
Out[4] :True

In [5] :isinstance(mylist, Iterable)
Out[5] :False

In [6] :isinstance(100, Iterable)
Out[6] :False
Copy the code


The nature of an iterable

We analyze the process of iterative use of an iterable and find that each iteration (i.e. in… Returns the next piece of data in the object, reading back until all the data has been iterated. Then, there should be a “human” in the process to keep track of how many pieces of data are accessed so that each iteration can return the next piece of data.

We refer to this “person” who helps us iterate over data as an Iterator.

The nature of an iterable is to provide us with an intermediate “person” — an iterator — to help us iterate through it.

Iterables provide us with an iterator through the __iter__() method. When we iterate over an iterable, we actually get one of the iterators provided by the iterator and then use that iterator to get each piece of data in turn.

That is, an object with an __iter__() method is an iterable.

from collections import可迭代class MyList(object) :

     def __init__(self) :
         self.container = []
                
     def add(self, item) :
         self.container.append(item)
            
     def __iter__(self) :
         """ Returns an iterator """
         We ignore for the moment how to construct an iterator object
         pass

mylist = MyList()

isinstance(mylist, Iterable) # True
Copy the code

This test found that the myList object with the __iter__() method added is already an iterable


Iter () and next() functions

List, tuple, and so on are iterables, and we get the iterators of these iterables through iter(). We then continuously use the next() function on the obtained iterator to fetch the next piece of data. The iter() function essentially calls the iterable’s __iter__ method

Let’s use ipython to debug

In [8]: li = [11.22.33.44.55]

In [9]: li_iter = iter(li)

In [10] :next(li_iter)
Out[10] :11

In [11] :next(li_iter)
Out[11] :22

In [12] :next(li_iter)
Out[12] :33

In [13] :next(li_iter)
Out[13] :44

In [14] :next(li_iter)
Out[14] :55

In [15] :next(li_iter)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-15-ab6a83f394a1> in <module>
----> 1 next(li_iter)

StopIteration:

In [16] :Copy the code


Note: Call again after we have iterated over the last datanext()The function throwsStopIterationTo tell us that all data has been iterated over and no longer needs to be executednext()The function.


How do I tell if an object is an iterator

We can use isinstance() to determine if an object is an Iterator:

In [6] :from collections import Iterator

In [7] :isinstance([], Iterator)
Out[7] :False

In [8] :isinstance(iter([]), Iterator)
Out[8] :True

In [9] :isinstance(iter("abc"), Iterator)
Out[9] :True
Copy the code


The Iterator Iterator

From the above analysis, we already know that iterators are used to help us keep track of the positions visited in each iteration. When we use the next() function on the iterator, the iterator will return data for the position next to the position it recorded. In effect, when next() is used, the iterator object’s __next__ method is called (Python3 is the object’s __next__ method, Python2 is the object’s next() method). So, to construct an iterator, we implement its __next__ method. But that’s not enough. Python requires iterators to be themselves iterable, so we also implement the __iter__ method for iterators, and __iter__ returns an iterator, which is itself an iterator, so the iterator’s __iter__ method returns itself.

One implemented__iter__Methods and__next__The object of the method is the iterator.

class MyList(object) :
    "" a custom iterable """
    
    def __init__(self) :
        self.items = []

    def add(self, val) :
        self.items.append(val)

    def __iter__(self) :
        myiterator = MyIterator(self)
        return myiterator


class MyIterator(object) :
    A custom iterator for the iterable above.
    def __init__(self, mylist) :
        self.mylist = mylist
        # current Is used to record the current accessed location
        self.current = 0

    def __next__(self) :
        if self.current < len(self.mylist.items):
            item = self.mylist.items[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration

    def __iter__(self) :
        return self

   
def main() :
    mylist = MyList()
    mylist.add(1)
    mylist.add(2)
    mylist.add(3)
    mylist.add(4)
    mylist.add(5)
    
    for num in mylist:
        print(num)
        
        
if __name__ == '__main__':
    main()
Copy the code


Running results:

1
2
3
4
5
[Finished in 0.1s]
Copy the code


for… in… Nature of the cycle

The essence of the for item in Iterable loop is to get the iterator of the Iterable through iter() and then call next() to get the next value and assign it to the item. The loop ends when it encounters an exception called StopIteration.

So we can use that as a while loop

def main() :
    mylist = MyList()
    mylist.add(1)
    mylist.add(2)
    mylist.add(3)
    mylist.add(4)
    mylist.add(5)
    
    # for num in mylist:
    # print(num)

    myli_iter = iter(mylist) Get the iterator object
    while True:
        try:
            item = next(myli_iter)
            print(item)
        except StopIteration:
            break
        

if __name__ == '__main__':
    main()
Copy the code


Application scenarios for iterators

We found that the core function of the iterator is to return the next data value by calling the next() function. If every time the data returned value is not read in an existing data set, but is calculated according to certain rules generated by the program, then means can no longer rely on an existing data set, that is to say, don’t put all want to iteration of the data of disposable cached for subsequent read in turn, this can save a lot of space for storage (memory).

For example, in mathematics there is a famous Fibonacci sequence in which the first number is 0, the second number is 1, and each subsequent number can be obtained by adding the first two numbers:

0.1.1.2.3.5.8.13.21.34.Copy the code

Now we want to pass for… in… Loop to iterate over the first n numbers in the Fibonacci sequence. So this Fibonacci sequence we can implement with iterators, where each iteration is mathematically calculated to produce the next number.

class FibIterator(object) :
    "" Fibonacci sequence iterator ""
    
    def __init__(self, n) :
        "" :param n: int specifies the first n numbers in the generated sequence.
        self.n = n
        The # current is used to store the current number generated in the sequence
        self.current = 0
        Num1 is used to store the preceding number, starting with 0 as the first number in the sequence
        self.num1 = 0
        Num2 is used to hold the previous number, starting with the second number in the sequence, 1
        self.num2 = 1

    def __next__(self) :
        """ called by next() to get the next number """
        if self.current < self.n:
            num = self.num1
            self.num1, self.num2 = self.num2, self.num1+self.num2
            self.current += 1
            return num
        else:
            raise StopIteration

    def __iter__(self) :
        An iterator's __iter__ returns itself.
        return self


if __name__ == '__main__':
    fib = FibIterator(10)
    for num in fib:
        print(num, end="")
Copy the code


The for loop is not the only one that accepts iterables

In addition to the for loop receiving iterables, lists, tuples, sets, etc., can also receive iterables.

li = list(FibIterator(10))
tp = tuple(FibIterator(10))
s = set(FibIterator(10))

print(li)
print(tp)
print(s)
Copy the code


The results are as follows:

[0.1.1.2.3.5.8.13.21.34]
(0.1.1.2.3.5.8.13.21.34)
{0.1.2.3.34.5.8.13.21}
Copy the code

Notice that the set is de-duplicated.


The public,

Create a new folder X

Nature took tens of billions of years to create our real world, while programmers took hundreds of years to create a completely different virtual world. We knock out brick by brick with a keyboard and build everything with our brains. People see 1000 as authority. We defend 1024. We are not keyboard warriors, we are just extraordinary builders of ordinary world.