1. 介绍
decorator是用来在代码运行期间动态增加功能的,本质上是一个返回函数的高阶函数。假设现在有这样一种需求,即在每个函数调用前记录日志,记录被调用的函数名称,可以这样实现:
def log(func):
def wrapper(*args, **kwargs):
print "CALL %s()" % func.__name__
return func(*args, **kwargs)
return wrapper
def sayHi():
print "Hi, Buddy."
def sayHello():
print "Hello, Buddy."
# 调用函数,记录日志
log(sayHi)()
# 输出为
# CALL sayHi()
# Hi, Buddy.
log(sayHello)()
# 输出为
# CALL sayHello()
# Hello, Buddy.
这种方法确实实现了记录日志的功能,但每次这么调用未免太过繁琐,decorator因此出现。
2. 使用
其实,之前定义的log
函数即为一个decorator,只是使用方式不正确:
@log
def sayHi():
print "Hi, Buddy."
sayHi()
# 输出为
# CALL sayHi()
# Hi, Buddy.
@log
def sayHello():
print "Hello, Buddy."
sayHello()
# 输出为
# CALL sayHello()
# Hello, Buddy.
可以看到,使用decorator非常简单方便,def sayHi():
前的@log
相当于将sayHi
作为参数传入log
函数中,并将返回值赋给sayHi
,即:
sayHi = log(sayHi)
但是细心的读者不难发现,这样一来函数sayHi
的__name__
属性发生变化,由之前的sayHi
变为wrapper
,使用python内置的functools.wraps
方法可以解决这一问题,改造后的decorator如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print "CALL %s()" % func.__name__
return func(*args, **kwargs)
return wrapper
让我们更进一步,使用三层嵌套的decorator,允许再多传入一次参数:
import functools
def log(text="CALL")
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print "%s %s()" % (text, func.__name__)
return func(*args, **kwargs)
return wrapper
return decorator
这次我们可以选择传入一个表示函数运行状态的字符串,由于多了一层嵌套,使用时也会有些变化:
@log("EXECUTE")
def sayHi():
print "Hi, Buddy."
sayHi()
# 输出为
# EXECUTE sayHi()
# Hi, Buddy.
修改过后嵌套使用为:
sayHi = log("EXECUTE")(sayHi)
3. 拓展
3.1
修改log
函数,使该decorator既可以通过
@log
使用,又可以通过
@log("EXECUTE")
使用:
import functools
def log(text="CALL"):
if callable(text):
func = text
@functools.wraps(func)
def wrapper(*args, **kwargs):
print "CALL %s()" % func.__name__
return func(*args, **kwargs)
else:
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print "%s %s()" % (text, func.__name__)
return func(*args, **kwargs)
return wrapper
return decorator
两种使用方式:
@log
def sayHi():
print "Hi, Buddy."
sayHi()
# 输出为
# CALL sayHi()
# Hi, Buddy.
@log("EXECUTE")
def sayHello():
print "Hello, Buddy."
sayHello()
# 输出为
# EXECUTE sayHello()
# Hello, Buddy.
3.2
修改log
函数,使该decorator在函数调用前及函数调用后分别输出一条日志:
import functools
def log(func):
def wrapper(*args, **kwargs):
print "CALL BEGINNING"
call = func(*args, **kwargs)
print "CALL ENDING"
return call
return wrapper
使用该decorator:
@log
def sayHi():
print "Hi, Buddy."
sayHi()
# 输出为
# CALL BEGINING
# Hi, Buddy.
# CALL ENDING
参考资料:
廖雪峰的官方网站