Python 装饰器用法
@dec def func(): pass
装饰器(Decorator)为装饰对象的器件,既可以装饰[函数](# 1. 对带参数的函数进行装饰),又可以装饰[类](# 6. 类装饰器)。可以在不修改代码条件下,为装饰对象添加新的功能或者帮助输出。装饰器的典型应用场景有类型检查、用户验证、输入合理性检查、输出格式化、异常捕获、日志管理。
为什么要用装饰器
在官方文档中,Python 2.4 引入的decorators
, 具有重要优势:
- decorators help reducing boilerplate code; 减少样板代码
- decorators help separation of concerns; 分离关注点
- decorators enhance readability and maintenability; 增强可读性和可维护性
- decorators are explicit. 显示的装饰器
-
michele.simionato 开发的
decorator
模块,简化了decorators
的使用,并附上了许多经典案例。 -
Python 装饰器用法实例总结第二节有简单的例子,解释符合开放封闭原则,降低多函数重复代码量和频繁改动,将非核心的日志记录功能和函数分离开,有利于保证核心业务代码的简洁和易理解性。
-
Python 装饰器 中经过用户验证场景不同解决方案的更迭来说明装饰器的优势。
使用装饰器@outer,也是仅需对基础平台的代码进行拓展,就可以实现在其他部门调用函数 API 之前都进行认证操作,在操作结束后保存日志,并且其他业务部门无需对他们自己的代码做任何修改,调用方式也不用变。
装饰器机制分析
本节摘自Python 装饰器 ,在其基础上补充更为直观的图示和流程图说明,文字描述可以详细查看原文章。
def outer(func): def inner(): print("认证成功!") result = func() print("日志添加成功") return result return inner @outer def f1(): print("业务部门1数据接口......") r = f1()


# 验证 inner 函数返回给 f1,即 f1 对象内存指向 inner内存 def outer(func): print("传入实参内存地址:%d" %id(func)) def inner(): print("认证成功!") result = func() print("日志添加成功") return result print("Outer 内部函数 inner 内存地址:%d" %id(inner)) return inner @outer def f1(): print("业务部门1数据接口......") print("装饰后 f1 函数内存地址:%d" %id(f1)) r = f1() >>> 输出结果 传入实参内存地址:32354376 Outer 内部函数 inner 内存地址:32372632 装饰后 f1 函数内存地址:32372632 认证成功! 业务部门1数据接口...... 日志添加成功
装饰器入门
本节摘自Python装饰器用法实例总结
1. 对带参数的函数进行装饰
内层函数进行改动传入我们的两个参数a和b,等价于use_logging(bar)(1,2)
def use_logging(func): def _deco(a,b): print("%s is running" % func.__name__) func(a,b) return _deco @use_logging def bar(a,b): print('i am bar:%s'%(a+b)) bar(1,2)
2. 函数参数数量不确定
使用python的变长参数 *args和**kwargs 解决不定参数问题
def use_logging(func): def _deco(*args,**kwargs): print("%s is running" % func.__name__) func(*args,**kwargs) return _deco @use_logging def bar(a,b): print('i am bar:%s'%(a+b)) @use_logging def foo(a,b,c): print('i am bar:%s'%(a+b+c)) bar(1,2) foo(1,2,3)
3. 装饰器带参数
#! /usr/bin/env python # -*- coding:utf-8 -*- # __author__ = "TKQ" def use_logging(level): def _deco(func): def __deco(*args, **kwargs): if level == "warn": print "%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging(level="warn") def bar(a,b): print('i am bar:%s'%(a+b)) bar(1,3) # 等价于use_logging(level="warn")(bar)(1,3)
4. functools.wraps - 还原函数原信息
使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring、name、参数列表
def use_logging(func): def _deco(*args,**kwargs): print("%s is running" % func.__name__) func(*args,**kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() #bar is running #i am bar #_deco #函数名变为_deco而不是bar,这个情况在使用反射的特性的时候就会造成问题。因此引入了functools.wraps解决这个问题。
# 使用functools.wraps: import functools def use_logging(func): @functools.wraps(func) def _deco(*args,**kwargs): print("%s is running" % func.__name__) func(*args,**kwargs) return _deco @use_logging def bar(): print('i am bar') print(bar.__name__) bar() #result: #bar is running #i am bar #bar ,这个结果是我们想要的。OK啦!
5. 实现带参数和不带参数的装饰器自适应
import functools def use_logging(arg): if callable(arg):#判断参入的参数是否是函数,不带参数的装饰器调用这个分支 @functools.wraps(arg) def _deco(*args,**kwargs): print("%s is running" % arg.__name__) arg(*args,**kwargs) return _deco else:#带参数的装饰器调用这个分支 def _deco(func): @functools.wraps(func) def __deco(*args, **kwargs): if arg == "warn": print "warn%s is running" % func.__name__ return func(*args, **kwargs) return __deco return _deco @use_logging("warn") # @use_logging def bar(): print('i am bar') print(bar.__name__) bar()
6. 类装饰器
使用类装饰器可以实现带参数装饰器的效果,但实现的更加优雅简洁,而且可以通过继承来灵活的扩展.
class loging(object): def __init__(self,level="warn"): self.level = level def __call__(self,func): @functools.wraps(func) def _deco(*args, **kwargs): if self.level == "warn": self.notify(func) return func(*args, **kwargs) return _deco def notify(self,func): # logit只打日志,不做别的 print "%s is running" % func.__name__ @loging(level="warn") #执行__call__方法 def bar(a,b): print('i am bar:%s'%(a+b)) bar(1,3)
继承扩展类装饰器
class email_loging(Loging): ''' 一个loging的实现版本,可以在函数调用时发送email给管理员 __init__ :不再接收被装饰函数,而是接收传入参数。 __call__ :接收被装饰函数,实现装饰逻辑。 ''' def __init__(self, email='admin@myproject.com', *args, **kwargs): self.email = email super(email_loging, self).__init__(*args, **kwargs) def notify(self,func): # 发送一封email到self.email print "%s is running" % func.__name__ print "sending email to %s" %self.email @email_loging(level="warn") def bar(a,b): print('i am bar:%s'%(a+b)) bar(1,3) arvin_feng
装饰器应用场景
该节摘抄记录自文章Python装饰器的应用场景,文章Python装饰器的应用场景
类型检查
from functools import wraps def require_ints(func): @wraps(func) # 将func的信息复制给inner def inner(*args, **kwargs): for arg list(args) + list(kwargs.values()): if not isinstance(arg, int: raise TypeError("{} 只接受int类型参数".format(func.__name__) return func(*args, **kwargs) return inner
用户验证
定义了装饰器 authenticate,函数 post_comment() 则表示发表用户对某篇文章的评论,每次调用这个函数前,都会先检查用户是否处于登录状态,如果是登录状态,则允许这项操作;如果没有登录,则不允许。
import functools def authenticate(func): @functools.wraps(func) # @functools.wraps(func) 也是一个装饰器, # 不使用它,则 post_comment.__name__ 的值为 wrapper。 # 使用它之后,则 post_comment.__name__ 的值依然为 post_comment。 def wrapper(*args, **kwargs): request = args[0] # 如果用户处于登录状态 if check_user_logged_in(request): # 执行函数 post_comment() return func(*args, **kwargs) else: raise Exception('Authentication failed') return wrapper @authenticate def post_comment(request, ...) ...
输入合理性检查
在大型公司的机器学习框架中,调用机器集群进行模型训练前,往往会用装饰器对其输入(往往是很长的 json 文件)进行合理性检查。这样就可以
- 大大避免输入不正确对机器造成的巨大开销。其实在工作中,很多情况下都会出现输入不合理的现象。因为我们调用的训练模型往往很复杂,输入的文件有成千上万行,很多时候确实也很难发现。
- 如果没有输入的合理性检查,很容易出现“模型训练了好几个小时后,系统却报错说输入的一个参数不对,成果付之一炬”的现象。这样的“惨案”,大大减缓了开发效率,也对机器资源造成了巨大浪费。
import functools def validation_check(input): @functools.wraps(func) def wrapper(*args, **kwargs): ... # 检查输入是否合法 @validation_check def neural_network_training(param1, param2, ...): ...
输出格式化
import json from functools import wraps def json_output(func): # 将原本func返回的字典格式转为返回json字符串格式 @wrap(func) def inner(*args, **kwargs): return json.dumps(func(*args, **kwargs)) return inner
异常捕获
import json from functools import wraps class Error1(Exception): def __init__(self, msg): self.msg = msg def __str__(self): return self.msg def json_output(func): @wrap(func) def inner(*args, **kwargs): try: result = func(*args, **kwargs) except Error1 as ex: result = {"status": "error", "msg": str(ex)} return json.dumps(result) return inner # 使用方法 @json_ouput def error(): raise Error1("该条异常会被捕获并按JSON格式输出")
日志管理
测试函数耗时,装饰器方式秉持开放封闭原则,无需调增函数体内部,而是通过外部封装方式,减少耦合。
import time import logging from functools import wraps def logged(func): @wraps(func) def inner(*args, **kwargs): # *args可以装饰函数也可以装饰类 start = time.time() result = func(*args, **kwargs) exec_time = time.time() - start logger = logging.getLoger("func.logged") logger.warning("{} 调用时间:{:.2} 执行时间:{:.2}s 结果:{}".format(func.__name__, start, exec_time, result) def calculate_similarity(items): ...
缓存装饰器
关于缓存装饰器的用法,其实十分常见,这里以 Python 内置的 LRU cache 为例来说明。LRU cache,在 Python 中的表示形式是 @lru_cache。@lru_cache 会缓存进程中的函数参数和结果,当缓存满了以后,会删除最近最久未使用的数据。
正确使用缓存装饰器,往往能极大地提高程序运行效率。举个例子,大型公司服务器端的代码中往往存在很多关于设备的检查,比如使用的设备是安卓还是 iPhone,版本号是多少。这其中的一个原因,就是一些新的功能,往往只在某些特定的手机系统或版本上才有。这样一来,我们通常使用缓存装饰器来包裹这些检查函数,避免其被反复调用,进而提高程序运行效率,比如写成下面这样:
@lru_cache def check(param1, param2, ...) # 检查用户设备类型,版本号等等 ...
本文作者:Oddpage
本文链接:https://www.cnblogs.com/oddpage/p/16106852.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步