Python中的装饰器

如果你想了解函数, 请参加srcmini的Python数据科学工具箱(第1部分)课程。
装饰器是Python中的一种设计模式, 允许用户在不修改其结构的情况下向现有对象添加新功能。装饰器通常在要装饰的函数的定义之前调用。在本教程中, 我们将向读者展示他们如何在Python函数中使用装饰器。
Python中的函数是一等公民。这意味着它们支持诸如作为参数传递, 从函数返回, 修改并分配给变量的操作。这是在我们深入研究创建Python装饰器之前要理解的基本概念。
将函数分配给变量
【Python中的装饰器】为使我们开始, 我们创建了一个函数, 该函数将在调用数字时将其加1。然后, 我们将函数分配给一个变量, 并使用此变量来调用该函数。

def plus_one(number):return number + 1add_one = plus_oneadd_one(5)

6

在其他函数中定义函数
接下来, 我们将说明如何在Python中的另一个函数内定义一个函数。待在我身边, 我们很快就会发现这一切与在Python中创建和理解装饰器有何关系。
def plus_one(number):def add_one(number):return number + 1result = add_one(number)return resultplus_one(4)

5

将函数作为参数传递给其他函数
函数也可以作为参数传递给其他函数。让我们在下面说明。
def plus_one(number):return number + 1def function_call(function):number_to_add = 5return function(number_to_add)function_call(plus_one)

6

返回其他函数的函数
一个函数还可以生成另一个函数。我们将在下面通过示例展示。
def hello_function():def say_hi():return "Hi"return say_hihello = hello_function()hello()

'Hi'

嵌套函数可以访问封闭函数的变量范围
Python允许嵌套函数访问封闭函数的外部范围。这是装饰器中的关键概念-这种模式称为” 关闭” 。
def print_message(message):"Enclosong Function"def message_sender():"Nested Function"print(message)message_sender()print_message("Some random message")

Some random message

创建装饰器
有了这些先决条件, 让我们继续创建一个简单的装饰器, 它将一个句子转换为大写。我们通过在一个封闭函数内定义一个包装器来实现。如你所见, 它与我们之前创建的另一个函数内部的函数非常相似。
def uppercase_decorator(function):def wrapper():func = function()make_uppercase = func.upper()return make_uppercasereturn wrapper

我们的装饰器函数接受一个函数作为参数, 因此, 我们将定义一个函数并将其传递给我们的装饰器。我们早先了解到我们可以将函数分配给变量。我们将使用该技巧来调用装饰器函数。
def say_hi():return 'hello there'decorate = uppercase_decorator(say_hi)decorate()

'HELLO THERE'

但是, Python为我们提供了应用装饰器的简便得多的方法。我们只需在要装饰的函数之前使用@符号。让我们在下面的实践中证明这一点。
@uppercase_decoratordef say_hi():return 'hello there'say_hi()

'HELLO THERE'

将多个装饰器应用于单个功能
我们可以对单个函数使用多个装饰器。但是, 装饰器将按照我们称为装饰器的顺序进行应用。在下面, 我们将定义另一个装饰器, 该装饰器将句子分成列表。然后, 我们将uppercase_decorator和split_string装饰器应用于单个函数。
def split_string(function):def wrapper():func = function()splitted_string = func.split()return splitted_stringreturn wrapper

@split_string@uppercase_decoratordef say_hi():return 'hello there'say_hi()

['HELLO', 'THERE']

从上面的输出中, 我们注意到装饰器的应用是自下而上的。如果我们交换了订单, 由于列表没有上层属性, 我们将看到一个错误。该句子首先被转换为大写, 然后被拆分为一个列表。
接受装饰器函数中的参数
有时我们可能需要定义一个接受参数的装饰器。我们通过将参数传递给包装函数来实现这一点。然后, 参数将传递给调用时正在修饰的函数。
def decorator_with_arguments(function):def wrapper_accepting_arguments(arg1, arg2):print("My arguments are: {0}, {1}".format(arg1, arg2))function(arg1, arg2)return wrapper_accepting_arguments@decorator_with_argumentsdef cities(city_one, city_two):print("Cities I love are {0} and {1}".format(city_one, city_two))cities("Nairobi", "Accra")

My arguments are: Nairobi, AccraCities I love are Nairobi and Accra

