Decorators & Generators in Python
Decorators:
A decorator in Python is a function that modifies or enhances another function without changing
its original source code. Decorators allow programmers to add extra functionality such as
logging, authentication, timing, and validation in a clean and reusable way.
Characteristics of Decorators
• To add functionality before or after a function executes.
• To avoid code duplication.
• To improve modularity and readability.
• To separate business logic from auxiliary tasks.
Functions as First-Class Objects
In Python, functions are first-class objects. This means:
• Functions can be assigned to variables.
• Functions can be passed as arguments.
• Functions can be returned from other functions.
This property makes decorators possible.
Basic Decorator
# without @ syntax:
def my_decorator(func):
def wrapper():
print("Before function execution")
func()
print("After function execution")
return wrapper
def say_hello():
print("Hello!")
decorated = my_decorator(say_hello)
decorated()
Using @ Decorator Syntax
@my_decorator
def say_hello():
print("Hello!")
say_hello()
The @ syntax is syntactic sugar. It is equivalent to:
say_hello = my_decorator(say_hello)
#Decorators with Arguments
def decorator_name(func):
def wrapper(*args, **kwargs):
print("Before execution")
result = func(*args, **kwargs)
print("After execution")
return result
return wrapper
@decorator_name
def add(a, b):
return a + b
print(add(5, 3))
#Chaining Multiple Decorators
Multiple decorators can be applied to a single function. They are executed from bottom to top.
Built-in Decorators
Common built-in decorators include:
• @staticmethod
• @classmethod
• @property
Real-World Applications
• Logging function calls
• Measuring execution time
• Authentication and authorization
• Caching results
• Input validation
## Decorators are a powerful feature in Python that allow modification of function behavior
without altering original code. They promote clean, reusable, and maintainable
programming practices.
Generators:
A generator in Python is a special type of function that produces values one at a time instead of
returning all values at once.
Generators use the keyword 'yield' instead of 'return'. They are useful for working with
large datasets because they generate values only when required, saving memory.
A generator function looks like a normal function but contains the keyword 'yield'. When
the function is called, it returns a generator object without executing the function
completely.
Example:
def count_up_to(n):
i=1
while i <= n:
yield i
i += 1
gen = count_up_to(5)
for num in gen:
print(num)
#Generator vs Normal Function
Normal Function:
Returns all values at once and stores them in memory.
Generator Function:
Produces values one at a time and pauses after each yield, making it memory efficient.
#Using next() with Generators
The next() function is used to manually retrieve the next value from a generator.
Example (next):
def simple_gen():
yield 10
yield 20
yield 30
g = simple_gen()
print(next(g))
print(next(g))
print(next(g))
Advantages of Generators
• Memory efficient
• Faster for large datasets
• Produce values only when required (lazy evaluation)
• Useful in data streaming and large data processing
Key Points
• Generators use the 'yield' keyword.
• They return an iterator object.
• Execution pauses and resumes automatically.
• They improve performance when handling large data.