📜  Python装饰器:完整指南

📅  最后修改于: 2022-05-13 01:54:35.224000             🧑  作者: Mango

Python装饰器:完整指南

装饰器是Python中的一种设计模式工具,用于围绕函数或类(定义的块)包装代码。这种设计模式允许程序员在不修改现有结构的情况下向现有函数或类添加新功能。本节概述了装饰器是什么,如何装饰函数和类,以及它可以解决什么问题。

装饰器

装饰器

了解装饰器

装饰器是一个函数,它接受另一个函数作为参数,执行一些操作,然后根据执行的操作返回参数。由于函数是Python中的一等对象,因此它们可以作为参数传递给另一个函数。
因此我们可以说装饰器是一个接受并返回一个可调用对象的可调用对象。
下面的代码显示了一个简单的装饰器,它为 my_function 文档字符串添加了额外的注释:

def decorated_docstring(function):
    function.__doc__ += '\n Hi George, I''m a simple Decorator.'
    return function
  
def my_function(string1):
    """Return the string."""
    return string1
  
def main():
    myFunc = decorated_docstring(my_function)
    myFunc('Hai')
    help(myFunc)
  
if __name__ == "__main__":
    main()

输出:

Help on function my_function in module __main__:

my_function(string1)
    Return the string.
    Hi George, Im a simple Decorator.

装饰器语法

通过上面的解释,你可以理解如何装饰一个函数。但是定义一个函数,将它分配给一个变量,然后将修饰函数重新分配给同一个变量是不可接受的。因此, Python 2.5 引入了一种修饰函数的语法,方法是在修饰符名称前面加上@字符,并直接添加到函数声明的上方。下面的代码显示了如何使用语法:

def reverse_decorator(function):
  
    def reverse_wrapper():
        make_reverse = "".join(reversed(function()))
        return make_reverse
  
    return reverse_wrapper
  
@reverse_decorator
def say_hi():
    return 'Hi George'
  
def main():
    print(say_hi())
  
if __name__ == "__main__":
    main()

输出:

egroeG iH

这里我们使用@字符来装饰函数。

@reverse_decorator
def say_hi():
    return 'Hi George'

上面的代码在语法上等价于下面的代码reverse_decorator(say_hi())

在这种情况下,reverse_decorator函数执行并创建 revserse_wrapper 的引用。让我们看看下面的例子,以便更好地理解:

def reverse_decorator(function):
    print('Inside reverse_decorator function')
  
    def reverse_wrapper():
        print('Inside reverse_wrapper function')
        return 'Return reverse_wrapper function'
  
    return reverse_wrapper
  
  
@reverse_decorator
def say_hi():
    return 'Inside say_hi'

输出:

Inside reverse_decorator function

这里 reverse_decorator 不执行函数reverse_wrapper 而是创建引用并在可调用函数调用函数时返回它。

def reverse_decorator(function):
    print('Inside reverse_decorator function')
    
    def reverse_wrapper():
        print('Inside reverse_wrapper function')
        return 'reverse_wrapper'
  
    return reverse_wrapper
  
  
@reverse_decorator
def say_hi():
    return 'Inside say_hi'
  
      
def main():
    print('Inside main()')
    print(say_hi)
    print(''); 
    print(say_hi()) # main executes the reference
  
main()

输出:

单函数多装饰器

现在您已经清楚如何使用@syntax 来装饰函数了。另一个很酷的东西是我们可以在一个函数上使用多个装饰器。这里要注意的一件重要事情是应用装饰器的顺序很重要,它是从底部到顶部应用的。让我们看看多个装饰器。

def reverse_decorator(function):
  
    def reverse_wrapper():
        make_reverse = "".join(reversed(function()))
        return make_reverse
  
    return reverse_wrapper
  
def uppercase_decorator(function):
  
    def uppercase_wrapper():
        var_uppercase = function().upper()
        return var_uppercase
  
    return uppercase_wrapper
  
@uppercase_decorator
@reverse_decorator
def say_hi():
    return 'hi george'
  
def main():
    print(say_hi())
  
if __name__ == "__main__":
    main()

