关于Python装饰器
装饰器使您能够在不更改函数源代码的情况下修改函数的行为,从而提供一种简洁灵活的方式来增强和扩展函数的功能。
在本文中,我将详细介绍如何在 Python 中使用装饰器,并展示装饰器在何处有用的示例。
快速功能回顾
简而言之,函数是一种使用不同参数重复运行代码块的方法。
换句话说,它可以接受输入,使用这些输入来运行一些预定义的代码集,然后返回一个输出。
函数接受输入,使用它来运行一组代码并返回输出
在 Python 中,一个函数是这样写的:
def add_one(num):
return num + 1
当我们想调用它时,我们可以用括号写出函数的名称并传入必要的输入(参数):
final_value = add_one(1)
print(final_value) # 2
请注意,在大多数情况下,参数和形参的含义相同。它们是函数中使用的变量。
区别在于我们指的是哪里。参数是我们在调用函数时传递给函数的内容,参数是函数中声明的内容。
如何将函数作为参数传递
通常,在调用带有参数的函数时,我们会传递整数、浮点数、字符串、列表、字典和其他数据类型的值。
但是,我们还可以做的是将一个函数也作为参数传递:
def inner_function():
print("inner_function is called")
def outer_function(func):
print("outer_function is called")
func()
outer_function(inner_function)
# outer_function is called
# inner_function is called
在这个例子中,我们创建了两个函数:inner_function和outer_function。
outer_function有一个参数调用,func它在调用它自己之后调用它。
outer_function 首先执行。然后它调用作为参数传递的函数
把它想象成我们如何像对待任何其他值或变量一样对待函数。
正确的说法是函数是一等公民。这意味着它们就像任何其他对象一样,可以作为参数传递给其他函数、分配给变量或由其他函数返回。
所以,outer_function可以接受一个函数作为参数,并在执行时调用它。
如何返回函数
能够将函数视为对象的另一个好处是我们可以在其他函数中定义它们并返回它们:
def outer_function():
print("outer_function is called")
def inner_function():
print("inner_function is called")
return inner_function
请注意,在这个例子中,当我们 return 时inner_function,我们没有调用它。
我们只返回了对它的引用,以便我们稍后可以存储和调用它:
returned_function = outer_function()
# outer_funciton is called
returned_function()
# inner_function is called
如果你和我一样,这可能看起来很有趣,但你可能仍然想知道这在实际程序中有何用处🤔。这是我们稍后要看的内容!
如何在 Python 中创建装饰器
接受函数作为参数,在其他函数中定义函数,并返回它们正是我们在 Python 中创建装饰器所需要知道的。我们使用装饰器为现有功能添加额外的功能。
例如,如果我们想创建一个装饰器,它会将任何函数的返回值加 1,我们可以这样做:
def add_one_decorator(func):
def add_one():
value = func()
return value + 1
return add_one
现在,如果我们有一个返回数字的函数,我们可以使用这个装饰器将 1 添加到它输出的任何值。
def example_function():
return 1
final_value = add_one_decorator(example_function)
print(final_value()) # 2
在此示例中,我们调用该add_one_decorator函数并将引用传递给example_function.
当我们调用该add_one_decorator函数时,它会创建一个新函数,add_one并在其中定义并返回对该新函数的引用。我们将此函数存储在变量中final_value。
所以,当执行final_value函数时,add_one函数被调用。
add_one然后,其中定义的函数将add_one_decorator调用example_function,存储它的值,并将其加一。
最终,这会2返回并打印到控制台。
代码将如何执行的过程
请注意我们如何不必更改原始文件example_function来修改其返回值并向其添加功能。这就是使装饰器如此有用的原因!
澄清一下,装饰器并不是 Python 特有的。它们是可以应用于其他编程语言的概念。但是在 Python 中,您可以使用语法轻松地使用它们@。
如何@在 Python 中使用语法
人物
正如我们在上面看到的,当我们要使用装饰器时,我们必须调用装饰器函数并传入我们要修改的函数。
在 Python 中,我们可以利用@语法来提高效率。
@add_one_decorator
def example_function():
return 1
通过@add_one_decorator在我们的函数上面写,它等效于以下内容:
example_function = add_one_decorator(example_function)
这意味着无论何时我们调用example_function,我们实际上都是add_one在调用装饰器中定义的函数。
如何与装饰器传递参数
使用装饰器时,我们可能还希望装饰函数在从包装函数调用时能够接收参数。
例如,如果我们有一个函数需要两个参数并返回它们的总和:
def add(a,b):
return a + b
print(add(1,2)) # 3
如果我们使用装饰器将 1 添加到输出:
def add_one_decorator(func):
def add_one():
value = func()
return value + 1
return add_one
@add_one_decorator
def add(a,b):
return a + b
add(1,2)
# TypeError: add_one_decorator.<locals>.add_one() takes 0 positional arguments but 2 were given
这样做时,我们遇到了一个错误:包装函数 ( add_one) 不接受任何参数,但我们提供了两个参数。
为了解决这个问题,我们需要在调用它时将接收到的任何参数传递add_one给装饰函数:
def add_one_decorator(func):
def add_one(*args, **kwargs):
value = func(*args, **kwargs)
return value + 1
return add_one
add_one_decorator
def add(a,b):
return a+b
print(add(1,2)) # 4
我们使用*args和**kwargs来表示add_one包装函数应该能够接收任意数量的位置参数 ( args) 和关键字参数 ( kwargs)。
args`将是给定的所有位置关键字的列表,在这种情况下`[1,2].
kwargs将是一个字典,键作为使用的关键字参数,值作为分配给它们的值,在本例中是一个空字典。
写作func(*args, **kwargs)表明我们想要调用func与接收到的相同的位置和关键字参数
这确保传递给修饰函数的所有位置参数和关键字参数都将传递给原始函数。
为什么 Python 中的装饰器有用?真实代码示例
现在我们已经了解了 Python 装饰器到底是什么,让我们看看装饰器何时有用的一些真实示例。
记录
在构建较大的应用程序时,记录哪些函数执行时使用了哪些信息的日志通常很有帮助,例如使用了哪些参数,以及在应用程序运行时函数返回了什么。
当出现问题时,这对于故障排除和调试非常有用,有助于查明问题的根源。即使不用于调试,日志记录也可用于监视程序的状态。
这是一个简单的示例,说明我们如何创建一个简单的记录器(使用内置的 Pythonlogging包)以将有关我们的应用程序运行的信息保存到名为的文件中main.log:
import logging
def function_logger(func):
logging.basicConfig(level = logging.INFO, filename="main.log")
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
logging.info(f"{func.__name__} ran with positional arguments: {args} and keyword arguments: {kwargs}. Return value: {result}")
return result
return wrapper
@function_logger
def add_one(value):
return value + 1
print(add_one(1))
每当该add_one函数运行时,一个新日志将附加到main.log文件中:
INFO:root:add_one ran with positional arguments: (1,) and keyword arguments: {}. Return value: 2
缓存
如果我们有一个应用程序需要使用相同的参数多次运行相同的函数,返回相同的值,它很快就会变得低效并占用不必要的资源。
为防止这种情况,在每次调用函数时存储所使用的参数和函数的返回值可能很有用,如果我们已经使用相同的参数调用了函数,则只需重新使用返回值即可。
在 Python 中,这可以通过使用随 Python 安装的模块@lru_cache中的装饰器来实现。functools
LRU指的是Least Recently Used,意思是每当调用函数时,都会存储使用的参数和返回值。但是一旦此类条目的数量达到最大大小(默认情况下为 128),最近最少使用的条目将被删除。
from functools import lru_cache
@lru_cache
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
在此示例中,函数fibonacci接受参数n,如果它小于1,则返回n,否则返回调用函数的总和n-1和n-2。
因此,如果使用 调用该函数n=10,它会返回55:
print(fibnoacci(10))
# 55
在这种情况下,当我们调用函数 时fibonacci(10),它会调用函数fibonacci(9)and fibonacci(8),依此类推,直到到达 1 或 0。
如果我们要多次使用这个函数:
fibonacci(50)
fibonacci(100)
我们可以利用已保存条目的缓存。因此,当我们调用时,它可以在到达时fibonacci(50)停止调用该函数,而当我们调用时,它可以在到达时停止调用该函数,从而使程序更加高效。fibonacci10fibonacci(100)``50
这些示例有一个共同点,那就是它们非常容易在 Python 中实现您预先存在的函数。您不需要更改代码或手动将您的函数包装在另一个代码中。
能够简单地使用@语法使得利用其他模块和包变得轻而易举。
概括
Python 装饰器可以毫不费力地扩展函数而无需修改它们。在本教程中,您了解了装饰器的工作原理,并看到了一些可以使用它们的示例。