Python 如何实现一个装饰器
首先,装饰器为调用高阶函数提供了一种简单的语法 根据定义,装饰器是一个函数,它接受另外一个函数并在其基础功能上做统一的扩展
闭包
对于 Python 来说,一切皆对象,对象也是可以作为参数传递,这就是装饰器实现的基础
例如,我想实现一个日志打印
功能,调用对应方法或者对象时,可以显示其运行的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 def printer (func ): print (f"func {func.__name__} running" , ) func() print (f"func {func.__name__} ended" ) def task (): print ("task run" ) if __name__ == '__main__' : printer(task)
在这里我们实现了task
方法,在调用这个方法的时候,会显示调用的相关信息。
可以看到我们使用printer
这个功能的时候,都是采用printer(func)
的方式,如果在多处使用的话, 那么有没有比较简单的方式,例如在执行task
方法的时候,自动去触发printer
呢,还能像调用原方法一样?
于是, 我们可以将该方法当成一个参数传递后,返回一个新的对象调用的方式:
1 2 3 4 5 6 7 8 9 10 11 12 def printer (func ): def inner (): print (f"func {func.__name__} running" , ) func() print (f"func {func.__name__} ended" ) return inner def task (): print ("task run" ) task = printer(task) task()
Python 中允许在一个方法中嵌套另外一个方法
,这种特殊的机制就是闭包 。 这个内部方法可以保留外部方法的作用域,内部方法也可以访问到外部方法的参数和变量。
装饰器
python 支持一种装饰器语法糖「@」,使用这个语法糖,我们也可以实现与上面完全相同的功能
1 2 3 @printer def task (): print ("task run " )
现在我们已经知道,装饰器其实就是先定义好一个闭包,然后使用语法糖@
来装饰方法,最后达到重新定义方法的作用 但是这样使用的方法其实最终执行的是 inner
方法
like:
1 2 3 4 5 6 7 @printer def task (): print ("task run " ) print (task.__name__)
如何解决这个问题?
python 其实有内置的 functools 模块,提供了一个 wraps
方法,专门来解决这个问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from functools import wrapsdef logger (func ): @wraps(func ) def inner (): print (f"func {func.__name__} running" , ) func() print (f"func {func.__name__} ended" ) return inner @logger def task (): print ("task run " ) print (task.__name__)
带参数的装饰器 装饰器当然也可以传入参数,例如:*args
、**kwargs
例如使用装饰器来计算程序的运行时间:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import timefrom functools import wrapsdef timeit (prefix ): def decorator (func ): @wraps(func ) def wrapper (*args, **kwargs ): start = time.time() func(*args, **kwargs) end = time.time() print ('%s: duration time: %ds' % (prefix, int (end - start))) return wrapper return decorator @timeit('prefix1' ) def hello (name ): time.sleep(1 ) print ('hello %s' % name)
装饰器模版 1 2 3 4 5 6 7 8 from functools import wrapsdef decorator (func ): @wraps(func ) def wrapper (*args, **kwargs ): value = func(*args, **kwargs) return value return wrapper
类实现装饰器 __call__
魔法方法结合类也可以实现装饰器
这里也给出一个通用模版
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import functoolsclass Decorator (object ): def __int__ (self, params ): self.params = params def __call__ (self, func ): @functools.wraps(func ) def wrapper (*args, **kwargs ): func(*args, **kwargs) return wrapper @Decorator('params' ) def hello (): print ("hello" ) hello()
装饰器的使用场景 记录调用日志 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 from functools import wrapsimport loggingclass Logger (object ): def __init__ (self, name ): self.name = name def __call__ (self, func ): @wraps(func ) def wrapper (*args, **kwargs ): logging.getLogger().setLevel(logging.INFO) logging.info('call method: %s %s %s' , func.__name__, args, kwargs) return func(*args, **kwargs) return wrapper @Logger('airflow' ) def task (name ): print (f"run {name} " ) if __name__ == '__main__' : task(name="task" )
路由映射 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from functools import wrapsimport loggingimport flaskclass Router (object ): def __init__ (self ): self.url_map = {} def register (self, url ): def wrapper (func ): self.url_map[url] = func return wrapper def call (self, url ): func = self.url_map.get(url) if not func: raise ValueError('No url function: %s' , url) return func() router = Router() @router.register('/page1' ) def page1 (): return "page1" print (router.call('page1' ))