Python 装饰器使用详解

发布时间 - 2026-01-11 02:33:43    点击率:

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象.

  经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。

  先来看一个简单例子:

def now():
  print('2017_7_29')

现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:

def now():
  print('2017_7_29')
  logging.warn("running")

假设有类似的多个需求,怎么做?再写一个logging在now函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码.

def use_logging(func):   
  logging.warn("%s is running" % func.__name__)   
  func() 

def now():   
  print('2017_7_29') 
  
use_logging(now)

在实现,逻辑上不难, 但是这样的话,我们每次都要将一个函数作为参数传递给日志函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行now(),但是现在不得不改成use_logging(now)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

  首先要明白函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。例如:

def now():
  print('2017_7_28')

f=now
f()
# 函数对象有一个__name__属性,可以拿到函数的名字
print('now.__name__:',now.__name__)
print('f.__name__:',f.__name__)

简单装饰器

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

def log(func):
  def wrapper(*args,**kw):
    print('call %s():'%func.__name__)
    return func(*args,**kw)
  return wrapper

# 由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,
# 只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
# wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。
# 在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数.现在执行:

now = log(now)
now()

输出结果:
    call now():
    2017_7_28

函数log就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像now被log装饰了。在这个例子中,函数进入时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。

使用语法糖:

@log
def now():
  print('2017_7_28')

@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作

  这样我们就可以省去now = log(now)这一句了,直接调用now()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。

  装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

  带参数的装饰器:

  如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会复杂一点。比如,要自定义log的文本:

def log(text):
  def decorator(func):
    def wrapper(*args,**kw):
      print('%s %s()'%(text,func.__name__))
      return func(*args,**kw)
    return wrapper
  return decorator

这个3层嵌套的decorator用法如下:

@log('goal')
def now():
  print('2017-7-28')
now()

等价于

now = log('goal')(now)

# 首先执行log('execute'),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数
now()

因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的'now'变成了'wrapper':

print(now.__name__)
# wrapper

因为返回的那个wrapper()函数名字就是'wrapper',所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ = func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
  @functools.wraps(func)
  def wrapper(*args, **kw):
    print('call %s():' % func.__name__)
    return func(*args, **kw)
  return wrapper

import functools

def log(text):
  def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
      print('%s %s():' % (text, func.__name__))
      return func(*args, **kw)
    return wrapper
  return decorator

类装饰器:

  再来看看类装饰器,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法

import time

class Foo(object):   
  def __init__(self, func):   
    self._func = func 
  
  def __call__(self):   
    print ('class decorator runing')   
    self._func()   
    print ('class decorator ending') 

@Foo 
def now():   
  print (time.strftime('%Y-%m-%d',time.localtime(time.time()))) 
  
now()

总结:

  概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。  

  同时在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。


# python装饰器详解  # 装饰器模式  # python  # 装饰器  # python使用装饰器作日志处理的方法  # python实现log日志的示例代码  # Python装饰器用法实例总结  # Python装饰器使用示例及实际应用例子  # Python 带有参数的装饰器实例代码详解  # Python中装饰器高级用法详解  # python 使用装饰器并记录log的示例代码  # 一个函数  # 是一个  # 被称为  # 就会  # 可以用  # 返回值  # 我们可以  # 本质上  # 就可以  # 高阶  # 有一个  # 的是  # 这一  # 是在  # 在这个  # 还可以  # 都要  # 那就  # 多个  # 不需要 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: JavaScript如何操作视频_媒体API怎么控制播放  Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康  Win11关机界面怎么改_Win11自定义关机画面设置【工具】  湖南网站制作公司,湖南上善若水科技有限公司做什么的?  Java遍历集合的三种方式  微信小程序 五星评分(包括半颗星评分)实例代码  网站制作大概要多少钱一个,做一个平台网站大概多少钱?  Laravel PHP版本要求一览_Laravel各版本环境要求对照  如何用IIS7快速搭建并优化网站站点?  香港服务器租用费用高吗?如何避免常见误区?  Laravel怎么发送邮件_Laravel Mail类SMTP配置教程  Laravel如何使用Gate和Policy进行权限控制_Laravel权限判定与策略规则配置  JavaScript Ajax实现异步通信  如何快速生成橙子建站落地页链接?  Laravel storage目录权限问题_Laravel文件写入权限设置  详解Android图表 MPAndroidChart折线图  如何撰写建站申请书?关键要点有哪些?  如何在万网ECS上快速搭建专属网站?  JavaScript常见的五种数组去重的方式  Laravel如何清理系统缓存命令_Laravel清除路由配置及视图缓存的方法【总结】  Laravel如何保护应用免受CSRF攻击?(原理和示例)  如何用虚拟主机快速搭建网站?详细步骤解析  如何在IIS中配置站点IP、端口及主机头?  Laravel怎么为数据库表字段添加索引以优化查询  Midjourney怎么调整光影效果_Midjourney光影调整方法【指南】  Laravel如何使用Blade组件和插槽?(Component代码示例)  Laravel如何优化应用性能?(缓存和优化命令)  如何确保西部建站助手FTP传输的安全性?  Laravel如何使用.env文件管理环境变量?(最佳实践)  微信公众帐号开发教程之图文消息全攻略  免费制作统计图的网站有哪些,如何看待现如今年轻人买房难的情况?  Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】  如何挑选优质建站一级代理提升网站排名?  Laravel全局作用域是什么_Laravel Eloquent Global Scopes应用指南  Laravel如何配置Horizon来管理队列?(安装和使用)  成都品牌网站制作公司,成都营业执照年报网上怎么办理?  如何在沈阳梯子盘古建站优化SEO排名与功能模块?  简历在线制作网站免费版,如何创建个人简历?  Google浏览器为什么这么卡 Google浏览器提速优化设置步骤【方法】  Laravel怎么创建自己的包(Package)_Laravel扩展包开发入门到发布  文字头像制作网站推荐软件,醒图能自动配文字吗?  魔毅自助建站系统:模板定制与SEO优化一键生成指南  Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】  html5的keygen标签为什么废弃_替代方案说明【解答】  Android中Textview和图片同行显示(文字超出用省略号,图片自动靠右边)  Laravel如何使用Telescope进行调试?(安装和使用教程)  Laravel项目结构怎么组织_大型Laravel应用的最佳目录结构实践  Laravel如何使用Service Container和依赖注入?(代码示例)  Android自定义listview布局实现上拉加载下拉刷新功能  详解vue.js组件化开发实践