问题产生
现在我有一个装饰器,目的是可以延迟执行某个函数。
python
def delay(timeout: int = 1):
def inner_func(func):
def execute(*args, **kwargs):
time.sleep(timeout)
return func(*args, **kwargs)
return execute
return inner_func
@delay
def run_task():
print("finished")
run_task()
看着完美无缺,肉眼看着应该是延迟1s打印finished,但是运行的时候告诉我:
TypeError: delay.<locals>.inner_func() missing 1 required positional argument: 'func'
Holyshit!!!!!
告诉我inner_func第一个参数缺失?我就纳闷的,这个为什么会缺失呢?这个不是待执行的函数吗?
然后我就debug,在def delay
第一行打印了一下timeout的值
python
def delay(timeout: int = 1):
print(timeout)
输入结果是<function run_task at 0x10339fe20>
好的人傻了,原来是因为@delay这个写法,如果不加()会把接下来要运行的函数作为参数传入。
可以理解为当写@delay
的时候,第一个参数实际上是def run_task
而当写@delay()
或者 delay(1)
或者 delay(timeout=2)
的时候,才是按照我们声明的顺序来的
如何解决
我的解决方案是:
python
import time
from typing import Callable, Optional
def delay(fn: Optional[Callable] = None, /, *, timeout: int = 1):
"""
:param fn:
:param timeout:
:return:
"""
def inner_func(func):
def execute(*args, **kwargs):
time.sleep(timeout)
return func(*args, **kwargs)
return execute
if callable(fn):
return inner_func(fn)
if fn is not None:
raise Exception("装饰器的参数必须以key-value的形式进行传递")
return inner_func
@delay
def run_task():
print("finished")
run_task()
既然有参装饰器的第一个参数是可变的,我干脆不用第一个值了,就直接用fn作为位置参数给他占着。 并且fn后面的参数必须声明为k-v的形式(虽然说写的时候稍微麻烦了一点,但是阅读方便啊)
那么就有两种case:
- 如果fn是一个函数,那么说明是用
@delay
方式声明的,fn肯定就是待执行的方法,那么直接把fn传给inner_func
即可 - 如果fn不是函数,说明就是正常的装饰器,就按照正常的装饰器声明即可。