Python| decorator 学习
Python decorator 学习 - Windows Live
November 09
Python decorator 学习
Python 程序一向简洁易读。不过也有例外,这两天 decorator 的语法就让我晕了一阵。不带参数时还好些,带参数的 decorator 定义要三层函数嵌套,网上的例子也比较少,自己想的时候是吃了点苦头。不过真正搞明白了也发现就那么回事。恩,记下此例以备忘:
目标描述:
写一个 decorator,使函数调用时能够打印被调用的次数和传给他的参数;且这两个功能由传给 decorator 的两个参数作为开关。
第一步:为目标函数func做第一层封装。
def newfunc(*kwds,**dic):
#do something before the call of func
if dbg_count:
cnt[0] += 1
print "Function %s() calling count= %s" % (func.__name__, cnt[0])
if dbg_arg:
print "Args:\t",kwds,dic
val = func(*kwds,**dic)
#do something after the call of func
#log.write( str(val) + os.linesep )
return val
*kwds 和 **dic 是要传给函数 func 的参数,我们在 newfunc 中把他们截获,做一点准备工作后再调用 func 。dbg_count 和 dbg_arg 是 decorator 接收的两个参数,分别作为是否打印对应信息的开关。cnt 是只有一个元素的 list , 用它的 0 号元素储存函数的调用次数,后面再具体分析。
#do something before the call of func
if dbg_count:
cnt[0] += 1
print "Function %s() calling count= %s" % (func.__name__, cnt[0])
if dbg_arg:
print "Args:\t",kwds,dic
val = func(*kwds,**dic)
#do something after the call of func
#log.write( str(val) + os.linesep )
return val
*kwds 和 **dic 是要传给函数 func 的参数,我们在 newfunc 中把他们截获,做一点准备工作后再调用 func 。dbg_count 和 dbg_arg 是 decorator 接收的两个参数,分别作为是否打印对应信息的开关。cnt 是只有一个元素的 list , 用它的 0 号元素储存函数的调用次数,后面再具体分析。
第二步:为 newfunc 做一层封装,将他作为替代 func 的函数返回。
def loadf(func):
cnt = [0]
def newfunc(*kwds,**dic):
#do something before the call of func
if dbg_count:
cnt[0] += 1
print "Function %s() calling count= %s" % (func.__name__, cnt[0])
if dbg_arg:
print "Args:\t",kwds,dic
val = func(*kwds,**dic)
#do something after the call of func
#log.write( str(val) + os.linesep )
return val
return newfunc
与第一步相比只增加了三行,完成两件事:(1)返回刚刚定义的 newfunc 函数,作为 func 的替代;(2)初始化函数调用计数器 cnt 。第一件事比较好理解,返回 newfunc 函数后,如果不考虑那两个控制参数,loadf 已经可以当成一个不含参数的 decorator 来使用了。关于第二件事,这里实际上 cnt[0] 是作为函数 newfunc 的一个静态变量来使用的。作为 c/c++ 程序员,当遇到给函数添加调用计数器时自然想到用静态变量来储存,但在 Python 中没有明确的 静态变量 声明语法,该如何保存上次函数调用后的一些值呢——嵌套函数,这是 Python 中的方法 (或许有人会说yield 或 Generator,那个虽然我也试过,但用作静态变量时很不自然,暂不讨论)。外层函数中定义的 list或dict 变量,在内层函数中一直有效且多次调用内层函数时不会重复初始化,完全就是静态变量的翻版嘛...这里还有个疑问,既然只用保存一个量,为何不用一个 int 型的 cnt ,而要麻烦的动用 list 呢。嘛,懒得写了,试过你就知道了。
cnt = [0]
def newfunc(*kwds,**dic):
#do something before the call of func
if dbg_count:
cnt[0] += 1
print "Function %s() calling count= %s" % (func.__name__, cnt[0])
if dbg_arg:
print "Args:\t",kwds,dic
val = func(*kwds,**dic)
#do something after the call of func
#log.write( str(val) + os.linesep )
return val
return newfunc
与第一步相比只增加了三行,完成两件事:(1)返回刚刚定义的 newfunc 函数,作为 func 的替代;(2)初始化函数调用计数器 cnt 。第一件事比较好理解,返回 newfunc 函数后,如果不考虑那两个控制参数,loadf 已经可以当成一个不含参数的 decorator 来使用了。关于第二件事,这里实际上 cnt[0] 是作为函数 newfunc 的一个静态变量来使用的。作为 c/c++ 程序员,当遇到给函数添加调用计数器时自然想到用静态变量来储存,但在 Python 中没有明确的 静态变量 声明语法,该如何保存上次函数调用后的一些值呢——嵌套函数,这是 Python 中的方法 (或许有人会说yield 或 Generator,那个虽然我也试过,但用作静态变量时很不自然,暂不讨论)。外层函数中定义的 list或dict 变量,在内层函数中一直有效且多次调用内层函数时不会重复初始化,完全就是静态变量的翻版嘛...这里还有个疑问,既然只用保存一个量,为何不用一个 int 型的 cnt ,而要麻烦的动用 list 呢。嘛,懒得写了,试过你就知道了。
第三步:最后一层封装,用来接收那两个控制参数。下面是最终版
def dbg_config(dbg_count=True, dbg_arg=True):
def loadf(func):
cnt = [0]
def newfunc(*kwds,**dic):
#do something before the call of real function
if dbg_count:
cnt[0] += 1
print "Function %s() calling count= %s" % (func.__name__, cnt[0])
if dbg_arg:
print "Args:\t",kwds,dic
val = func(*kwds,**dic)
#do something after the call of real function
#log.write( str(val) + os.linesep )
return val
return newfunc
return loadf
def loadf(func):
cnt = [0]
def newfunc(*kwds,**dic):
#do something before the call of real function
if dbg_count:
cnt[0] += 1
print "Function %s() calling count= %s" % (func.__name__, cnt[0])
if dbg_arg:
print "Args:\t",kwds,dic
val = func(*kwds,**dic)
#do something after the call of real function
#log.write( str(val) + os.linesep )
return val
return newfunc
return loadf
做的事情很简单,接收参数,最后返回 loadf 。来分析一下如何起作用的:
@dbg_config(True,True)
def func(a,b):pass
调用 func(1,2) 时,展开后就是
dbg_config(True,True)(func)(1,2)
由于 dbg_config(True,True) 返回的是 loadf ,于是上面继续展开,得到
loadf(func)(1,2)
而 loadf(func) 又返回 newfunc ,变成
newfunc(1,2)
就把对 func 的调用,通过两层封装,变成了对 newfunc 的调用。newfunc 做点补充工作,最后在内部调用了 func
上面,由于 decorator 要接收参数,所以得三层封装。若不带参数时只用两层即可。
最后看下使用情况:
>>> @dbg_config(dbg_arg=False)
... def foo():pass
...
>>> foo()
Function foo() calling count= 1
>>> foo()
Function foo() calling count= 2
>>> foo()
Function foo() calling count= 3
... def foo():pass
...
>>> foo()
Function foo() calling count= 1
>>> foo()
Function foo() calling count= 2
>>> foo()
Function foo() calling count= 3
通过将 dbg_arg 赋值为 False,关闭打印参数的功能
>>> @dbg_config()
... def add(a,b,c):
... return a+b+c
...
>>> print add(1,2,3)
Function add() calling count= 1
Args: (1, 2, 3) {}
6
>>> print add(3,c=2,b=5)
Function add() calling count= 2
Args: (3,) {'c': 2, 'b': 5}
10
... def add(a,b,c):
... return a+b+c
...
>>> print add(1,2,3)
Function add() calling count= 1
Args: (1, 2, 3) {}
6
>>> print add(3,c=2,b=5)
Function add() calling count= 2
Args: (3,) {'c': 2, 'b': 5}
10
两个功能都打开(默认),打印了调用次数,也打印了参数,最后输出运行结果。
应该都比较清楚了。写 decorator 的时候有个 functools 模块好像有工具可以用;另外 yield 从 2.4 版开始可以作为表达式而不是一个语句来使用,灵活性大增,可以搞些复杂的用法了,以后有空再来分析吧。
No comments:
Post a Comment