定义通用装饰器
为了定义可以应用于任何功能的通用装饰器, 我们使用args和** kwargs。 args和** kwargs收集所有位置和关键字参数, 并将它们存储在args和kwargs变量中。 args和kwargs允许我们在函数调用期间传递尽可能多的参数。
def a_decorator_passing_arbitrary_arguments(function_to_decorate):def a_wrapper_accepting_arbitrary_arguments(*args, **kwargs):print('The positional arguments are', args)print('The keyword arguments are', kwargs)function_to_decorate(*args)return a_wrapper_accepting_arbitrary_arguments@a_decorator_passing_arbitrary_argumentsdef function_with_no_argument():print("No arguments here.")function_with_no_argument()

The positional arguments are ()The keyword arguments are {}No arguments here.

让我们看看如何使用带有位置参数的装饰器。
@a_decorator_passing_arbitrary_argumentsdef function_with_arguments(a, b, c):print(a, b, c)function_with_arguments(1, 2, 3)

The positional arguments are (1, 2, 3)The keyword arguments are {}1 2 3

关键字参数是使用关键字传递的。如下所示。
@a_decorator_passing_arbitrary_argumentsdef function_with_keyword_arguments():print("This has shown keyword arguments")function_with_keyword_arguments(first_name="Derrick", last_name="Mwiti")

The positional arguments are ()The keyword arguments are {'first_name': 'Derrick', 'last_name': 'Mwiti'}This has shown keyword arguments

将参数传递给装饰器
现在, 让我们看看如何将参数传递给装饰器本身。为了实现这一点, 我们定义了一个装饰器制造商, 该制造商接受参数, 然后在其中定义一个装饰器。然后, 我们像之前所做的那样在装饰器中定义包装函数。
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2, decorator_arg3):def decorator(func):def wrapper(function_arg1, function_arg2, function_arg3) :"This is the wrapper function"print("The wrapper can access all the variables\n""\t- from the decorator maker: {0} {1} {2}\n""\t- from the function call: {3} {4} {5}\n""and pass them to the decorated function".format(decorator_arg1, decorator_arg2, decorator_arg3, function_arg1, function_arg2, function_arg3))return func(function_arg1, function_arg2, function_arg3)return wrapperreturn decoratorpandas = "Pandas"@decorator_maker_with_arguments(pandas, "Numpy", "Scikit-learn")def decorated_function_with_arguments(function_arg1, function_arg2, function_arg3):print("This is the decorated function and it only knows about its arguments: {0}"" {1}" " {2}".format(function_arg1, function_arg2, function_arg3))decorated_function_with_arguments(pandas, "Science", "Tools")

The wrapper can access all the variables- from the decorator maker: Pandas Numpy Scikit-learn- from the function call: Pandas Science Toolsand pass them to the decorated functionThis is the decorated function, and it only knows about its arguments: Pandas Science Tools

调试装饰器
正如我们已经注意到的, 装饰器包装函数。原始函数名称, 它的文档字符串和参数列表都被包装封包隐藏:例如, 当我们尝试访问decorated_function_with_arguments元数据时, 我们将看到包装封包的元数据。这在调试时提出了挑战。
decorated_function_with_arguments.__name__

'wrapper'

decorated_function_with_arguments.__doc__

'This is the wrapper function'

为了解决这一难题, Python提供了functools.wraps装饰器。该装饰器将丢失的元数据从未装饰的函数复制到装饰的闭包中。让我们展示一下如何做到这一点。
import functoolsdef uppercase_decorator(func):@functools.wraps(func)def wrapper():return func().upper()return wrapper

@uppercase_decoratordef say_hi():"This will say hi"return 'hello there'say_hi()

'HELLO THERE'

当我们检查say_hi元数据时, 我们注意到它现在是在指功能的元数据, 而不是包装器的元数据。
say_hi.__name__

'say_hi'

say_hi.__doc__

'This will say hi'

在定义装饰器时始终使用functools.wraps是明智的做法, 也是一种好的做法。这将使你免于调试的很多麻烦。
Python装饰器摘要
装饰器可以动态更改功能, 方法或类的功能, 而不必直接使用子类或更改要修饰的功能的源代码。在Python中使用装饰器还可以确保你的代码是DRY(请勿重复自己)。装饰器有几种用例, 例如:
  • Python框架(例如Flask和Django)中的授权
  • 记录中
  • 测量执行时间
  • 同步化
要了解有关Python装饰器的更多信息, 请查看Python的装饰器库。

    推荐阅读