输出:

EGROEG IH

在这里,字符串首先被反转,其次它转换为大写。

@uppercase_decorator
@reverse_decorator
def say_hi():
    return 'hi george'

上面的代码在语法上等价于
uppercase_decorator(reverse_decorator(say_hi()))

装饰器函数中的参数

到目前为止,您已经看到了没有任何参数的装饰器。在某些情况下,有必要传递参数来相应地装饰方法。

def decorator_arguments(function):
    def wrapper_arguments(arg1, arg2):
        print("Arguments accepted are: {0}, {1}".format(arg1, arg2))
        function(arg1, arg2)  # calls the function hobbies
  
    return wrapper_arguments
  
@decorator_arguments
def hobbies(hobby_one, hobby_two):
    print("My Hobbies are {0} and {1}".format(hobby_one, hobby_two))
  
def main():
    hobbies("Travelling", "Reading")
  
if __name__ == "__main__":
    main()

输出:

在上述情况下,装饰器不会接受任何参数,而是由可调用函数传递给包装函数。下面的代码在使用引用传递参数的过程中提供了更多的清晰度。

def decorator_arguments(function):
  
    def wrapper_arguments(arg1, arg2):
        print(" Arguments accepted are: {0}, {1}".format(arg1, arg2))
  
    return wrapper_arguments
  
@decorator_arguments
def hobbies(hobby_one, hobby_two):
    print("My Hobbies are {0} and {1}".format(hobby_one, hobby_two))
  
def main():
  
    # the reference of wrapper_arguments
    # is returned
    hob = hobbies
    print(hob)
  
    # passing the arguments to the 
    # wrapper_arguments
    hob('Travelling', 'Reading')
  
main()

输出

装饰器在哪里使用?

使用装饰器的地方
标准库提供了许多包含装饰器的模块以及许多使用装饰器的Python工具和框架。几个例子是:

  • 您可以使用 @classmethod 或 @staticmethod 装饰器来创建方法而不创建实例
  • mock 模块允许使用 @mock.patch 或 @mock.patch.object 作为用于单元测试的装饰器。
  • Django(使用@login_required)等常用工具用于设置登录权限,Flask(使用@app.route)用于函数注册使用装饰器。
  • 要将函数识别为异步任务,Celery 使用 @task 装饰器。

为什么你应该写装饰器

为什么选择装饰器
编写这个可重用的Python功能的两个重要原因是,如果编写得当,装饰器是模块化和显式的。

  • 装饰器的模块化
  • 装饰器是显式的

装饰器的模块化

使用装饰器的程序员可以轻松地在定义的代码块中添加或删除功能,从而避免重复样板设置。

装饰器是显式的

程序员可以根据需要将装饰器应用于所有可调用对象。这确保了可读性并提供了干净的代码。

装饰器用例

何时编写装饰器
在这里,我们将讨论一些装饰器用例,它们可以帮助您理解何时编写装饰器。

  1. 功能插件
  2. 数据清理
  3. 函数注册

功能插件

编写装饰器的首要原因是它可以灵活地向已定义的代码块(函数和类)添加额外的功能。

数据清理

使用适当的数据清理方法,您可以删除或销毁存储在内存中的数据,使其无法恢复。例如,您可以使用缓存工具来避免运行相同的函数,或者您可以使用方法来验证用户登录凭据等,这表明了数据清理的重要性。装饰器可以正确清理传递给装饰函数的参数,也可以清理函数返回的数据。

函数注册

编写装饰器的另一点是注册一个函数。这里装饰器允许两个或多个子系统进行通信,而无需太多关于彼此的信息。

装饰类

上面的部分展示了装饰器如何帮助装饰函数。我们已经看到装饰器是可调用的,它接受并返回一个可调用对象。由于类是可调用的,因此装饰器也用于装饰类。装饰类的一些用途是:

  • 添加或增强属性
  • 属性交互
  • 改变类的API(改变类的声明方式及其实例使用)

让我们来看看如何使用类来装饰一个函数。

class MyDecorator:
  
    def __init__(self, function):
        self.function = function
  
    def __call__(self):
        print("Inside Function Call")
        self.function()
  
