让我们了解一下python装饰器。这是我喜欢在 python 中使用的模式之一,它被高级程序员广泛使用。
什么是Decorators🤔
python中的Decorators是一种设计模式,它允许我们在不修改函数本身的情况下扩展函数的功能。我们可以通过用另一个函数包装我们想要扩展功能的函数来做到这一点。它一点也不复杂,这是一个非常简单的模式。在底层,我们只是将一个函数作为参数传递给另一个函数,该函数将返回一个函数,我们可以调用它来调用最初作为参数传递的函数。
Decorators背后的模式
在python中,我们可以嵌套函数。内部函数可以访问封闭函数的外部范围。这意味着如果我们在封闭的函数中定义一个变量,内部函数可以访问它。这种模式称为闭包。这个概念很重要,它是python Decorators的核心概念。这个概念也用于其他语言,包括 JavaScript、Golang 等。
Decorators编码
让我们深入研究一些 Python 代码,这些代码将帮助我们理解上述所有理论和行话。
Decorators功能
假设我们有一个calculate_number
返回数字的函数5
,但随后出现了一些要求,我们需要将该函数的输出增加1
,现在我们可以创建另一个函数increase_number
来帮助我们做到这一点。这只是一个示例,我们将在教程结束时介绍复杂的用例。
我们有返回数字的函数
def calculate_number():
return 5
我们这里还有一个函数,我们关注一下这个函数
# Decorator to increase function output by one
def increase_number(func):
def wrapper():
"""
Calls the function passed as an argument in the outer
scope and adds one to the output
"""
return func() + 1
return wrapper
所以让我们分解一下;
-
看看
increase_number
功能,def increase_number(func): def wrapper(): ... return wrapper
我们需要将一个函数作为参数传递给这个函数。
increase_number
在被调用的函数中定义了一个函数wrapper
,我们可以将其命名为任何名称,并且函数在不调用的情况下返回。这意味着如果increase_number
调用函数,它将返回一个函数作为响应,wrapper
也可以调用它。# Pass `calculate_number` as an argument var_a = increase_number(calculate_number) # Call the function returned above inner_function_response = var_a()
var_a
从上面将包含wrapper
等待调用的函数。然后它被调用并将其返回值分配给变量inner_function_response
。 -
在上面的嵌套函数
wrapper
中,我们调用了作为参数传递给封闭函数的函数,并添加1
到它并返回答案,def wrapper(): """ Calls the function passed as an argument in the outer scope and adds one to the output """ return func() + 1
我们可以调用作为参数传递的函数,因为它在封闭函数的范围内,而嵌套函数作为对外部范围的访问。这个概念称为闭包。我们也可以在调用函数之前和调用函数之后做一些事情。另外,在定义函数之前和定义函数之后做一些事情
wrapper
。试试下面的例子;
def outer_function(func):
print('Before defining inner function')
def inner_function():
print('Do something before calling function')
func() # Call function passed as argument in outer function
print('Do something after calling function')
print('Do something before returning the inner function')
return inner_function
def sample():
print('I was decorated')
outer_function(sample)()
输出
Before defining inner function
Do something before returning the inner function
Do something before calling function
I was decorated
Do something after calling function
完整代码:增加数字Decorators
def increase_number(func):
def wrapper():
return func() + 1
return wrapper
def calculate_number():
return 5
var_a = increase_number(calculate_number)
inner_function_response = var_a()
print(inner_function_response)
运行上面的代码应该输出6
.
总结再次发生的事情;
- 函数
calculate_number
返回 5, - 该函数
increase_number
是一个Decorators,它接收一个函数并用另一个函数包装它,然后返回包装器,允许我们操纵作为参数传递的函数的返回值,该参数将成为 functioncalculate_number
。 -
wrapper
函数内部的嵌套函数 ,increase_number
调用传递给 function 的函数increase_number
并将返回值加 1,然后返回答案。 - 嵌套函数 ,
wrapper
由函数 返回increase_number
,因此我们可以用它做一些事情,大多数时候我们最终只是像普通函数一样调用它,就像我们在这里所做的那样var_a
。我说大部分时间是因为我们也可以将变量中返回的函数var_a
作为参数传递给另一个装饰器函数。
使用Decorators的 Pythonic 语法
Decorators在 python 中被大量使用,甚至还有内置的Decorators,如 staticmethod、classmethod、property 等。因此,您可以使用 Pythonic 语法来装饰函数。假设我们想calculate_number
使用当前的方式将多个Decorators应用到上面代码中的函数,当代码中存在错误或只是阅读代码时,对于您或其他人来说理解代码太复杂了。
Pythonic 代码:增加数量
def increase_number(func):
def wrapper():
return func() + 1
return wrapper
@increase_number
def calculate_number():
return 5
print(calculate_number())
这很漂亮,我喜欢这种语法。这里发生的事情很简单,我们calculate_number
用函数 来装饰函数,方法increase_number
是把它放在前面calculate_number
有@
符号的函数的正上方。这告诉python这是一个装饰器,所以当这个函数calculate_number
被调用时,它会自动将函数传递给Decorators并调用包装函数。所以现在我们可以调用函数本身,它会被装饰。现在我们可以轻松地将多个Decorators添加到一个函数中,而不会使它变得太复杂而难以理解。
多个Decorators
多个Decorators可以应用于单个函数,只需使用Decorators语法将Decorators堆叠在一起即可@decorator_function
。
def split_string(func):
def inner():
return func().split()
return inner
def uppercase_string(func):
def inner():
return func().upper()
return inner
@split_string
@uppercase_string
def speak_python():
return "I love speaking Python"
print(speak_python())
输出
['I', 'LOVE', 'SPEAKING', 'PYTHON']
你可能会想到的第一个问题是,这个装饰器在调用时应用到这个函数的顺序是什么?答案是从下往上,uppercase_string
然后split_string
。所以从上面的输出中你可以看出字符串首先被转换为大写,然后它被拆分返回一个大写字符串的列表。
你也可以测试一下,尝试切换Decorators的位置,@uppercase_string
放在@split_string
.
代码
@uppercase_string
@split_string
def speak_python():
return "I love speaking Python"
输出
...
AttributeError: 'list' object has no attribute 'upper'
从上面的输出中,有一个错误,因为@uppercase_string
Decorators试图调用upper
列表上的方法。发生这种情况是因为@split_string
Decorators拆分字符串首先返回一个字符串列表,然后 @uppercase_string
Decorators尝试upper
在该列表上应用该方法,这是不可能的,因为 python 列表没有upper
内置方法。
装饰接受参数的函数
如果我们的函数接受参数,不用担心,我们只需要更新我们的Decorators以接受参数并在调用它时将它们传递给装饰函数。让我们修改speak_python
上面的函数,接受一种语言作为参数,用字符串格式化,然后返回。
错误代码:
@split_string
@uppercase_string
def speak_language(language):
return f"I love speaking {language}"
print(speak_language("Python"))
输出:
...
TypeError: inner() takes 0 positional arguments but 1 was given
我们收到错误是因为参数Python
被传递给Decorators的内部函数,但这些内部函数不接受任何参数、位置参数或关键字参数。所以我们必须更新Decorators以确保内部函数接受参数。
好代码:
def split_string(func):
def inner(a):
return func(a).split()
return inner
def uppercase_string(func):
def inner(a):
return func(a).upper()
return inner
@split_string
@uppercase_string
def speak_language(language):
return f"I love speaking {language}"
print(speak_language("Python"))
输出:
['I', 'LOVE', 'SPEAKING', 'PYTHON']
现在它正在工作,发生了什么;
- 内部函数现在接受一个参数,
- 然后将参数传递给装饰函数。
通用Decorators
我们还可以创建一个Decorators,它接受任意数量的参数、位置参数和关键字参数,并将它们传递给装饰函数。假设我们更新了speak_language
函数以接受更多参数,我们将不得不更新所有Decorators以接受这些参数。想象一下,我们有一个包含很多Decorators的复杂代码库,很难更新所有Decorators。所以我们应该确保我们的Decorators将来可以处理多个参数。
最佳代码:
def split_string(func):
def inner(*args, **kwargs):
return func(*args, **kwargs).split()
return inner
def uppercase_string(func):
def inner(*args, **kwargs):
return func(*args, **kwargs).upper()
return inner
@split_string
@uppercase_string
def speak_language(word, language):
return f"I {word} speaking {language}"
print(speak_language("hate", "Python"))
输出:
['I', 'HATE', 'SPEAKING', 'PYTHON']
*args 和 ** kwargs 允许您将多个参数或关键字参数传递给函数。所以我们在内部函数中使用它来接受任意数量的参数,并将它们全部传递给装饰函数。现在无论speak_language
将来向函数传递多少参数,我们都不需要更新装饰器。少了一个需要考虑的问题。😁
Decorator Factory
让我们回到我们的 increase_number 函数代码:
def increase_number(func):
def wrapper():
return func() + 1
return wrapper
@increase_number
def calculate_number():
return 5
print(calculate_number())
这个Decorators只增加这个数字1
,如果我们想应用增加 10 怎么办?我们可以编辑Decorators将其增加 10。如果我们有另一个函数需要Decorators将其增加 5,我们可以创建另一个类似的Decorators increase_number
将其增加 5。这不好,因为我们在重复代码,我们应该尽量不要在任何可能的地方重复代码,因为如果代码中存在错误,我们将不得不更改所有已复制代码的地方。
Decorators也可以。通过对闭包的理解,我们知道嵌套函数可以访问外部函数的作用域。然后我们可以创建一个返回Decorators的函数。这样做意味着如果我们将参数传递给创建Decorators的函数,Decorators将可以访问传递给其外部函数的参数,并且Decorators的内部函数也应该可以访问这些参数。让我们看一下下面的代码。
代码:
def A(arg1):
def B():
def C():
def D():
def E():
print(arg1)
return E
return D
return C
return B
A("Good Python")()()()()
输出:
Good Python
我认为通过这段代码,我已经公平地解释了嵌套函数总是可以访问外部函数作用域的事实。FunctionE
嵌套了四层,但仍然可以访问 function 的范围A
。
这与Decorators工厂背后的想法相同,这是一个返回Decorators的函数。因此,为了使应用的增加动态化,我们可以创建一个函数,该函数接受要增加的数字作为参数,然后返回一个可以访问该数字的Decorators。代码:
def apply_increase(increase):
def increase_number(func):
def wrapper():
return func() + increase
return wrapper
return increase_number
@apply_increase(10)
def calculate_number():
return 5
@apply_increase(1)
def myother_number():
return 1
print(calculate_number())
print(myother_number())
输出:
15
2
该apply_increase
函数接受数字作为参数,然后返回Decorators。由于上述每个函数返回的Decorators函数都可以访问其外部函数的范围,因此我们得到了唯一的Decorators,因为apply_increase(10)
和apply_increase(1)
中的数字不同。对于返回的两个Decorators,它们的increase
论点会有所不同。
结论👍
Decorators是python中一个非常神奇的模式,用它我们可以扩展被包装函数的功能。我们在本文中介绍了很多内容,但还有其他内容我们没有介绍。类装饰器、方法装饰器和 python functools 模块将在另一篇文章中介绍。感谢阅读,阿里加托。✌️
如果你喜欢这篇文章,你可以给这篇文章点赞,如果你有任何问题或看法,可以在下方评论,并关注我获取更多关于软件编程的更新。
网友评论