正好没事看到这样的一个图片:
整个过程说的通俗易懂,可以拿去用于校招面试考察基本功。
但是它图里的例子太简单了,而且是非常理想化的不带参数的装饰器。如果根据这个图照搬照抄想自己写一个带参数的装饰器,那么就会失败。
被装饰的函数带有参数的情况
举个例子,比如下面这个python,是完全仿照上面的例子写的:
1 | # 创建一个装饰器 |
原以为这个输出的结果是:
1
2
3开始!
经过努力 杨过 得到了武功秘籍!
他终于修炼成功了九阴真经
哪知道,结果是这样的:
1
zhuangshiqi.<locals>.xiulian() takes 1 positional argument but 2 were given
嗯,学习知识还是要通过实际的栽跟头和实际的场景去学习的。
首先这个错误的意思就是xiulian()这个函数里是只能传入一个函数的,但是调用的是传入了两个函数。所以我们需要确保 xiulian 函数接收到的参数能够正确传递给 find_book 函数。调整过的代码如下:
1 | def zhuangshiqi(func): |
首先就像图里的第四个小图说的,当使用@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 | def zhuangshiqi(func): |
这段代码的args
是空的,kwargs
是{'name': '林平之','place': '岳不群家窗户外','book': '辟邪剑谱'}
。 看上去kwargs的用法更加清晰,推荐!
还要注意一点,装饰器函数装饰器应该返回一个新函数,而不是立即执行原函数。所以例子里zhuangshiqi
下面必须要有xiulian
这个函数,不能直接接func()
。
装饰器本身带有参数的情况
装饰器本身也是可以带函数的,下面这个例子就比较明显:
1 | import time |
上面这个例子里,delay(seconds)
这是一个装饰器函数,他返回的是一个真正的装饰器decorator
。而装饰器decorator
,它接收一个函数func
作为参数。
而内部函数wrapped_function
,该函数在调用被装饰函数之前等待指定的时间。并且通过{func.__name__}
获取到被装饰函数的名字,并在调用被装饰函数之后打印出该函数的名字。这句话可以更好的帮你理解 被调函数=装饰器(函数)
这个装饰器的基本逻辑。
而@wraps(func)
是啥意思呢?它是为了确保装饰器不会改变被装饰函数的签名和文档字符串(docstring)而加上的。
装饰器本身带有参数的情况其实在工作中很常见,最常见的操作就是打印日志的时候,可以在装饰器添加参数level="INFO" or evel="DEBUG"
来调整对应的日志级别。
总之,装饰器是python的一个进阶,有了它,能够使你的代码更加简洁和易维护,而且也让菜鸟们更加崇拜你。