Topics Covered
In this module, you’ll learn:- Functions are Objects
- Higher-Order Functions
- Closures
- Decorators
- Iterators
- Generators
- Context Managers
Try Yourself: 💻 VS Code | 🚀 Colab | 📥 Download
Verify Solutions: 💻 VS Code | 🚀 Colab | 📥 Download
Functions are Objects
Like integers, strings, lists, and dictionaries, functions are also objects. Therefore, a function can:- Be assigned to a variable.
- Be passed as an argument.
- Be returned from another function.
- Be stored in a collection.
Assigning a Function
Show Output
Show Output
Storing Functions
Show Output
Show Output
Exercise 2
What is the difference between the following statements?Solution
Solution
f = greetassigns the function object.f = greet()calls the function and stores its return value.
Higher-Order Functions
A higher-order function is a function that:- Accepts one or more functions as arguments.
- Returns a function.
Passing Functions as Arguments
Show Output
Show Output
Returning Functions
Show Output
Show Output
Example
Show Output
Show Output
Exercise 2
Predict the output.Show Output
Show Output
Solution
Solution
Exercise 3
When is a function called a higher-order function?Solution
Solution
- Accepts one or more functions as arguments.
- Returns a function.
Higher-order functions form the foundation for closures and decorators.
Closures
Sometimes we want a function to remember information from previous function calls. A normal function cannot do this because its local variables are destroyed when the function finishes executing.Example: Normal Function
Show Output
Show Output
counter() is called, the local variable count is created again and initialized to 0. Therefore, the function cannot remember its previous state.
To preserve the state between function calls, we can use a closure.
What is a Closure?
A closure is an inner function that remembers the variables of its enclosing function even after the enclosing function has finished executing. A closure is created when:- A function is defined inside another function.
- The inner function uses variables from the outer function.
- The inner function is returned.
Example
Show Output
Show Output
count is preserved even after counter() has finished executing. Each call to increment() updates the same variable instead of creating a new one.
The nonlocal keyword allows the inner function to modify a variable defined in the enclosing function.
Exercise 2
Predict the output.Show Output
Show Output
Solution
Solution
count is preserved inside the closure and updated on each function call.Exercise 3
Why do we use closures?Solution
Solution
Closures are widely used for state preservation and form the foundation of decorators, where the wrapper function remembers the original function passed to the decorator.
Decorators
A decorator is a function that extends or modifies the behavior of another function without changing its original code. A decorator is a higher-order function because it:- Accepts a function as an argument.
- Returns another function.
Creating a Decorator
Show Output
Show Output
greet(), the decorator returns a new function with additional behavior.
Using the @ Syntax
Python provides the @ syntax as a convenient way to apply decorators.
Show Output
Show Output
Exercise 2
Which statement is equivalent to the following code?Solution
Solution
@ syntax is a shorthand for applying a decorator.Decorating Functions with Parameters
The previous decorator works only for functions that do not accept any arguments.Show Output
Show Output
Show Output
Show Output
wrapper() does not accept any arguments, Python raises an error.
*argscollects all positional arguments.**kwargscollects all keyword arguments.func(*args, **kwargs)forwards all arguments to the original function.
Show Output
Show Output
Thewrapper()function is defined inside another function and remembers the originalfunceven after the outer function has finished executing. This behavior was possible because of closure.
An iterator is an object that returns one value at a time from a collection, while a generator is a special type of iterator created using the
yield keyword. They provide a memory-efficient way to process data without loading everything into memory at once.
Learning Objectives
After completing this lesson, you will be able to:- Understand iterables, iterators, and generators.
- Create iterators using
iter()andnext(). - Build custom iterators.
- Create generators using
yield. - Differentiate between
yieldandreturn. - Create generator expressions.
- Compare iterators and generators.
- Identify real-world use cases of generators.
What is an Iterator?
An iterator is an object that returns one element at a time from a collection. It remembers its current position and produces the next value only when requested. Python uses iterators internally whenever you iterate over a collection using afor loop.
Iterator Protocol
An iterator implements the following special methods:__iter__()– Returns the iterator object.__next__()– Returns the next element.
__next__() raises a StopIteration exception.
Creating an Iterator
Use theiter() function to create an iterator from an iterable.
Show Output
Show Output
Retrieving Values
Use thenext() function to retrieve values from an iterator.
Show Output
Show Output
StopIteration
Once all elements are consumed, callingnext() again raises a StopIteration exception.
Show Output
Show Output
Exercise 2
Predict the output.Show Output
Show Output
Solution
Solution
iter().Exercise 3
What exception will be raised by the following code?Show Output
Show Output
Solution
Solution
StopIteration exception is raised because the iterator has no more elements to return.Creating a Custom Iterator
You can create your own iterator by implementing the__iter__() and __next__() methods.
__iter__()returns the iterator object.__next__()returns the next value.- When all values are consumed,
__next__()raises aStopIterationexception.
Example
Show Output
Show Output
How It Works
- The
Counterobject is created. - The
forloop calls__iter__()to obtain the iterator. - The loop repeatedly calls
__next__(). - Each call returns the next value.
- When the limit is reached,
StopIterationis raised, ending the loop.
Exercise 2
What happens ifraise StopIteration is removed from the __next__() method?
Solution
Solution
Exercise 3
Which two special methods must every custom iterator implement?Solution
Solution
__iter__()__next__()
What is a Generator?
A generator is a special type of iterator created using a function that contains theyield keyword. Unlike a normal function that returns all values at once, a generator produces one value at a time and automatically remembers its execution state.
Generators are easier to write than custom iterators because Python automatically implements the iterator protocol for you.
Creating a Generator
A function becomes a generator as soon as it contains ayield statement.
Show Output
Show Output
Using next() with a Generator
The next() function starts the generator and retrieves one value at a time.
Show Output
Show Output
"Ending" message is not printed because the generator pauses after the third yield. It executes the remaining statements only when resumed again.
Using a Generator with a for Loop
Generators can be directly used in a for loop.
Show Output
Show Output
for loop automatically calls next() until the generator raises StopIteration.
Exercise 2
Predict the output.Show Output
Show Output
Solution
Solution
"Hello" message is printed only when the generator starts executing (for example, by calling next(g) or iterating over it).Exercise 3
What is the output?Show Output
Show Output
Solution
Solution
for loop automatically retrieves values from the generator until it is exhausted.Understanding yield
The yield keyword is used to produce a value from a generator. Unlike return, which terminates a function, yield pauses the function and preserves its current state. The next time the generator is resumed, execution continues from the statement immediately after the previous yield.
yield vs return
Execution Flow
Show Output
Show Output
yield.
State Preservation
One of the biggest advantages of generators is that they automatically preserve the values of local variables.Show Output
Show Output
count is not reinitialized each time. Its value is preserved between successive calls to next().
Multiple yield Statements
A generator can contain multiple yield statements.
Show Output
Show Output
yield produces one value before the generator pauses.
Exercise 2
Predict the output.Solution
Solution
Show Output
Show Output
x is preserved between the two yield statements.Exercise 3
What is the main difference betweenreturn and yield?
Solution
Solution
returnterminates the function and returns a value.yieldpauses the function, returns a value, preserves its state, and resumes execution when requested again.
Generator Expressions
A generator expression provides a concise way to create generators. It is similar to a list comprehension but uses parentheses() instead of square brackets [].
Generator expressions generate values only when required, making them memory efficient.
Syntax
Show Output
Show Output
Example
Show Output
Show Output
Generator Expression vs List Comprehension
- A list comprehension stores all values in memory.
- A generator expression generates values one at a time.
Exercise 2
Which symbol is used to create a generator expression?Solution
Solution
(), whereas list comprehensions use square brackets [].Infinite Generators
Generators can produce infinite sequences because values are generated only when requested.Example
Show Output
Show Output
Fibonacci Generator
Generators are commonly used to generate mathematical sequences.Example
Show Output
Show Output
Memory Efficiency
One of the biggest advantages of generators is memory efficiency.List Example
Generator Example
When to Use Generators
Use generators when:- Working with large datasets.
- Reading large files.
- Processing streaming data.
- Producing values on demand.
- Creating infinite sequences.
Iterator vs Generator
Remember: Every generator is an iterator, but not every iterator is a generator.
Iterable vs Iterator vs Generator
- Iterable → An object that can produce an iterator.
- Iterator → Produces one value at a time.
- Generator → A special iterator created using the
yieldkeyword.
Real-World Applications
Generators are commonly used for:- Reading large files line by line.
- Processing large datasets.
- Streaming data from APIs.
- Log processing.
- Data pipelines.
- Machine learning workflows.
- Infinite sequences.
Example: Reading a File
Show Output
Show Output
Key Takeaways
- An iterable is an object that can produce an iterator.
- An iterator returns one value at a time using
next(). - A generator is a simpler way to create an iterator using
yield. - The
yieldkeyword pauses execution and preserves the function’s state. - Generator expressions provide a concise syntax for creating generators.
- Generators are ideal for processing large datasets because they use lazy evaluation.
- Every generator is an iterator, but not every iterator is a generator.
Check Your Understanding
Question 1 What is the purpose of theiter() function?
Solution
Solution
iter() function converts an iterable into an iterator.Solution
Solution
__iter__() and __next__()yield keyword?
Solution
Solution
yield keyword pauses a generator, returns a value, preserves its state, and resumes execution from the same point when requested again.yield and return?
Solution
Solution
returnterminates the function.yieldpauses the function and allows it to continue later.
Solution
Solution
().Solution
Solution
for loop?
Solution
Solution
for loop.Solution
Solution
Solution
Solution
- Reading large files
- Processing large datasets
- Streaming API data
- Log processing
- Infinite sequences
Context Managers
Whenever we open external resources such as files or database connections, they should be closed properly. Python provides the with statement to handle this automatically.Without a Context Manager
Show Output
Show Output
close(), the file may remain open.
Using a Context Manager
Show Output
Show Output
with block.
Custom Context Manager
A context manager implements two special methods:__enter__()__exit__()
Show Output
Show Output
Exercise 1
Open a file using thewith statement and display its contents.
Sample Input
Solution
Solution
Exercise 2
Create a context manager that prints"Start" on entry and "End" on exit.
Sample Input
Solution
Solution