如果你想了解函数, 请参加srcmini的Python数据科学工具箱(第1部分)课程。
装饰器是Python中的一种设计模式, 允许用户在不修改其结构的情况下向现有对象添加新功能。装饰器通常在要装饰的函数的定义之前调用。在本教程中, 我们将向读者展示他们如何在Python函数中使用装饰器。
Python中的函数是一等公民。这意味着它们支持诸如作为参数传递, 从函数返回, 修改并分配给变量的操作。这是在我们深入研究创建Python装饰器之前要理解的基本概念。
将函数分配给变量
为使我们开始, 我们创建了一个函数, 该函数将在调用数字时将其加1。然后, 我们将函数分配给一个变量, 并使用此变量来调用该函数。
def plus_one(number):
return number + 1
add_one = plus_one
add_one(5)
6
在其他函数中定义函数
接下来, 我们将说明如何在Python中的另一个函数内定义一个函数。待在我身边, 我们很快就会发现这一切与在Python中创建和理解装饰器有何关系。
def plus_one(number):
def add_one(number):
return number + 1
result = add_one(number)
return result
plus_one(4)
5
将函数作为参数传递给其他函数
函数也可以作为参数传递给其他函数。让我们在下面说明。
def plus_one(number):
return number + 1
def function_call(function):
number_to_add = 5
return function(number_to_add)
function_call(plus_one)
6
返回其他函数的函数
一个函数还可以生成另一个函数。我们将在下面通过示例展示。
def hello_function():
def say_hi():
return "Hi"
return say_hi
hello = 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_uppercase
return wrapper
我们的装饰器函数接受一个函数作为参数, 因此, 我们将定义一个函数并将其传递给我们的装饰器。我们早先了解到我们可以将函数分配给变量。我们将使用该技巧来调用装饰器函数。
def say_hi():
return 'hello there'
decorate = uppercase_decorator(say_hi)
decorate()
'HELLO THERE'
但是, Python为我们提供了应用装饰器的简便得多的方法。我们只需在要装饰的函数之前使用@符号。让我们在下面的实践中证明这一点。
@uppercase_decorator
def 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_string
return wrapper
@split_string
@uppercase_decorator
def 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_arguments
def 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, Accra
Cities 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_arguments
def 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_arguments
def 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_arguments
def 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 wrapper
return decorator
pandas = "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 Tools
and pass them to the decorated function
This 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 functools
def uppercase_decorator(func):
@functools.wraps(func)
def wrapper():
return func().upper()
return wrapper
@uppercase_decorator
def 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的装饰器库。
评论前必须登录!
注册