python 装饰器使用
介绍 (Introduction)
Decorator is one of the very important features in Python, and you may have seen it many places in Python code, for instance, the functions with annotation like @classmethod, @staticmethod, @property etc. By definition, decorator is a function that extends the functionality of another function without explicitly modifying it. It makes the code shorter and meanwhile improve the readability. In this article, I will be sharing with you how we shall use the Python decorators.
Decorator是Python中非常重要的功能之一,您可能已经在Python代码中看到了很多地方,例如,带有@ classmethod,@ staticmethod,@ property等注释的函数。按定义,decorator是一个扩展的函数无需显式修改其他功能的功能。 它使代码更短,同时提高了可读性。 在本文中,我将与您分享如何使用Python装饰器。
基本语法 (Basic Syntax)
If you have checked my this article about the Python closure, you may still remember that we have discussed about Python allows to pass in a function into another function as argument. For example, if we have the below functions:
如果你已经检查了我的这个文章对Python的关闭,你仍然可能还记得,我们已经讨论了Python允许在一个函数传递到另一个函数作为参数。 例如,如果我们具有以下功能:
add_log — to add log to inspect all the positional and keyword arguments of a function before actually calling it
add_log —添加日志以在实际调用函数之前检查函数的所有位置和关键字参数
send_email — to accept some positional and keyword arguments for sending out emails
send_email —接受一些位置和关键字参数以发送电子邮件
def add_log(func):
def log(*args, **kwargs):
for arg in args:
print(f"{func.__name__} - args: {arg}")
for key, val in kwargs.items():
print(f"{func.__name__} - {key}, {val}")
return func(*args, **kwargs)
return log def send_email(subject, to, **kwargs):
#send email logic
print(f"email sent to {to} with subject {subject}.")
We can pass in the send_email function to add_log as argument, and then we trigger the sending of the email.
我们可以将send_email函数传递给add_log作为参数,然后触发电子邮件的发送。
sender = add_log(send_email)
sender("hello", "contact@codeforests.com", attachment="debug.log", urgent_flag=True)
This code will generate the output as per below:
此代码将生成如下输出:
![Image for post](https://miro.medium.com/max/9999/0*ek3UbjYy86Bboj6C.png)
You can see that the send_email function has been invoked successfully after all the arguments were printed out. This is exactly what decorator is doing — extending the functionality of the send_email function without changing its original structure. When you directly call the send_email again, you can still see it’s original behavior without any change.
您可以看到在打印完所有参数之后,send_email函数已成功调用。 这正是装饰器正在做的-在不更改其原始结构的情况下扩展send_email函数的功能。 再次直接调用send_email时,仍然可以看到它的原始行为,而无需进行任何更改。
![Image for post](https://miro.medium.com/max/9999/0*uiLzL-l-zzXUQLFo.png)
Python装饰器功能 (Python decorator as a function)
Before Python 2.4, the classmethod() and staticmethod() function were used to decorate functions by passing in the decorated function as argument. And later the @ symbol was introduced to make the code more concise and easier to read especially when the functions are very long.
在Python 2.4之前,classmethod()和staticmethod()函数用于通过传递装饰函数作为参数来装饰函数。 后来引入了@符号,以使代码更简洁,更易于阅读,尤其是在函数很长的情况下。
So let implement our own decorator with @ syntax.
因此,让我们使用@语法实现我们自己的装饰器。
Assuming we have the below decorator function and we want to check if user is in the whitelist before allowing he/she to access certain resources. We follow the Python convention to use wrapper as the name of the inner function (although it is free of your choice to use any name).
假设我们具有以下装饰器功能,并且我们希望在允许用户访问某些资源之前检查用户是否在白名单中。 我们遵循Python约定将包装器用作内部函数的名称(尽管可以自由选择使用任何名称)。
class PermissionDenied(Exception):
pass def permission_required(func):
whitelist = ["John", "Jane", "Joe"]
def wrapper(*args, **kwargs):
user = args[0]
if not user in whitelist:
raise PermissionDenied
func(*args, **kwargs)
return wrapper
Next, we decorate our function with permission_required as per below:
接下来,我们按如下所示用Permission_required装饰我们的函数:
@permission_required
def read_file(user, file_path):
with open(file_path, "r") as f:
#print out the first line of the file
print(f.readline())
When we call our function as per normal, we shall expect the decorator function to be executed first to check if user is in the whitelist.
当我们正常调用函数时,我们应该期望装饰器函数首先执行,以检查用户是否在白名单中。
read_file("John", r"C:\pwd.txt")
You can see the below output has been printed out:
您可以看到以下输出已打印出来:
![Image for post](https://miro.medium.com/max/9999/0*wjvUtZuhYQewwUhP.png)
If we pass in some user name not in the whitelist:
如果我们传递的用户名不在白名单中:
read_file("Johnny", r"C:\pwd.txt")
You would see the permission denied exception raised which shows everything works perfect as per we expected.
您会看到引发了拒绝权限异常,该异常表明一切都按我们预期的那样完美进行。
![Image for post](https://miro.medium.com/max/9999/0*z8pl5GklQFTTjuK8.png)
But if you are careful enough, you may find something strange when you check the below.
但是,如果足够小心,请检查以下内容,可能会发现一些奇怪的地方。
![Image for post](https://miro.medium.com/max/9999/0*cJcYOiJoV93ldmNX.png)
So it seems there is some flaw with this implementation although the functional requirement has been met. The function signature has been overwritten by the decorator, and this may cause some confusing to other people when they want to use your function.
因此,尽管已满足功能要求,但此实现似乎仍存在一些缺陷。 装饰器已经覆盖了函数签名,这在其他人想要使用您的函数时可能会引起混淆。
functools.wraps的使用 (Use of the functools.wraps)
To solve this problem, we will need to introduce one more Python module functools, where we can use the wraps method to update back the metadata info for the original function.
为了解决这个问题,我们将需要再引入一个Python模块functools ,在这里我们可以使用wraps方法来更新原始函数的元数据信息。
Let update our decorator function again by adding @wraps(func) to the wrapper function:
通过在包装函数中添加@wraps(func)来再次更新装饰器函数:
from functools import wraps
def permission_required(func):
... @wraps(func)
def wrapper(*args, **kwargs):
...
return wrapper
Finally, when we check the function signature and name again, it shows the correct information now.
最后,当我们再次检查函数签名和名称时,它现在显示正确的信息。
![Image for post](https://miro.medium.com/max/9999/0*fB2WSk_Ai3d3OIsb.png)
So what happened was that, the @wraps(func) would invoke a update_wrapper function which updates the metadata of the original function automatically so that you will not see the wrapper’s metadata. You may want to check the update_wrapper function in the functools module to further understand how the metadata is updated.
因此发生了什么,@ wraps(func)将调用update_wrapper函数,该函数会自动更新原始函数的元数据,这样您就不会看到包装程序的元数据。 您可能需要检查functools模块中的update_wrapper函数,以进一步了解元数据的更新方式。
Beside decorating normal function, the decorator function can be also used to decorate the class function, for instance, the @staticmethod and @property are commonly seen in Python code to decorate the class functions.
除了装饰普通函数外,decorator函数还可用于装饰类函数,例如,@ staticmethod和@property在Python代码中常见于装饰类函数。
Python装饰器作为一个类 (Python decorator as a class)
Decorator function can be also implemented as a class in case you find your wrapper function has grown too big or has nested too deeply. To make this happen, you will need to implement a __call__ function so that the class instance become callable with the decorated function as argument.
如果发现包装函数太大或嵌套太深,则装饰器函数也可以实现为类。 为了实现这一点,您将需要实现__call__函数,以便使用修饰的函数作为参数可调用该类实例。
Below is the code that implements our earlier example as a class:
以下是将我们之前的示例实现为类的代码:
from functools import update_wrapper class PermissionRequired:
def __init__(self, func):
self._whitelist = ["John", "Jane", "Joe"]
update_wrapper(self, func)
self._func = func def __call__(self, *args, **kwargs):
user = args[0]
if not user in self._whitelist:
raise PermissionDenied
return self._func(*args, **kwargs)
Take note that we will need to call the update_wrapper function to manually update the metadata for our decorated function. And same as before, we can continue using @ with class name to decorate our function.
请注意,我们将需要调用update_wrapper函数来手动更新装饰函数的元数据。 和以前一样,我们可以继续使用@和类名来修饰函数。
@PermissionRequired def read_file(user, file_path):
with open(file_path, "r") as f:
#print out the first line of the file
print(f.readline())
结论 (Conclusion)
In this article, we have reviewed through the reasons of Python decorators being introduced with the basic syntax of implementing our own decorators. And we also discussed about the decorator as function and class with some examples. Hopefully this article would help you to enhance your understanding about Python decorator and guide you on how to use it in your project.
在本文中,我们回顾了通过使用实现我们自己的装饰器的基本语法引入Python装饰器的原因。 我们还通过一些示例讨论了装饰器作为函数和类的问题。 希望本文能帮助您增进对Python装饰器的理解,并指导您如何在项目中使用它。
Originally published at https://www.codeforests.com on August 7, 2020.
最初于 2020年8月7日 发布在 https://www.codeforests.com 。
翻译自: https://medium.com/@codeforests/why-you-should-use-python-decorator-e4d203a94c15
python 装饰器使用