@MyDecorator
def function():
    print("GeeksforGeeks")
  
def main():
    function()
  
if __name__ == "__main__":
    main()

输出:

Inside Function Call
GeeksforGeeks

让我们看另一个例子。下面的代码,根据实例的创建时间对实例进行排序。这里我们需要三个附加属性——实例化时间戳、__lt__ 和 __gt__ 方法。

import functools
import time
  
def sort_by_creation_time(cl):
    org_init = cl.__init__
  
    # Enhance the class to store the creation
    # time based on the instantiation.
    @functools.wraps(org_init)
    def new_init(self, *args, **kwargs):
        org_init(self, *args, **kwargs)
        self._created = time.time()
  
    # __lt__ and __gt__ methods return true or false 
    # based on the creation time.
    cl.__init__ = new_init
    cl.__lt = lambda self, other: self._created < other._created
    cl.__gt__ = lambda self, other: self._created > other._created
    return cl
  
@sort_by_creation_time
class Sort(object):
    def __init__(self, identifier):
        self.identifier = identifier
  
    def __repr__(self):
        return self.identifier
  
  
def main():
  
    first = Sort('Python')
    second = Sort('Data Analysis')
    third = Sort('Machine Learning')
  
    sortables = [third, first, second]
    print(sorted(sortables))
  
if __name__ == "__main__":
    main()

输出

[Python, Data Analysis, Machine Learning]

new_init它的主要职责是运行被包装的函数,并为被包装的函数添加额外的函数@functools.wraps(org_init)更新包装函数以反映被包装函数的外观。检查 functools 以了解详细信息。

@functools.wraps(org_init)
def new_init(self, *args, **kwargs):
 
    # calls the init method of class
    org_init(self, *args, **kwargs)
 
    # add an extra attribute and store 
    # creation time  
    self._created = time.time() 
 
# reference of new_init is assigned to
# __init__ method and executes when
# callable creates the class object.
cl.__init__ = new_init  

装饰一个函数并返回一个类

有时需要装饰一个函数并返回一个类。例如,高级案例开发人员可以在 API 中子类化一个类。它还可以避免增加样板代码。

class Addition(object):
  
    def __call__(self, *args, **kwargs):
        return self.run(*args, **kwargs)
  
    def run(self, *args, **kwargs):
        raise NotImplementedError('Subclass must implement `run`.')
          
    def identity(self):
        return 'Hi George, I''m here to help you with addition !'
  
def addition(decorated):
    class AddSubclass(Addition):
        def run(self, *args, **kwargs):
  
            if args:
                add = 0
                for arg in args:
                    add = arg + add   # add arguments 
                return add
                  
            else:
                # func is called if there are no arguments passed.
                return decorated(*args, **kwargs) 
                                                      
  
    return AddSubclass()
  
@addition
def func(*args):
    return 1 + 1
  
print(func.identity())
print(func())
print(func(2, 2))
print(func(2, 1, 4))

输出

Hi George, Im here to help you with addition!
2
4
7

在这里,装饰器创建了 Addition 的子类并返回子类的实例而不是引用( return AddSubclass() )。

def addition(decorated):
    class AddSubclass(Addition):
        def run(self, *args, **kwargs):
  
            if args:
                add = 0
                for arg in args:
                    add = arg + add
                return add
                  
            else:
                return decorated(*args, **kwargs)
  
    return AddSubclass()

而 `Addition` 类中的 __call__ 方法意味着它的实例是可调用的。因此,子类 `AddSubclass()` 的实例可以运行任务,因此程序员可以避免将 run 方法调用为func().run()

概括

装饰器是围绕函数和类包装代码的出色工具,它提供了一种使用样板代码的有效方法,也有助于提高可读性。尽管它们是模块化和显式的,但这个工具也有缺点。由于它是一种将代码包装在已定义块周围的工具,因此调试变得更具挑战性。而且,写得不好的装饰器可能会导致错误。
毕竟,一个编写良好的装饰器可以被视为一种高效的可重用Python功能,用于包装已定义的代码块。