The code you’ve shared demonstrates closures in Python, but not yet decorators. Let me explain how closures work in your example, and then show how to transform this into using decorators.
14.2 What’s Happening
Your example uses a closure - where logger is a function that returns another function (log_func). This inner function “closes over” and remembers the func variable from its parent scope.
When you call:
add_logger = logger(add)
You’re creating a new function (log_func) that:
Has access to the add function (stored in the func parameter)
Logs information before executing the original function
Prints the result of the function call
14.3 Converting to a Decorator
Decorators are a syntactic way to apply this closure pattern. Here’s how to convert your example to use decorators:
import logginglogging.basicConfig(filename='example.log', level=logging.INFO)def logger(func):def log_func(*args): logging.info('Running "{}" with arguments {}'.format(func.__name__, args))print(func(*args))return log_func# Apply decorator to functions@loggerdef add(x, y):return x+y@loggerdef sub(x, y):return x-y# Now you can call the functions directlyadd(3, 3)add(4, 5)sub(10, 5)sub(20, 10)
6
9
5
10
The @logger syntax is shorthand for add = logger(add). It automatically wraps your function with the logger functionality.
14.4 How Decorators Work
When you use the @decorator syntax:
Python defines the function (e.g., add)
It then passes this function to the decorator (logger)
The decorator returns a new function (the inner log_func)
This new function replaces the original function
14.5 Adding Parameters to Decorators
You can also create decorators that accept parameters. This involves adding another layer of functions:
One issue with decorators is that they replace the original function, including its name and docstring. To fix this, use functools.wraps:
import loggingimport functoolslogging.basicConfig(filename='example.log', level=logging.INFO)def logger(func):@functools.wraps(func) # Preserves function metadatadef log_func(*args): logging.info('Running "{}" with arguments {}'.format(func.__name__, args)) result = func(*args)print(result)return resultreturn log_func@loggerdef add(x, y):"""Add two numbers and return the result."""return x+y# Now add.__name__ will be 'add' instead of 'log_func'# and add.__doc__ will still be availableprint(add.__name__) # Output: addprint(add.__doc__) # Output: Add two numbers and return the result.
add
Add two numbers and return the result.
14.7 Closure vs. Decorator: Comparison
Aspect
Closure Approach
Decorator Approach
Syntax
add_logger = logger(add)
@logger above function
Function usage
Call wrapped function: add_logger(3, 3)
Call original function name: add(3, 3)
Code clarity
Separate wrapping from function definition
Wrapping happens at function definition
Metadata preservation
Original function name/docs lost
Can preserve with functools.wraps
Reusability
Requires explicit wrapping of each function
Can apply to multiple functions with same syntax
Nested application
More verbose with multiple wrappers
Clean syntax for multiple decorators: @dec1 @dec2
Would you like me to explain any specific aspect of decorators in more detail?