装饰器(decorator)是Python中的设计模式,它允许用户在不修改现有对象结构的情况下向其添加新功能。装饰器通常应用于函数,它们在增强或修改函数的行为方面起着至关重要的作用。传统上,装饰器放在想要装饰的函数的定义之前。在本教程中,我们将演示如何在Python函数中有效地使用装饰器。
Python中函数是一等公民,这意味着它们支持诸如作为参数传递、从函数返回、修改和赋值给变量等操作。这个特性是至关重要的,因为它允许函数像Python中的任何其他对象一样被对待,从而在编程中实现更大的灵活性。
函数特性
函数变量
首先,我们创建一个函数,每当调用该函数时,它都会给数字加1。然后将函数赋值给变量,并使用该变量调用函数。
def plus_one(number):return number + 1add_one = plus_one
add_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():result = function()make_uppercase = result.upper()return make_uppercasereturn wrapper
decorator函数接受一个函数作为参数,因此我们应该定义函数并将其传递给decorator。我们之前学过可以将函数赋值给变量。我们将使用这个技巧来调用decorator函数。
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
装饰器应用到单个至函数。
import functools
def split_string(function):@functools.wraps(function)def wrapper():result = function()splitted_string = result.split()return splitted_stringreturn wrapper @split_string
@uppercase_decorator
def say_hi():return 'hello there'say_hi()
## ['HELLO', 'THERE']
从上面的输出中,我们注意到装饰器的应用是自下而上的。如果我们交换了顺序,我们就会看到错误信息,因为列表没有上upper
属性。这个句子首先被转换成大写,然后被分成列表。
注意:当堆叠装饰器时,使用functools.wraps
是一种常见的做法,这样能保留原函数的元数据。这有助于在调试和理解修饰函数的属性时保持清晰度,后面调试部分你会看到差异。
装饰函数参数
有时我们可能需要定义接受参数的装饰器。我们通过将参数传递给包装器函数来实现,然后将参数传递给调用时正在装饰的函数。
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
注意: 必须确保装饰器中的参数数量(本例中为arg1, arg2)与包装函数中的参数数量(本例中为cities)匹配。当使用带参数的装饰器时,这种对齐对于避免错误和确保正确的功能至关重要。
通用装饰器
要定义可以应用于任何函数的通用装饰器,我们使用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
注意: 在装饰器中使用**kwargs
允许它处理关键字参数。这使得通用装饰器具有通用性,并且能够在函数调用期间处理各种参数类型。
装饰器传参
现在让我们看看如何将参数传递给装饰器本身。为了实现这点,我们需要定义decorator maker,它接受参数,然后在其中定义decorator。最后就像前面一样在装饰器中定义包装器函数:
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 Tools
and pass them to the decorated function
This is the decorated function, and it only knows about its arguments: Pandas Science Tools
调试装饰器
可能你已注意到,装饰器包装函数、原始函数名、以及docstring和参数列表都被包装器闭包隐藏了。例如,当我们尝试访问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_decorator
def say_hi():"This will say hi"return 'hello there'say_hi()
## 'HELLO THERE'say_hi.__name__
## 'say_hi'
say_hi.__doc__
## 'This will say hi'
总是使用functools.wraps
是明智的,也是好的做法。它将在调试时省去很多麻烦。
总结
本文介绍python函数基础知识,然后介绍如何定义函数装饰器。装饰器应用非常广泛,它可以动态地改变函数、方法或类的功能,而不必直接使用子类或更改被装饰函数的源代码。在Python中使用装饰器还可以确保你的代码是DRY(Don’t Repeat Yourself)。下面列举几个应用装饰器的典型用例:
- Python框架(如Flask和Django)中的授权
- fastApi中 restful服务实现
- 日志记录
- 测量执行时间
要了解有关Python Decorator的更多信息,请查看Python的Decorator库。学习了装饰器的基础知识,后面我们会结合实际场景展示应用案例,包括应用日志、运行时间监控、缓存、重试、限流、异常处理、类型转换等。