浅谈python装饰器的参数传入

正好没事看到这样的一个图片:
paradin

整个过程说的通俗易懂,可以拿去用于校招面试考察基本功。

但是它图里的例子太简单了,而且是非常理想化的不带参数的装饰器。如果根据这个图照搬照抄想自己写一个带参数的装饰器,那么就会失败。

被装饰的函数带有参数的情况

举个例子,比如下面这个python,是完全仿照上面的例子写的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 创建一个装饰器
def zhuangshiqi(func):
def xiulian(book):
print ("开始!")
func()
print ("他终于修炼成功了"+book)
return xiulian

# 引用这个装饰器,原函数是传入一个参数,输出一顿话
@zhuangshiqi
def find_book(name):
print("经过努力",name,"得到了武功秘籍!")

find_book("杨过","九阴真经")

原以为这个输出的结果是:

1
2
3
开始!
经过努力 杨过 得到了武功秘籍!
他终于修炼成功了九阴真经

哪知道,结果是这样的:

1
zhuangshiqi.<locals>.xiulian() takes 1 positional argument but 2 were given

嗯,学习知识还是要通过实际的栽跟头和实际的场景去学习的。

首先这个错误的意思就是xiulian()这个函数里是只能传入一个函数的,但是调用的是传入了两个函数。所以我们需要确保 xiulian 函数接收到的参数能够正确传递给 find_book 函数。调整过的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
def zhuangshiqi(func):
def xiulian(*args, **kwargs):
print("开始!")
func(*args, **kwargs)
print ("他终于修炼成功了" + args[1])
return xiulian

@zhuangshiqi
def find_book(name):
print("经过努力", name, "得到了武功秘籍!")

find_book("杨过", "九阴真经")

首先就像图里的第四个小图说的,当使用@zhuangshiqi 装饰 find_book 时,等价于:find_book = zhuangshiqi(find_book),这意味着 find_book 现在指向 xiulian 函数,xiulian 函数其实是包裹了原始的 find_book 函数的。而find_book看上去只有一个函数name,但是其实在xiulian里还有一个就是args[1],所以我们把func()这里加上args, *kwargs,确保这里是能吃进多个函数的。

find_book("杨过", "九阴真经"),这个调用实际上是调用了 xiulian("杨过", "九阴真经")。只不过九阴真经find_book里没有体现,但是确实``xiulian的必须,所以如果传入2个参数的话,就会报错,因为它默认认为只需要1个参数。

这里kwargs是空的,传递的参数被 *args 捕获,所以print ("他终于修炼成功了" + args[1])这里就能顺利拿到“九阴真经”这个函数。

上面例子里用的是 args, *args是位置参数是元组, *kwargs 是关键字参数是字典。如果想改造用 **kwargs的话,代码应该改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
def zhuangshiqi(func):
def xiulian(*args, **kwargs):
print("开始!")
func(*args, **kwargs)
print("修炼了" + kwargs['book'])
return xiulian

@zhuangshiqi
def find_book(name, place,book):
print("经过努力", name, "在", place, "得到了武功秘籍!")

find_book(name="林平之",place='岳不群家窗户外',book="辟邪剑谱") # 这里多家了一个函数

这段代码的args是空的,kwargs{'name': '林平之','place': '岳不群家窗户外','book': '辟邪剑谱'}。 看上去kwargs的用法更加清晰,推荐!

还要注意一点,装饰器函数装饰器应该返回一个新函数,而不是立即执行原函数。所以例子里zhuangshiqi下面必须要有xiulian这个函数,不能直接接func()

装饰器本身带有参数的情况

装饰器本身也是可以带函数的,下面这个例子就比较明显:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import time
from functools import wraps

def delay(seconds):
"""
装饰器,用于延迟函数的执行。
:param seconds: 延迟的秒数
"""
def decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
print(f"等待 {seconds} 秒后执行 {func.__name__} 函数...")
time.sleep(seconds)
result = func(*args, **kwargs)
return result
return wrapped_function
return decorator

# 使用装饰器
@delay(3)
def greet(name):
print(f"Hello, {name}!")

@delay(2)
def add(a, b):
result = a + b
print(f"结果是: {result}")
return result

# 调用被装饰的函数
greet("Alice")
add(5, 3)

上面这个例子里,delay(seconds)这是一个装饰器函数,他返回的是一个真正的装饰器decorator。而装饰器decorator,它接收一个函数func作为参数。

而内部函数wrapped_function,该函数在调用被装饰函数之前等待指定的时间。并且通过{func.__name__}获取到被装饰函数的名字,并在调用被装饰函数之后打印出该函数的名字。这句话可以更好的帮你理解 被调函数=装饰器(函数)这个装饰器的基本逻辑。

@wraps(func)是啥意思呢?它是为了确保装饰器不会改变被装饰函数的签名和文档字符串(docstring)而加上的。

装饰器本身带有参数的情况其实在工作中很常见,最常见的操作就是打印日志的时候,可以在装饰器添加参数level="INFO" or evel="DEBUG"来调整对应的日志级别。

总之,装饰器是python的一个进阶,有了它,能够使你的代码更加简洁和易维护,而且也让菜鸟们更加崇拜你。

感谢您请我喝咖啡~O(∩_∩)O,如果要联系请直接发我邮箱chenx1242@163.com,我会回复你的
-------------本文结束感谢您的阅读-------------