侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

快速了解Python中的装饰器

Python  /  管理员 发布于 7年前   151

需要理解的一些概念

要理解Python中的装饰器,我觉得还是应该从最基本的概念开始:

装饰器模式:所谓的装饰器模式,可以简单地理解为“在不改变原有内部实现的情况下,为函数或者类添加某种特性”。这样我们就可以将一些与业务无关、具有通用性的代码抽象出来,作为装饰器附加到需要这些代码的函数或者类之上。用面向切面编程的思想解释就是“装饰器应该是一个切面”。

函数是一等公民:意思就是函数可以被当成普通变量一样使用。在Python中,可以把函数赋值给变量,可以将函数作为其它函数的参数,也可以将函数作为其它函数的返回值。

闭包:我们都知道局部作用域可以引用全局作用域中的变量,相似的,当一个函数内部又定义了其它函数的时候,内部函数可以使用外部函数所在作用域的变量,这就是闭包。

从最简单的装饰器做起

理解完以上的概念之后,我们尝试一下利用这些特性实现一个简单的装饰器。

首先明确一下需求,我们有时候会需要在函数调用时打印一个相应的日志,虽然可以通过在所有需要打印日志的函数代码中嵌入打印日志的代码来实现,但这种方法不仅增加了许多重复代码,而且在业务代码中嵌入与业务无关的代码增加了整体的耦合度。因此,我们需要实现一个装饰器,这个装饰器在函数调用时可以打印一个日志记录函数调用行为。

如果我们有以下函数foo,代表具体的业务函数:

def foo():  print('in function foo')

我们设想通过调用foo = deco(foo)实现给函数foo增加打印日志的功能,并且不影响它原有的业务。那么在这种设想下,装饰器deco应该也是一个函数,它接收另一个函数作为输入,并返回一个新的、经过装饰的函数。在Python中,我们可以这么写:

def deco(func): # 接收一个函数作为参数  def new_func():    print(f'[log] run function {func.__name__}') # 此处使用了Python3.6的格式化字符串    func() # 闭包,在内部函数中使用了外部函数的变量  return new_func # 将新函数作为返回值返回

执行一下试试效果:

>>> foo = deco(foo)>>> foo()[log] run function fooin function foo

不错,至此我们已经实现了一个最简单的装饰器!在上面的代码中,装饰器deco接收任意的函数作为参数,再在内部构造另一个函数,利用闭包的特性,可以在新函数里调用存在于装饰器deco局部作用域中的函数func。

神奇的@

按照上面那么写,每次我们都得为需要装饰的函数赋一个新值,万一函数或者装饰器的数量增加了,手动写赋值和函数调用就会变得非常麻烦。那么在Python中,有没有更优雅的写法呢?答案是有的,你只需要一个@符号。

在Python中,当我们需要一个装饰器时:

def deco(func):  def new_func():    print(f'[log] run function {func.__name__}')    func()  return new_func@decodef foo():  print('in function foo')

这个地方我们省略了函数的赋值,直接在函数foo定义的上一行加上@deco进行装饰。运行一下试试看:

>>> foo()[log] run function fooin function foo

是不是感觉很神奇?其实这里面没什么魔法,只不过是Python在处理函数定义的代码时,帮你在其中把foo=deco(foo)的逻辑加上了而已。

装饰器也想要参数

上面的代码实现了在业务函数调用之前打印日志的功能,那如果我们需要在业务代码执行完之后打印一条自定义的消息,怎么办呢?我们必须要让我们的装饰器可以接收自定义参数。

上面提到过,Python做的只是当写了@deco时,把调用deco(func)的结果赋值给它装饰的函数而已。顺着这个逻辑,当我们需要一个带参数的装饰器时,代码上应该是写为@deco('some message'),这时Python将调用deco(msg)(func)的结果赋值给foo。那么事情就变得简单了,我们只需要在上面代码的基础上嵌套一层函数:

def deco(msg):  def inner_deco(func):    def new_func():      print(f'[log] run function {func.__name__}')      func()      print(f'[log] {msg}')    return new_func  return inner_deco@deco('some message')def foo():  print('in function foo')

执行一下试试:

>>> foo()[log] run function fooin function foo[log] some message

不支持带参数的被装饰函数的装饰器不是好装饰器

上面的代码还是有问题,因为我们只考虑了函数foo没有参数时的情况,万一函数foo带了参数,这个装饰器就会丢失参数信息,这不是一个合格的装饰器应该出现的情况。所以,我们借助Python中的*args和**kwargs使被装饰的函数可以支持传入任意参数:

def deco(msg):  def inner_deco(func):    def new_func(*args, **kwargs):      print(f'[log] run function {func.__name__}')      func(*args, **kwargs)      print(f'[log] {msg}')    return new_func  return inner_deco@deco('some message')def foo(a, b=None):  print('in function foo')  print(f'a is {a} & b is {b}')

这样一来,无论函数foo的参数列表是怎么样的都不会有问题了:

>>> foo('hello')[log] run function fooin function fooa is hello & b is None[log] some message

不支持有返回值的被装饰函数的装饰器不是好装饰器

别忘了,到目前为止,我们写的函数foo都没有返回值,如果函数foo有返回值怎么办呢?我想你心里应该有答案了:

def deco(msg):  def inner_deco(func):    def new_func(*args, **kwargs):      print(f'[log] run function {func.__name__}')      rlt = func(*args, **kwargs)      print(f'[log] {msg}')      return rlt    return new_func  return inner_deco@deco('some message')def foo(a, b=None):  print('in function foo')  print(f'a is {a} & b is {b}')  return 'ok'

由于装饰器在原函数执行完之后还有别的操作,所以应该把返回值暂存起来,等到装饰器的逻辑执行完毕,才返回最终结果。这就是我们的最终版装饰器了!

>>> rlt = foo('a')[log] run function fooin function fooa is a & b is None[log] some message>>> print(rlt)ok

有没有更骚的操作?

当然有啊!我标题都这么写了难不成会没有?

在Python中,你可以使用类来作为装饰器:

class Deco(object):  def __call__(self, func):    def new_func():      print(f'[log] run function {func.__name__}')      func()    return new_func@Deco()def foo():  print('in function foo')
>>> foo()[log] run function fooin function foo

这么做的好处就是可以利用类更好地管理参数和调用逻辑,比起之前三层函数嵌套的形式是不是清晰多了?

在Python中,你还可以使用装饰器来装饰一个类,比如这样:

def add_doc(doc):  def deco(cls):    cls.__doc__ = doc    return cls  return deco@add_doc('this is the doc of Cls')class Cls(object):  pass

来看看效果:

>>> help(Cls)Help on class Cls in module __main__:class Cls(builtins.object) | this is the doc of Cls

上面的代码只是一个示例,展示装饰器怎么装饰一个类而已,不是说在实际情况下应该这么用。大部分的情况下,我们对于类的拓展应该是通过继承而不是装饰。

具体怎么巧妙地利用装饰器就要靠大家发挥自己的想象力了。

总结

以上就是本文关于快速了解Python中的装饰器的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!


  • 上一条:
    python+django+sql学生信息管理后台开发
    下一条:
    简单了解python模块概念
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客