包阅导读总结
关键词:Python 装饰器、函数、类、参数、行为扩展
总结:本文主要介绍了 Python 装饰器,包括其基础概念(如一等函数、闭包),如何创建和应用简单装饰器,处理带参数的函数,使用类作为装饰器,以及其最佳实践和实际应用。强调装饰器能在不修改原函数代码的情况下扩展函数行为。
主要内容:
– 基础概念:
– 一等函数:可作为参数传递、从其他函数返回、赋值给变量。
– 闭包:内部函数能记住外部函数创建时的环境。
– Python 装饰器:
– 定义:接受函数作为参数,增加功能并返回新函数。
– 应用:展示如何将装饰器应用于简单函数。
– `@`语法:更简洁的应用方式。
– 处理带参数的函数:通过`args`和`kwargs`实现。
– 使用类作为装饰器:
– 步骤:定义类、实现`__init__`方法和`__call__`方法。
– 应用:使用`@`语法应用类装饰器。
思维导图:
文章地址:https://www.freecodecamp.org/news/decorators-in-python-tutorial/
文章来源:freecodecamp.org
作者:Samyak Jain
发布时间:2024/7/22 22:48
语言:英文
总字数:2306字
预计阅读时间:10分钟
评分:90分
标签:Python,装饰器,编程,一等函数,闭包
以下为原文内容
本内容来源于用户推荐转载,旨在分享知识与观点,如有侵权请联系删除 联系邮箱 media@ilingban.com
In this tutorial, you will learn about Python decorators: what they are, how they work, and when to use them.
Table of Contents
- Foundation for Decorators
- Introduction to Python Decorators
- Creating Simple Decorators
– Applying Decorators to Functions
– Using the @ Syntax for Decorators - How to Handle Functions with Arguments
- Using Classes as Decorators
- Best Practices for Using Decorators
- Real-World Applications of Decorators
- Conclusion
Decorators are a powerful and elegant way to extend the behavior of functions or methods without modifying their actual code. But before diving into decorators, it’s helpful to understand two foundational concepts in Python: first-class functions and closures.
Foundation for Decorators
First-Class Functions in Python
First-class functions mean that functions in Python are treated like any other object. This implies that functions can be:
- Passed as arguments to other functions.
- Returned from other functions.
- Assigned to variables.
Understanding Closures
Closures in Python allow a function to remember the environment in which it was created. This means the inner function has access to the variables in the local scope of the outer function even after the outer function has finished executing.
Let’s look at an example to understand closures:
def outer_func(): greet = "Hello!" def inner_func(): print(greet) return inner_funcnew_function = outer_func()new_function() # Outputs: Hello!new_function() # Outputs: Hello!
In this example:
- We have
outer_func
that doesn’t take any parameters but has a local variablegreet
. - An
inner_func
is defined withinouter_func
that printsgreet
. - When we call
outer_func
, it returnsinner_func
but does not execute it immediately. We assign the returned function tonew_function
. Now,new_function
can be called later, and it will remember thegreet
variable fromouter_func
’s scope, printing “Hello!” each time it’s called.
This is what a closure is—it remembers our greet
variable even after the outer function has finished executing.
Modifying Closures with Parameters
Let’s enhance our closure by passing a parameter to the outer_func
instead of using a local variable:
def outer_func(greet): def inner_func(): print(greet) return inner_funcnamaste_func = outer_func("Namaste!")howdy_func = outer_func("Howdy!")namaste_func() # Outputs: Namaste!howdy_func() # Outputs: Howdy!
Here:
outer_func
now takes a parametergreet
.- The
inner_func
prints thisgreet
. - When we call
outer_func
with “Namaste!” and “Howdy!”, it returns functions that remember these specific messages.
So, this was a quick brief about first-class functions and closures. If you want to learn more about them you can read this comprehensive blog here.
Introduction to Python Decorators
A decorator is a function that takes another function as an argument, adds some functionality, and returns a new function. This allows you to “wrap” another function to extend its behavior (adding some functionality before or after) without modifying the original function’s source code.
So, this is the closure example that we used above:
def outer_func(greet): def inner_func(): print(greet) return inner_func
Now, let’s look at a Decorator Example:
def decorator_function(func): def wrapper_function(): return func() return wrapper_function
Here, instead of a value (like greet
), we’re accepting a function (func
) as an argument. Within our wrapper_function
, instead of just printing out a message, we’re going to execute this func
and then return that.
Applying Decorators to Functions
Here’s how we can apply our decorator to a simple function:
def decorator_function(func): def wrapper_function(): return func() return wrapper_functiondef display(): print('The display function was called')decorated_display = decorator_function(display)decorated_display() # Outputs: The display function was called
In this example:
- We define a simple function
display
that prints a message. - We apply the
decorator_function
todisplay
, creating a new variabledecorated_display
. - When we call
decorated_display()
, it runs thewrapper_function
inside our decorator, which in turn calls and returns thedisplay
function.
Using the @ Syntax for Decorators
Python provides a more readable way to apply decorators using the @
symbol. This syntax is easier to understand and is commonly used in Python code:
def decorator_function(func): def wrapper_function(): print(f'Wrapper executed before {func.__name__}') return func() return wrapper_function@decorator_functiondef display(): print('The display function was called')display() # Outputs: Wrapper executed before display # The display function was called
Here:
- We use
@decorator_function
decorator above thedisplay
function definition which is equivalent todisplay = decoratorFunction(display)
. - Now, when we call
display()
, it automatically goes through the decorator, printing the additional message first.
How to Handle Functions with Arguments
The decorator we’ve written so far won’t work if our original function takes arguments. For example, consider the following function:
def display_info(name, age): print('display_info was called with ({}, {})'.format(name, age))display_info('Kalam', 83) # Outputs: display_info was called with (Kalam, 83)
If we try to apply our current decorator to display_info
, it will raise an error because the wrapperFunction
takes no arguments but the original function expects two.
Modifying the Decorator to Handle Arguments
We can modify our decorator to accept any number of positional and keyword arguments by using *args
and **kwargs
.
import functoolsdef decoratorFunction(func): @functools.wraps(func) def wrapperFunction(*args, **kwargs): print('Wrapper executed before {}'.format(func.__name__)) return func(*args, **kwargs) return wrapperFunction@decoratorFunctiondef display(): print('The display function was called')@decoratorFunctiondef display_info(name, age): print('display_info was called with ({}, {})'.format(name, age))display_info('Kalam', 83) display()
In this updated decorator:
wrapperFunction
now accepts any number of positional (*args
) and keyword arguments (**kwargs
).- These arguments are passed to
func
when it is called insidewrapperFunction
.
The output of this will be:
Wrapper executed before display_infodisplay_info was called with (Kalam, 83)Wrapper executed before displayThe display function was called
This setup makes our decorator flexible enough to handle any function, regardless of its parameters.
BTW, Notice how we added new functionality to two different functions (display()
and display_info()
) without altering them? This is one of the main benefits of decorators: they allow us to extend the behavior of several functions in a DRY (Don’t Repeat Yourself) way, as demonstrated in this example.
How to Use Classes as Decorators
While function-based decorators are common, you can also use classes to create decorators. Using classes as decorators can offer more flexibility and readability, especially for complex decorators.
To help you understand them better, We’ll turn a function-based decorator into a class-based decorator:
Original Function-Based Decorator
Let’s start with a simple function-based decorator:
def decoratorFunction(func): def wrapperFunction(*args, **kwargs): print('Wrapper executed before calling {}'.format(func.__name__)) return func(*args, **kwargs) return wrapperFunction
Creating a Class-Based Decorator
To turn this function-based decorator into a class-based decorator, follow these steps:
Step 1: Define the Class
First, we define a new class called DecoratorClass
. This class will handle the decoration process.
class DecoratorClass: pass
Step 2: Implement the __init__
Method
The __init__
is a special method that initializes the object when an instance of the class is created.
Next, we pass the function to be decorated (func
) as an argument to the __init__
method and store it in an instance variable self.func
.
class DecoratorClass: def __init__(self, func): self.func = func
Step 3: Implement the __call__
Method
The __call__
method is a special method that allows an instance of the class to be called as a function. This method is essential because it handles the actual decoration logic. In this case:
- The
__call__
method takes*args
and**kwargs
to handle any number of positional and keyword arguments. - Inside
__call__
, we print a message and then call the original function with its arguments.
class DecoratorClass: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print('Executing wrapper before {}'.format(self.func.__name__)) return self.func(*args, **kwargs)
Using the Class-Based Decorator
We can now use the @
syntax to apply the class-based decorator to functions, just as we did with the function-based decorator.
@DecoratorClassdef display(): print('display function executed')@DecoratorClassdef display_info(name, age): print('display_info function executed with arguments ({}, {})'.format(name, age))
Running the Decorated Functions
When we call the decorated functions, the __call__
method of DecoratorClass
is executed:
display_info('Kalam', 83)display()
Complete Example
Here is the complete example with the class-based decorators:
class DecoratorClass: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print('Executing wrapper before {}'.format(self.func.__name__)) return self.func(*args, **kwargs)@DecoratorClassdef display(): print('display function executed')@DecoratorClassdef display_info(name, age): print('display_info function executed with arguments ({}, {})'.format(name, age))display_info('Kalam', 83)display()
In this class-based decorator:
__init__
Method: This method binds the original function to an instance of the class.__call__
Method: This method allows an instance ofDecoratorClass
to be called as a function. It prints a message and then calls the original function with any provided arguments.- Decorating Functions: We use the
@DecoratorClass
syntax to decorate thedisplay
anddisplay_info
functions. - Execution: When
display_info('Kalam', 83)
is called, the__call__
method ofDecoratorClass
is executed, printing the message and then executingdisplay_info
. Similarly, whendisplay()
is called, it executes the__call__
method, prints the message, and then executesdisplay
.
Both function-based and class-based decorators provide the same functionality. The choice between them depends on personal preference and the complexity of the decorator logic.
Best Practices for Using Decorators
When using decorators in Python, it’s essential to follow best practices to maintain clean, maintainable code that aligns with Pythonic conventions.
When you create a decorator, the original function’s metadata (such as its name, docstring, and module) is often lost. This can lead to confusion and issues with introspection, documentation, and debugging. To preserve this metadata, use the functools.wraps
decorator within your wrapper function.
functools.wraps
was introduced in Python 2.5 as part of the functools
module, which provides higher-order functions and operations on callable objects. The wraps
decorator is specifically designed to update the wrapper function to look more like the wrapped function by copying attributes such as the function name, module, and docstring (yes, you guessed it right – functools.wraps()
itself is a decorator).
Let’s see an Example:
import functoolsdef decoratorFunction(func): @functools.wraps(func) def wrapperFunction(*args, **kwargs): print(f'Wrapper executed before {func.__name__}') return func(*args, **kwargs) return wrapperFunction@decoratorFunctiondef display(): """Display function docstring""" print('The display function was called')print(display.__name__) # Outputs: displayprint(display.__doc__) # Outputs: Display function docstring
In this example, @functools.wraps(func)
is used to ensure that the wrapperFunction
retains the original func
‘s metadata.
2. Keep Decorators Simple and Focused
A decorator should have a single responsibility and should not try to do too many things. If a decorator becomes complex, consider breaking it down into multiple, simpler decorators that can be composed together. Example:
import functoolsdef log_function_call(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'Calling {func.__name__}') return func(*args, **kwargs) return wrapperdef measure_time(func): import time @functools.wraps(func) def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) end = time.time() print(f'{func.__name__} took {end - start} seconds') return result return wrapper@log_function_call@measure_timedef compute_square(n): return n * nprint(compute_square(5))
In this example, log_function_call
and measure_time
are simple, single-responsibility decorators that can be composed to add both logging and timing functionality to compute_square
. This is what Decorators do – providing a clean and readable way to implement common patterns like logging and timing.
3. Use Descriptive Names for Decorators and Wrapped Functions
Choose clear and descriptive names for your decorators and the functions they wrap so they clearly indicates their functionality. This makes the purpose and behavior of the code more apparent.
4. Document Your Decorators
Always document your decorators, explaining their purpose and how they should be used. This is especially important if others will use your decorators or if you are working in a team.
Practical Applications of Decorators
Now you might be wondering, “Okay, decorators are fancy and all, but how and where do we actually use them?” Well, here are some practical applications:
- Logging Function Calls:
Logging is a common requirement for tracking the usage of functions and methods, especially in debugging and monitoring applications. - Timing Functions:
Decorators can measure the time it takes for a function to execute, which is useful for performance analysis.
We have seen both the examples above in the best practices section.
Beyond these common uses, there are other usecases such as:
- Input Validation:
Decorators can be used to validate inputs to functions, ensuring that they meet certain criteria before the function proceeds. - Memoization:
Thememoize
function is a decorator that helps to cache (store) the results of expensive function calls and reuse the cached result when the same inputs occur again. This technique is called Memoization and it is useful to optimize the performance, especially for recursive functions like calculating Fibonacci numbers.
from functools import wrapsdef validate_non_negative(func): @wraps(func) def wrapper(*args, **kwargs): if any(arg < 0 for arg in args): raise ValueError("Arguments must be non-negative") return func(*args, **kwargs) return wrapper@validate_non_negativedef square_root(x): return x ** 0.5print(square_root(4))
import functoolsdef memoize(func): cache = {} @functools.wraps(func) def wrapper(*args): if args in cache: return cache[args] result = func(*args) cache[args] = result return result return wrapper@memoizedef fibonacci(n): if n in {0, 1}: return n return fibonacci(n - 1) + fibonacci(n - 2)fibonacci(10)
Run this function to see the time difference when running Fibonacci function with or without memoization.
- Access Control and Authentication:
In web applications, access control and authentication are crucial for security. Decorators can be used to enforce user permissions, ensuring that only authorized users can access certain functions or endpoints.
from functools import wrapsdef requires_login(func): @wraps(func) def wrapper(*args, **kwargs): if not user_is_logged_in(): raise Exception("User not logged in") return func(*args, **kwargs) return wrapper@requires_logindef view_dashboard(): return "Dashboard content"# user_is_logged_in is a placeholder for the actual authentication check function.
Conclusion
Decorators in Python provide a clean and powerful way to extend the behavior of functions. By understanding first-class functions and closures, you can grasp how decorators work under the hood.
Whether you’re using function-based or class-based decorators, you can enhance your functions without altering their original code, keeping your codebase clean and maintainable.
- Decorators are powerful for extending the functionality of functions.
- They can be implemented using functions or classes.
- The
@decorator
syntax is a cleaner and more readable way to apply decorators. - They help keep your code DRY (Don’t Repeat Yourself) by abstracting common functionality.
Thank you for reading! If you have any comments, criticism, or questions, feel free to tweet or reach out to me at @OGsamyak. Your feedback helps me improve!