In Python, two advanced yet essential concepts are Iterators and Decorators. Mastering these topics will allow you to write cleaner, efficient, and more powerful code.
This tutorial will cover:
- What are Iterators?
- Implementing custom Iterators.
- Iterator protocols (
iter()
andnext()
). - What are Decorators?
- Writing and applying custom Decorators.
- Practical examples and explanations.
Let’s dive into each in detail!
Iterators in Python
What is an Iterator?
An iterator is an object in Python that allows a programmer to traverse through all elements of a collection (e.g., lists, tuples, dictionaries, or strings). An iterator maintains a state to remember its current position during iteration.
Iterable vs Iterator
- Iterable: Any object you can loop over (e.g., lists, tuples).
- Iterator: An object that provides a
__next__()
method to retrieve the next item and a__iter__()
method.
Iterator Protocol
An object must implement two methods to become an iterator:
__iter__()
: returns the iterator object itself.__next__()
: returns the next element.
Example of a Custom Iterator:
Let’s create an iterator class that generates a sequence of numbers:
class MyIterator:
def __init__(self, max_value):
self.max_value = max_value
self.current = 0
def __iter__(self):
# Returns the iterator object itself
return self
def __next__(self):
# Defines how to get the next element
if self.current < self.max_value:
self.current += 1
return self.current
else:
# Stops iteration
raise StopIteration
Using the Iterator:
numbers = MyIterator(5)
for num in numbers:
print(num)
Explanation of the Example:
- The class
MyIterator
implements both__iter__()
and__next__()
. - Each call to
__next__()
incrementsself.current
and returns it until reachingmax_value
. - Once reached, the iterator raises a
StopIteration
exception automatically handled by Python’sfor
loop.
Output:
1
2
3
4
5
Decorators in Python
What is a Decorator?
A decorator in Python is a design pattern that allows you to add extra functionality to an existing function without modifying its original structure. Decorators are very powerful, enabling cleaner and more readable code.
Why Use Decorators?
- Code reusability
- Cleaner syntax and code maintenance
- Cross-cutting concerns (logging, timing, access control, etc.)
Basic Decorator Syntax in Code:
def my_decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
say_hello()
Explanation of Basic Decorator:
my_decorator
wraps the originalsay_hello
function.- The decorator adds extra statements before and after the function call.
@my_decorator
syntax simplifies the application of the decorator.
Output:
Before function call
Hello!
After function call
Detailed Example: Practical Decorator (Timing and Logging)
Here’s a more advanced example: a decorator that measures the execution time of a function.
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function '{func.__name__}' executed in {end_time - start_time:.5f} seconds")
return result
return wrapper
@timer_decorator
def calculate_squares(n):
return [i ** 2 for i in range(n)]
calculate_squares(10000)
Explanation of Timer Decorator:
- The decorator
timer_decorator
records the time before and after the function call. *args
and**kwargs
allow the decorated function to accept arbitrary arguments.- The wrapper prints out how long the function took to execute.
Output (Example):
Function ‘calculate_squares’ executed in 0.00201 seconds
Chaining Multiple Decorators
You can apply multiple decorators to a single function. The decorators execute from the bottom up (nearest to the function first).
def uppercase_decorator(func):
def wrapper():
return func().upper()
return wrapper
def bold_decorator(func):
def wrapper():
return f"<b>{func()}</b>"
return wrapper
@bold_decorator
@uppercase_decorator
def greet():
return "hello decorators!"
print(greet())
Explanation of Decorator Chaining
greet()
is first modified byuppercase_decorator
, turning the returned text uppercase.- Then
bold_decorator
wraps the uppercase text with<b>
tags. - Order of decorators matters significantly.
Output:
HELLO DECORATORS!
Iterators vs Decorators: When to Use Which?
- Use iterators when you need custom traversal logic, lazy evaluation, or memory-efficient looping (for instance, when handling large datasets).
- Use decorators when you need reusable additional behaviors like logging, timing, authentication, or modifying function outputs without altering the original code structure.