如何创建函数装饰器并将它们链接在一起

如何创建函数装饰器并将它们链接在一起

技术背景

在 Python 中,装饰器是一种强大的工具,它允许在不修改原函数代码的情况下,对函数的行为进行扩展和修改。装饰器本质上是一个函数,它接受另一个函数作为参数,并返回一个新的函数。装饰器可以用于许多场景,如日志记录、性能测试、权限验证等。了解如何创建函数装饰器并将它们链接在一起,能帮助开发者编写更加模块化、可复用和易于维护的代码。

实现步骤

理解 Python 函数的对象特性

在 Python 中,函数是对象,可以被赋值给变量、在其他函数内部定义,还可以作为参数传递和返回。

1
2
3
4
5
6
7
8
def shout(word="yes"):
return word.capitalize() + "!"

scream = shout
print(scream()) # 输出: 'Yes!'

del shout
print(scream()) # 仍然可以输出 'Yes!'

创建简单的装饰器

装饰器是一个函数,它接受另一个函数作为参数,并返回一个新的函数。新的函数通常会在调用原函数的前后执行一些额外的代码。

1
2
3
4
5
6
7
8
9
10
11
12
def my_shiny_new_decorator(a_function_to_decorate):
def the_wrapper_around_the_original_function():
print("Before the function runs")
a_function_to_decorate()
print("After the function runs")
return the_wrapper_around_the_original_function

def a_stand_alone_function():
print("I am a stand alone function, don't you dare modify me")

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()

使用装饰器语法

Python 提供了 @ 语法糖来简化装饰器的使用。

1
2
3
4
5
@my_shiny_new_decorator
def another_stand_alone_function():
print("Leave me alone")

another_stand_alone_function()

链式调用装饰器

可以将多个装饰器应用到同一个函数上,它们会按照从下到上的顺序依次执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def bread(func):
def wrapper():
print("</''''''\\>")
func()
print("<\______/>")
return wrapper

def ingredients(func):
def wrapper():
print("#tomatoes#")
func()
print("~salad~")
return wrapper

@bread
@ingredients
def sandwich(food="--ham--"):
print(food)

sandwich()

核心代码

简单装饰器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def makebold(fn):
def wrapper():
return "<b>" + fn() + "</b>"
return wrapper

def makeitalic(fn):
def wrapper():
return "<i>" + fn() + "</i>"
return wrapper

@makebold
@makeitalic
def say():
return "hello"

print(say()) # 输出: <b><i>hello</i></b>

带参数的装饰器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
def my_decorator(func):
def wrapped(function_arg1, function_arg2):
print("I am the wrapper around the decorated function.\n"
"I can access all the variables\n"
"\t- from the decorator: {0} {1}\n"
"\t- from the function call: {2} {3}\n"
"Then I can pass them to the decorated function"
.format(decorator_arg1, decorator_arg2,
function_arg1, function_arg2))
return func(function_arg1, function_arg2)
return wrapped
return my_decorator

@decorator_maker_with_arguments("Leonard", "Sheldon")
def decorated_function_with_arguments(function_arg1, function_arg2):
print("I am the decorated function and only knows about my arguments: {0} {1}"
.format(function_arg1, function_arg2))

decorated_function_with_arguments("Rajesh", "Howard")

最佳实践

  • 使用 functools.wrapsfunctools.wraps 可以将原函数的元信息(如名称、文档字符串等)复制到装饰器返回的新函数中,方便调试和文档生成。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import functools

def bar(func):
@functools.wraps(func)
def wrapper():
print("bar")
return func()
return wrapper

@bar
def foo():
print("foo")

print(foo.__name__) # 输出: foo
  • 避免过度使用装饰器:装饰器会增加函数调用的开销,因此在性能敏感的场景中要谨慎使用。
  • 保持装饰器的简洁性:装饰器应该专注于单一的功能,避免在一个装饰器中实现过多的逻辑。

常见问题

  • 如何传递参数给装饰器:可以使用一个函数来生成装饰器,该函数接受参数并返回一个装饰器。
  • 装饰器的执行顺序:链式调用装饰器时,装饰器会按照从下到上的顺序依次执行。
  • 如何调试被装饰的函数:使用 functools.wraps 可以保留原函数的元信息,方便调试。同时,可以在装饰器中添加日志输出,帮助定位问题。

如何创建函数装饰器并将它们链接在一起
https://119291.xyz/posts/2025-05-09.how-to-create-and-chain-function-decorators/
作者
ww
发布于
2025年5月9日
许可协议