Python函数装饰器的创建与链式调用

Python函数装饰器的创建与链式调用

技术背景

在Python中,装饰器是一种强大的工具,它允许在不修改原函数代码的情况下,对函数的行为进行扩展。装饰器本质上是一个函数,它接受另一个函数作为参数,并返回一个新的函数。这种特性使得装饰器在很多场景下非常有用,比如日志记录、性能测试、权限验证等。

实现步骤

1. 理解Python函数是对象

在Python中,函数是一等公民,可以被赋值给变量、作为参数传递给其他函数,也可以在函数内部定义函数。以下是一些示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 定义一个函数
def shout(word="yes"):
return word.capitalize() + "!"

# 将函数赋值给变量
scream = shout
print(scream()) # 输出: 'Yes!'

# 在函数内部定义函数
def talk():
def whisper(word="yes"):
return word.lower() + "..."
print(whisper())

talk() # 输出: "yes..."

2. 函数引用与返回函数

函数可以作为参数传递给其他函数,也可以返回另一个函数。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
def getTalk(kind="shout"):
def shout(word="yes"):
return word.capitalize() + "!"
def whisper(word="yes"):
return word.lower() + "..."
if kind == "shout":
return shout
else:
return whisper

talk = getTalk()
print(talk()) # 输出: 'Yes!'

3. 手动创建装饰器

手动创建装饰器的步骤如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 定义一个装饰器
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_decorated = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function_decorated()

4. 使用装饰器语法

Python提供了@符号来简化装饰器的使用:

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

another_stand_alone_function()

5. 链式调用装饰器

可以将多个装饰器链式应用到同一个函数上:

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()

核心代码

以下是实现@make_bold@make_italic装饰器的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from functools import wraps

# 定义makebold装饰器
def makebold(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
return wrapper

# 定义makeitalic装饰器
def makeitalic(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return "<i>" + fn(*args, **kwargs) + "</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
def a_decorator_passing_arguments(function_to_decorate):
def a_wrapper_accepting_arguments(arg1, arg2):
print("I got args! Look: {0}, {1}".format(arg1, arg2))
function_to_decorate(arg1, arg2)
return a_wrapper_accepting_arguments

@a_decorator_passing_arguments
def print_full_name(first_name, last_name):
print("My name is {0} {1}".format(first_name, last_name))

print_full_name("Peter", "Venkman")

装饰方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def method_friendly_decorator(method_to_decorate):
def wrapper(self, lie):
lie = lie - 3
return method_to_decorate(self, lie)
return wrapper

class Lucy(object):
def __init__(self):
self.age = 32

@method_friendly_decorator
def sayYourAge(self, lie):
print("I am {0}, what did you think?".format(self.age + lie))

l = Lucy()
l.sayYourAge(-3)

传递参数给装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def decorator_maker_with_arguments(decorator_arg1, decorator_arg2):
print("I make decorators! And I accept arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))
def my_decorator(func):
print("I am the decorator. Somehow you passed me arguments: {0}, {1}".format(decorator_arg1, decorator_arg2))
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")

常见问题

1. 装饰器会减慢函数调用速度

装饰器本质上是在原函数的基础上增加了一层包装,会引入额外的函数调用开销,从而减慢函数调用速度。在性能敏感的场景中,需要谨慎使用。

2. 无法取消装饰

一旦函数被装饰,就无法取消装饰。虽然有一些技巧可以实现可移除的装饰器,但实际中很少使用。

3. 调试困难

装饰器会包装函数,使得调试变得困难。不过,从Python 2.5开始,可以使用functools.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

Python函数装饰器的创建与链式调用
https://119291.xyz/posts/2025-04-14.python-function-decorators-and-chaining/
作者
ww
发布于
2025年4月14日
许可协议