2017-07-07-python-装钸器
1 装钸器
在python中,函数是对象。可以进行赋值等操作:
# -*- coding: utf-8; -*- def hi(name="yasoob"): return "hi " + name print(hi()) # output: 'hi yasoob' # 我们甚至可以将一个函数赋值给一个变量,比如 greet = hi # 我们这里没有在使用小括号,因为我们并不是在调用hi函数 # 而是在将它放在greet变量里头。我们尝试运行下这个 print(greet()) # output: 'hi yasoob' # 如果我们删掉旧的hi函数,看看会发生什么! del hi print(hi()) #outputs: NameError print(greet()) #outputs: 'hi yasoob'
函数中可以定义函数:
def out_fun(): def inter_fun1(): print 'in inter_fun1' def inter_fun2(): print 'in inter_fun2' print 'begin out_fun' inter_fun1() inter_fun2() print 'end_out_fun' out_fun()
内部的 inter_fun1
和 inter_fun2
在外部是不可见的。
函数也可以返回函数:
def re_fun(): print 'in re_fun' def hi(): print 'hi this is the returned function' return 'this is return value' return hi a = re_fun() print a()
函数也可以做为参数传递给另一个函数:
def a(fun): print 'a will call function %s' % fun.__name__ fun() def hi(): print 'hi' def hello(): print 'hello,world' a(hi) a(hello)
装钸器就是让你能在你的函数的前后去执行代码:
def a_new_decorator(a_func): def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction def a_function_requiring_decoration(): print("I am the function which needs some decoration to remove my foul smell") a_function_requiring_decoration() #outputs: "I am the function which needs some decoration to remove my foul smell" a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration) #now a_function_requiring_decoration is wrapped by wrapTheFunction() a_function_requiring_decoration() #outputs:I am doing some boring work before executing a_func() # I am the function which needs some decoration to remove my foul smell # I am doing some boring work after executing a_func()
如果我们按照python中的语法来写的话,这个装钸器应该是这样的:
def a_new_decorator(a_func): def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): """Hey you! Decorate me!""" print("I am the function which needs some decoration to " "remove my foul smell") a_function_requiring_decoration() #outputs: I am doing some boring work before executing a_func() # I am the function which needs some decoration to remove my foul smell # I am doing some boring work after executing a_func() #the @a_new_decorator is just a short way of saying: a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
但是这样会有一个问题:
def a_new_decorator(a_func): def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): """Hey you! Decorate me!""" print("I am the function which needs some decoration to " "remove my foul smell") a_function_requiring_decoration() a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration) print a_function_requiring_decoration.__name__
最后函数的 __name__
属性被覆盖了。这并不是我们想要的。python中有一
个 functools.wraps
可以用来解决这个问题,他可以保留被装钸的函数的
__name__
和 __doc__
属性。
from functools import wraps def a_new_decorator(a_func): @wraps(a_func) def wrapTheFunction(): print("I am doing some boring work before executing a_func()") a_func() print("I am doing some boring work after executing a_func()") return wrapTheFunction @a_new_decorator def a_function_requiring_decoration(): """Hey yo! Decorate me!""" print("I am the function which needs some decoration to " "remove my foul smell") print(a_function_requiring_decoration.__name__) # Output: a_function_requiring_decoration
web应用认证的时候常用装钸器来这样写:
from functools import wraps def requires_auth(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization if not auth or not check_auth(auth.username, auth.password): authenticate() return f(*args, **kwargs) return decorated
记录日志也是常使用的:
from functools import wraps def logit(func): @wraps(func) def with_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging @logit def addition_func(x): """Do some math.""" return x + x result = addition_func(4) # Output: addition_func was called
如果需要定义带参数的装钸器:
from functools import wraps def logit(logfile='out.log'): def logging_decorator(func): @wraps(func) def wrapped_function(*args, **kwargs): log_string = func.__name__ + " was called" print(log_string) # 打开logfile,并写入内容 with open(logfile, 'a') as opened_file: # 现在将日志打到指定的logfile opened_file.write(log_string + '\n') return func(*args, **kwargs) return wrapped_function return logging_decorator @logit() def myfunc1(): pass myfunc1() # Output: myfunc1 was called # 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串 @logit(logfile='func2.log') def myfunc2(): pass myfunc2() # Output: myfunc2 was called # 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串
可以类似于这样来理解:
logit(logfile='out.log')(myfunc1) logit(logfile='func2.log')(myfunc2)
函数式编程,函数是第一类数据,可以做为参数传递,也可以做为返回值。所 以这种带参数的装钸器执行就是类似于上面这样。
装钸器还可以实现为一个类,这就更灵活啦。比如上面的记录日志的同时,还 想再发邮件给管理员。
from functools import wraps class clogit(object): """Documentation for clogit """ def __init__(self, logfile): super(clogit, self).__init__() self.logfile = logfile def __call__(self, func): @wraps(func) def wrapper(*args, **kargs): logstring = 'function %s is called' % func.__name__ func(*args, **kargs) with open(self.logfile, 'wb') as f: f.write(logstring) self.notify(logstring) return wrapper def notify(self, s): # clogit not notify pass @clogit('/tmp/logit') def function_test(): print 'this is function' function_test()
这个感觉有点像:
clogit('/tmp/logit').__call__(function_test)
它比 嵌套函数的方式 更整洁一些。而且扩展也很方便啦,现在我们可以定义 一个可以发邮件的子类:
from functools import wraps class clogit(object): """Documentation for clogit """ def __init__(self, logfile='/tmp/logit'): super(clogit, self).__init__() self.logfile = logfile def __call__(self, func): @wraps(func) def wrapper(*args, **kargs): logstring = 'function %s is called' % func.__name__ func(*args, **kargs) with open(self.logfile, 'wb') as f: f.write(logstring) self.notify(logstring) return wrapper def notify(self, s): # clogit not notify pass class email_andlogit(clogit): """Documentation for email_andlogit """ def __init__(self, emailaddress='xxx@gmail.com', *args, **kargs): super(email_andlogit, self).__init__(*args, **kargs) self.emailaddress = emailaddress def notify(self, s): print 'I will email %s to %s' % (s, self.emailaddress) # send email here @email_andlogit('peng@gmail.com', '/tmp/emaillogit') def test_email(): print 'this is function' test_email()
这样, email_andlogit
不仅会写入日志,还会发送一封邮件给管理员啦。
最后,有一个问题:再思考一下能否写出一个 @log
的 decorator
,使
它既支持:
@log def f(): pass
又支持
@log('execute') def f(): pass
下面是解法,有点复杂,需要慢慢理解下。
# -*- coding: utf-8; -*- import functools def log(args): ''' 函数Log装饰器 ''' desc = 'Just call!' def decoration(func): ''' 装饰器 ''' @functools.wraps(func) def wrapper(*K, **KM): ''' 执行装饰器参数函数 ''' print '[+] Function Execute: ' + func.__name__ + ' (' + desc + ')' result = func(*K, **KM) print '[+] Function Finished: ' + func.__name__ + ' (' + desc + ')' return result return wrapper if hasattr(args, '__call__'): return decoration(args) else: desc = args return decoration @log def fun_log(): print 'fun_log' @log('[test]') def fun_test(): print 'fun_test' fun_log() print '================' fun_test()
我理解 @log('tet')
这种形式的时候,参数 tet
是放到了一个闭包里面
的。直到函数调用结束,闭包才释放掉。