# Topic covered
* Generators
* Iterable and Iterator in Python
* Create an Iterator Class
11.1 Generators
Generator is a function which is responsible to generate a sequence of values.
We can write generator functions just like ordinary functions, but it uses yield keyword to return values.
def simpleGeneratorFun():
yield 1
yield 2
yield 3
for value in simpleGeneratorFun():
print(value, end=' ')
# 1 2 3
x = simpleGeneratorFun()
print(list(x))
# [1, 2, 3]
Advantages of Generator Functions
- When compared with class level iterators, generators are very easy to use
- Improves
memory utilizationandperformance. - Generators are the best suitable for reading data from large number of large files
- Generators work great for web scraping and crawling.
* Saves memory
* Don't want the result immediately
* Lazy evaluation
[…] → list comprehension (eager, stores everything in memory).
We will get MemoryError in this case because all these values are required to store in the memory.
# Normal Collection --> []
l=[x*x for x in range(10000000000000000)]
print(l[0])
(…) → generator expression (lazy, memory efficient).
We won't get any MemoryError because the values won’t be stored at the beginning
# Generators --> ()
g=(x*x for x in range(10000000000000000))
print(next(g))
# 0
It doesn’t compute all the values at once (which would be impossible here, because 10^16 is huge).
Instead, it computes values lazily, one at a time, when you ask for them.
👉 Memory efficient — it doesn’t store billions of numbers.
Infinite Generators
An infinite generator is a generator that never stops producing values — it doesn’t have a natural end, unlike a finite list or range.
👉 You control how much you consume (using next() or loops with break)
def infinite_chai():
count = 1
while True:
yield f"Refil #{count}"
count += 1
refill = infinite_chai()
user2 = infinite_chai()
for _ in range(5):
print(next(refill))
for _ in range(6):
print(next(user2))
g = infinite_chai()
print(next(g))
print(next(g))
print(next(g))
Send data to Generators
.send(value) passes a value back into the generator at the point where it was paused.
def chai_customer():
print("Welcome ! What chai would you like ?")
order = yield
# the generator will paused at order = yield, waiting for input.
while True:
print(f"Preparing: {order}")
order = yield
# Pauses again at the next yield - it will always sy=tuck in this loop
stall = chai_customer()
next(stall) # start the generator
stall.send("Masala Chai")
stall.send("Lemon Chai")
# Output
# Welcome ! What chai would you like ?
# Preparing: Masala Chai
# Preparing: Lemon Chai
✅ So, this code models a chai stall that keeps taking and preparing orders one by one, without restarting the function every time
Close generators
def local_chai():
yield "Masala Chai"
yield "Ginger Chai"
def imported_chai():
yield "Matcha"
yield "Oolong"
def full_menu():
yield from local_chai()
yield from imported_chai()
for chai in full_menu():
print(chai)
# Masala Chai
# Ginger Chai
# Matcha
# Oolong
def chai_stall():
try:
while True:
order = yield "Waiting for chai order"
except:
print("Stall closed, No more chai")
stall = chai_stall()
print(next(stall))
stall.close() #cleanup
11.2 Iterable and Iterator in Python
- Iteration means to access each item of something one after another generally
using a loop - list, tuples, dictionaries, etc. –> all are iterables
- One important property it has
- an
__iter__()method oriter()method - which allows any iterable to return an iterator object.
- an
iter()andnext()
# list of cars
cars = ['Audi', 'BMW', 'Jaguar', 'Kia', 'MG', 'Skoda']
# get iterator object using the iter() / __iter__() method
cars_iter = iter(cars)
cars_iter = cars.__iter__()
# use the next / __next__() method to iterate through the list
print(next(cars_iter))
# OP: Audi
print(cars_iter.__next__())
# OP: BMW
Create an Iterator Class
- To create an object/class as an iterator you have to implement the methods
__iter__() and __next__()to your object. - To prevent the iteration to go on forever, we can use the
StopIterationstatement.
class MyNumbers:
def __iter__(self):
self.a = 1
return self
def __next__(self):
if self.a <= 3:
x = self.a
self.a += 1
return x
else:
raise StopIteration
my_class = MyNumbers()
my_iter = iter(my_class)
print(next(my_iter))
print(next(my_iter))
print(next(my_iter))
print(next(my_iter))
print(next(my_iter))