Lecture Notes 18#

timer as context manager#

import time
class Timer:
    def __enter__(self):
        self.t1 = time.time()
        
    def __exit__(self, *args):
        self.t2 = time.time()
        print(f"Time used here: {self.t2 - self.t1}")
with Timer() as t:
    print("Hello")
    time.sleep(1)
    print("World")
Hello
World
Time used here: 1.000225305557251

Iterables#

  • an object that supports iteration

  • allowed in the top a for loop

iter([])
<list_iterator at 0x7f085188df00>
  • iter([...]) delegates to [...].__iter__()

'__iter__' in dir([])
True
  • not all objectes are iterable

iter(1) 
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[6], line 1
----> 1 iter(1)

TypeError: 'int' object is not iterable
'__iter__' in dir(int)
False

iterators#

Generate values of a sequence when called by next

'__next__' in dir(iter([]))  # True for an iterator
True
next(iter([1, 2, 3]))
1
it = iter([1, 2, 3])
next(it)
1
next(it)
2
next(it)
3
next(it)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[28], line 1
----> 1 next(it)

StopIteration: 
for element in [1, 2, 3]:
    print(element)
1
2
3

classes supporting iteration#

class Counter:
    def __init__(self, size):
        print("__init__:", size)
        self.size = size
        self.start = 0
    def __iter__(self):
        print("__iter__:", self.size)
        return CounterIter(self.start, self.size)
    
class CounterIter:
    def __init__(self, start, size):
        self.start = start
        self.size = size
    def __next__(self):
        if self.start < self.size:
            self.start = self.start + 1
            return self.start
        raise StopIteration
for n in Counter(3):
    print(n)
__init__: 3
__iter__: 3
1
2
3
a_iterable = Counter(3)
__init__: 3
a_iterator = iter(a_iterable)
__iter__: 3
next(a_iterator)
1
next(a_iterator)
2
next(a_iterator)
3
next(a_iterator)
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
Cell In[66], line 1
----> 1 next(a_iterator)

Cell In[59], line 18, in CounterIter.__next__(self)
     16     self.start = self.start + 1
     17     return self.start
---> 18 raise StopIteration

StopIteration: 

generators#

  • look like function

  • has yield statement instead of return

def g(n):
    print('enter g with',n)
    yield n
    yield n + 1
    print('after yield')
g(2)
<generator object g at 0x7f8e701093c0>
'__iter__' in dir(g(2))
True
'__next__' in dir(g(2))
True
list(g(2))
enter g with 2
after yield
[2, 3]
for n in g(2):
    print(n)
enter g with 2
2
3
after yield

Example#

  • a given number of Fibonacci sequence nubmers

def f(n):
    """
    Return the n first numbers in the Fibonacci sequence
    """
    count = 0
    a = 0
    b = 1
    while count < n:
        yield a
        a, b = b, a+b
        count += 1
list(f(5))
[0, 1, 1, 2, 3]
list(f(10))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]