简体   繁体   中英

Understanding the __iter__() method in terms of a class

I'm currently trying to learn more about for loops and generators, and I came across this piece of code:

class OdeStore:
    def __init__(self,data):
        self.data=data #list of form [[t0,u0],[t1,u1]...]
    def __iter__(self):
        for t,u in self.data: #specify the data iterated over
            yield u #how it is yielded
        
store=OdeStore([[0,1],[0.1,1.1],[0.2,1.3]])
for u in store:
   print(u)

If my understanding is correct, we may define the __iter__() inside a class to define how a for loop will act when it involves an instance of the class. In this case, we state that once we loop over an instance of the OdeStore class, the second entry of the nested lists will be yielded. Is my interpretation of the code correct? My confusion mainly comes from including a for loop inside the definition of __iter__ .

For example if we now apply the list() function, we obtain:

list(store) # returns [1, 1.1, 1.3]

I've tried to find how the list() function operates, but I suppose it appends entries of an object to an empty list (or something thereof).

Perhaps inserting some print statements will provide more information:

class OdeStore:
    def __init__(self,data):
        self.data=data #list of form [[t0,u0],[t1,u1]...]
    def __iter__(self):
        print("  starting __iter__")
        for t,u in self.data: #specify the data iterated over
            print(f"  iter at {t},{u}, returning {u}")
            yield u #how it is yielded
        
print("iterating over OdeStore")
store=OdeStore([[0,1],[0.1,1.1],[0.2,1.3]])
for u in store:
   print(u)

print("starting list")
l = list(store)
print(f"list: {l}")

Output:

iterating over OdeStore
  starting __iter__
  iter at 0,1, returning 1
1
  iter at 0.1,1.1, returning 1.1
1.1
  iter at 0.2,1.3, returning 1.3
1.3
starting list
  starting __iter__
  iter at 0,1, returning 1
  iter at 0.1,1.1, returning 1.1
  iter at 0.2,1.3, returning 1.3
list: [1, 1.1, 1.3]

You can see the initial call to __iter__ (made when the for loop starts) and that the for loop inside __iter__ is "paused" by the yield call. Conceptually, the state of execution is saved by the yield call, a value is returned to the for loop, and the next time the for loop calls in to the iterator execution is resumed with the saved context.

I've tried to find how the list() function operates, but I suppose it appends entries of an object to an empty list (or something thereof).

I believe this is correct on a conceptual level. As you can see from the output, during list construction an iterator is created and walks the OdeStore.

Maybe breaking it down this way will make it a little more concrete. Let's change our script to this:

class OdeStore:
    def __init__(self,data):
        self.data=data #list of form [[t0,u0],[t1,u1]...]
    def __iter__(self):
        print("  starting __iter__")
        for index in range(0, len(self.data)):
            print(f"  iter at index {index}, returning {self.data[index][1]}")
            yield self.data[index][1]
        #for t,u in self.data: #specify the data iterated over
        #    print(f"  iter at {t},{u}, returning {u}")
        #    yield u #how it is yielded
        
store=OdeStore([[0,1],[0.1,1.1],[0.2,1.3]])
print("Creating iterator")
iter = iter(store)
print("calling next 1st time")
print(next(iter))
print("calling next 2nd time")
print(next(iter))
print("calling next 3rd time")
print(next(iter))
print("calling next 4th time")
print(next(iter))

Output:

Creating iterator
calling next 1st time
  starting __iter__
  iter at index 0, returning 1
1
calling next 2nd time
  iter at index 1, returning 1.1
1.1
calling next 3rd time
  iter at index 2, returning 1.3
1.3
calling next 4th time
Traceback (most recent call last):
  File "./odestore.py", line 25, in <module>
    print(next(iter))
StopIteration

Conceptually, every yield is saving off the index and returning the calculated value. When the iterator is invoked again, this stored index is incremented (because of the for loop in __iter__ ). The call to yield saves of the new value of index and returns the calculated value. When we run out of values, __iter__ returns instead of yielding and the iterator implementation class (called generator ) raises the StopIteration exception.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM