在廖大的装饰器这一章节的task,遇到了这样的问题
请编写一个decorator,能在函数调用的前后打印出'begin call'和'end call'的日志。
再思考一下能否写出一个@log的decorator,使它既支持:
@log
def f():
pass
又支持:
@log('execute')
def f():
pass
一开始我是这样写的:
import functools
def log(text = ''): #我希望用一个默认参数来解决 @log 的情况
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log
def now():
print("---now----")
now()
@log('exec')
def now():
print("---now----")
now()
然后build后的信息:
Traceback (most recent call last):
File "F:\py\testttt.py", line 34, in <module>
now()
TypeError: decorator() missing 1 required positional argument: 'func'`
原因分析:
由于 log(text)的返回值是一个函数 decorator(func), 装饰器的写法
@log
def now():
等价于
now = log(now)
所以 now 被当作 text的实参传入, 而decorator(func)的形参func并无对应的形参,所以提示decorator()缺少一个位置参数
于是,若采用如下写法:
@log()
def now():
print("---now----")
now()
结果:
now(): #前面有一个空格
---now----
当然这样与task的要求不符合了
也就是说,我必须要在log(text)中判断传进来的text到底是函数类型还是其他的类型(字符串,整型等),而不能一劳永逸,因为它log()是一个三层的嵌套函数,参数的对应位置是会造成影响的
改进后如下
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
if isinstance(text,(str, int, float)):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
else:
print("begin call %s()" % func.__name__)
func(*args, **kw)
print("end call %s()" % func.__name__)
return wrapper
if isinstance(text,(str, int, float)):
return decorator
else:
return decorator(text) #若text是函数类型的参数,则将它再次传
#入decorator(func) 作为func的实参
@log
def f():
print("---here1----")
f()
print("")
@log('exec')
def f():
print("---here2----")
f()
结果:
begin call f()
---here1----
end call f()
exec f():
---here2----