在Python中,装潢器平常用来润饰函数,完成大众功用,到达代码复用的目标。在函数定义前加上@xxxx,然后函数就注入了某些行动,很奇异!然则,这只是语法糖罢了。
场景
假定,有一些事变函数,用来对数据做差别的处置惩罚:
def work_bar(data): pass def work_foo(data): pass
我们想在函数挪用前/后输出日记,怎么办?
<!--more-->
傻瓜解法
logging.info('begin call work_bar') work_bar(1) logging.info('call work_bar done')
假如有多处代码挪用呢?想一想就怕!
函数包装
傻瓜解法无非是有太多代码冗余,每次函数挪用都要写一遍logging
。能够把这部份冗余逻辑封装到一个新函数里:
def smart_work_bar(data): logging.info('begin call: work_bar') work_bar(data) logging.info('call doen: work_bar')
如许,每次挪用smart_work_bar
即可:
smart_work_bar(1) # ... smart_work_bar(some_data)
通用闭包
看上去挺圆满……然则,当work_foo
也有一样的须要时,还要再完成一遍smart_work_foo
吗?如许明显不科学呀!
别急,我们能够用闭包:
def log_call(func): def proxy(*args, **kwargs): logging.info('begin call: {name}'.format(name=func.func_name)) result = func(*args, **kwargs) logging.info('call done: {name}'.format(name=func.func_name)) return result return proxy
这个函数吸收一个函数对象(被代办函数)作为参数,返回一个代办函数。挪用代办函数时,先输出日记,然后挪用被代办函数,挪用完成后再输出日记,末了返回挪用效果。如许,不就到达通用化的目标了吗?——关于恣意被代办函数func
,log_call
都可轻松应对。
smart_work_bar = log_call(work_bar) smart_work_foo = log_call(work_foo) smart_work_bar(1) smart_work_foo(1) # ... smart_work_bar(some_data) smart_work_foo(some_data)
第1
行中,log_call
吸收参数work_bar
,返回一个代办函数proxy
,并赋给smart_work_bar
。第4
行中,挪用smart_work_bar
,也就是代办函数proxy
,先输出日记,然后挪用func
也就是work_bar
,末了再输出日记。注重到,代办函数中,func
与传进去的work_bar
对象牢牢关联在一起了,这就是闭包。
再提一下,能够掩盖被代办函数名,以smart_
为前缀取新名字照样显得有些累坠:
work_bar = log_call(work_bar) work_foo = log_call(work_foo) work_bar(1) work_foo(1)
语法糖
先来看看以下代码:
def work_bar(data): pass work_bar = log_call(work_bar) def work_foo(data): pass work_foo = log_call(work_foo)
虽然代码没有什么冗余了,然则看是去照样不够直观。这时候候,语法糖来了~~~
@log_call def work_bar(data): pass
因而,注重一点(划重点啦),这里@log_call
的作用只是:通知Python
编译器插进去代码work_bar = log_call(work_bar)
。
求值装潢器
先来猜猜装潢器eval_now
有什么作用?
def eval_now(func): return func()
看上去好新鲜哦,没有定义代办函数,算装潢器吗?
@eval_now def foo(): return 1 print foo
这段代码输出1
,也就是对函数举行挪用求值。那末到底有什么用呢?直接写foo = 1
不可么?在这个简朴的例子,这么写固然能够啦。来看一个更庞杂的例子——初始化一个日记对象:
# some other code before... # log format formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) # again some other code after...
用eval_now
的体式格局:
# some other code before... @eval_now def logger(): # log format formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) return logger # again some other code after...
两段代码要到达的目标是一样的,然则后者明显更清楚,很有代码块的风仪。更主要的是,函数挪用在部分名字空间完成初始化,防止暂时变量(如formatter
等)污染外部的名字空间(比方全局)。
带参数装潢器
定义一个装潢器,用于纪录慢函数挪用:
def log_slow_call(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > 1: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
第3
、5
行分别在函数挪用前后采样当前时候,第7
行盘算挪用耗时,耗时大于一秒输出一条正告日记。
@log_slow_call def sleep_seconds(seconds): time.sleep(seconds) sleep_seconds(0.1) # 没有日记输出 sleep_seconds(2) # 输出正告日记
然则,阈值设置老是要视状况决议,差别的函数能够会设置差别的值。假如阈值有要领参数化就好了:
def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
然则,@xxxx
语法糖老是以被装潢函数为参数挪用装潢器,也就是说没有机会通报threshold
参数。怎么办呢?——用一个闭包封装threshold
参数:
def log_slow_call(threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy return decorator @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
如许,log_slow_call(threshold=0.5)
挪用返回函数decorator
,函数具有闭包变量threshold
,值为0.5
。decorator
再装潢sleep_seconds
。
采纳默许阈值,函数挪用照样不能省略:
@log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
处女座能够会对第一行这对括号觉得不爽,那末能够如许革新:
def log_slow_call(func=None, threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy if func is None: return decorator else: return decorator(func)
这类写法兼容两种差别的用法,用法A
默许阈值(无挪用);用法B
自定义阈值(有挪用)。
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds) # Case B @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
用法A
中,发作的事变是log_slow_call(sleep_seconds)
,也就是func
参数黑白空的,这是直接调decorator
举行包装并返回(阈值是默许的)。
用法B
中,先发作的是log_slow_call(threshold=0.5)
,func
参数为空,直接返回新的装潢器decorator
,关联闭包变量threshold
,值为0.5
;然后,decorator
再装潢函数sleep_seconds
,即decorator(sleep_seconds)
。注重到,此时threshold
关联的值是0.5
,完成定制化。
你能够注重到了,这里最好运用关键字参数这类挪用体式格局——运用位置参数会很貌寝:
# Case B- @log_slow_call(None, 0.5) def sleep_seconds(seconds): time.sleep(seconds)
固然了,函数挪用只管运用关键字参数是一种极佳实践,寄义清楚,在参数许多的状况下更是如此。
智能装潢器
上节引见的写法,嵌套条理较多,假如每一个相似的装潢器都用这类要领完成,照样比较费力的(头脑不够用),也比较轻易失足。
假定有一个智能装潢器smart_decorator
,润饰装潢器log_slow_call
,便可取得一样的才能。如许,log_slow_call
定义将变得更清楚,完成起来也更省力啦:
@smart_decorator def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
脑洞开完,smart_decorator
怎样完成呢?实在也简朴:
def smart_decorator(decorator): def decorator_proxy(func=None, **kwargs): if func is not None: return decorator(func=func, **kwargs) def decorator_proxy(func): return decorator(func=func, **kwargs) return decorator_proxy return decorator_proxy
smart_decorator
完成了今后,想象就成立了!这时候,log_slow_call
,就是decorator_proxy
(外层),关联的闭包变量decorator
是本节最最先定义的log_slow_call
(为了防止歧义,称为real_log_slow_call
)。log_slow_call
支撑以下种种用法:
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds)
用法A
中,实行的是decorator_proxy(sleep_seconds)
(外层),func
非空,kwargs
为空;直接实行decorator(func=func, **kwargs)
,即real_log_slow_call(sleep_seconds)
,效果是关联默许参数的proxy
。
# Case B # Same to Case A @log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
用法B
中,先实行decorator_proxy()
,func
及kwargs
均为空,返回decorator_proxy
对象(内层);再实行decorator_proxy(sleep_seconds)
(内层);末了实行decorator(func, **kwargs)
,等价于real_log_slow_call(sleep_seconds)
,效果与用法A
一致。
# Case C @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
用法C
中,先实行decorator_proxy(threshold=0.5)
,func
为空但kwargs
非空,返回decorator_proxy
对象(内层);再实行decorator_proxy(sleep_seconds)
(内层);末了实行decorator(sleep_seconds, **kwargs)
,等价于real_log_slow_call(sleep_seconds, threshold=0.5)
,阈值完成自定义!
以上就是Python装潢器的细致用法引见(代码示例)的细致内容,更多请关注ki4网别的相干文章!