举例讲解Python装饰器


Posted in Python onDecember 24, 2020

在Python里面,函数可以作为参数传入一个函数,函数也可以复制给变量,通过变量调用函数。装饰器可以扩展一个函数的功能,为函数做一个装饰器注解,可以把装饰器里面定义的功能于所有函数提前执行,提升代码的复用程度。

现在有这么个场景。

打卡

互联网公司里面有各种员工,程序员,前台...,程序员在打开电脑前,需要打卡,前台要早点来开门(我也不清楚,谁开门,这里假定,前台开门),前台开门前也需要打卡。也就是说,打卡是所有员工的最先的公共动作,那么可以把打卡这个功能抽出来作为公共逻辑。

普通函数调用方法

自然想到,可以实现如下。

def di(f):
  print('%s 打卡,滴...' % f.__name__)
  return f()


def boot():
  print('开机')


def open():
  print('开门')


if __name__ == '__main__':
  """
  程序员开机之前,前台开门之前,都需要先在门外指纹机打卡。
  """
  di(boot)
  di(open)

定义了一个函数di(f),可以打印f.__name__即f的函数名信息,同时返回f()的执行结果。

注意:__name__如果作为模块导入,module.__name__就是模块自己的名字,如果模块自己作为脚本执行,返回__main__。

执行结果:

boot 打卡,滴...
开机
open 打卡,滴...
开门

这样设计,如果有很多函数都要调用,就很麻烦,那么装饰器就排上了用场。

简单装饰器 与 @语法糖

装饰器:在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

简单装饰器

定义一个di(f)方法,还是把要执行的逻辑的函数作为参数传入,里面定义一个wrapper函数,返回值是f的执行结果。
在if __name__ == '__main__':里面,调用了这个装饰器,不修改定义好了的函数,在运行期间动态添加功能"打卡"。

import functools

# 简单装饰器
def di(f):
  """
  程序员开机之前,前台开门之前,都需要先在门外指纹机打卡。
  :param f: 传入一个函数
  :return:
  """
  # 把原始函数的__name__等属性复制到wrapper()
  @functools.wraps(f)
  def wrapper():
    print('%s 打卡,滴...' % f.__name__)
    return f()
  return wrapper


def boot():
  print('开机')


def open():
  print('开门')


if __name__ == '__main__':

  # 第一种,简单装饰器
  a = di(boot)
  a1 = di(open)
  print(a.__name__) # 结果wrapper 加@functools.wraps(f)后结果为 boot
  a()
  a1()

di(boot)的返回值a就是wrapper函数,通过a()就调用了wrapper函数,得到boot的返回值。同理,di(open)一样。

结果

boot
boot 打卡,滴...
开机
open 打卡,滴...
开门

由于di(boot)的返回值a就是wrapper函数,那么print(a.__name__)的结果就理所当然是是wrapper,我们希望是boot,怎么办,functools.wraps(f)这个注解可以把原始函数boot的__name__等属性复制到wrapper(),把这行代码注释也能运行,那么print(a.__name__)的结果就是wrapper。

第二种,@ 语法糖
通过@语法糖,也能将装饰器应用于函数上面,推荐。

import functools

def di(f):
  """
  程序员开机之前,前台开门之前,都需要先在门外指纹机打卡。
  :param f: 传入一个函数
  :return:
  """
  # 把原始函数的__name__等属性复制到wrapper()
  @functools.wraps(f)
  def wrapper():
    print('%s 打卡,滴...' % f.__name__)
    return f()
  return wrapper


# @ 语法糖
@di
def boot2():
  print('开机')


@di
def open2():
  print('开门')
  
  
if __name__ == '__main__':

  # 第二种,@ 语法糖
  boot2()
  open2()

@di标记相当于,a2 = di(boot2) a2()。不用这么麻烦,因为加了@符号标记,直接用boot2()调用装饰器即可。

结果

boot2 打卡,滴...
开机
open2 打卡,滴...
开门

业务逻辑函数需要参数

业务逻辑函数可能需要参数,比如:

def boot(name):
  print('%s 开机' % name)

那么,只需要将前面的装饰器修改为:

import functools

# 业务逻辑函数需要参数
def di(f):
  """
  程序员开机之前,前台开门之前,都需要先在门外指纹机打卡。
  :param f: 传入一个函数
  :return:
  """
  # 把原始函数的__name__等属性复制到wrapper()
  @functools.wraps(f)
  def wrapper(*args, **kwargs):
    print('%s 打卡,滴...' % f.__name__)
    return f(*args, **kwargs)
  return wrapper


@di
def boot(name):
  print('%s 开机' % name)


if __name__ == '__main__':
  boot('keguang')

结果:

boot 打卡,滴...
keguang 开机

给wrapper也加上*args, **kwargs参数,在boot里面直接调用f(*args, **kwargs)即可。顺便提一下:

  • *args:可以传入一个数组参数
  • **kwargs:可以传入一个k-v对参数

先后顺序对应,数组参数在前。举例:

def f(*args, **kwargs):
  print('args=', args)
  print('kwargs=', kwargs)

print(f(1, 2, 3, a = 'a', b = 'b'))

# 结果
# args= (1, 2, 3)
# kwargs= {'a': 'a', 'b': 'b'}

带参数的装饰器

如果装饰器也带参数,比如现在如果某个员工早晨上班来得早< 9:00,咱可以做个表扬,那么相当于只需要在前面的di()外面套一层函数,di_args即可,在wrapper里面。使用这个参数

import functools

# 带参数的装饰器
def di_args(time):
  def di(f):
    """
    程序员开机之前,前台开门之前,都需要先在门外指纹机打卡。
    :param f: 传入一个函数
    :return:
    """
    # 把原始函数的__name__等属性复制到wrapper()
    @functools.wraps(f)
    def wrapper(*args, **kwargs):
      if time < '9:00':
        print('来的真早,很棒。。。')

      print('%s 打卡,滴...' % f.__name__)
      return f(*args, **kwargs)
    return wrapper
  return di


@di_args('8:00')
def boot(name):
  print('%s 开机' % name)


if __name__ == '__main__':
  boot('keguang')

参数在@di_args('8:00')传入即可,有点像java里面的注解。最后还是通过boot('keguang')调用即可,结果:

来的真早,很棒。。。
boot 打卡,滴...
keguang 开机

类装饰器

类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

# 类装饰器
class di(object):
  def __init__(self, f):
    self._f = f

  def __call__(self, *args, **kwargs):
    print('decorator start...')
    self._f()
    print('decorator end...')


@di
def boot():
  print('开机')


if __name__ == '__main__':
  boot()

加上@di装饰器标识,会用boot去实例化di类,然后执行__call__函数,object表示这个类可以传入任何类型参数。
运行结果

decorator start...
开机
decorator end...

装饰器有一个典型的应用场景就是打log日志,如果所有逻辑都需要日志记录程序的运行状况,那么可以对这些逻辑(函数)加日志模块装饰器,就能达到相应目的。

以上就是举例讲解Python装饰器的详细内容,更多关于python装饰器的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
pyqt4教程之实现windows窗口小示例分享
Mar 07 Python
Python中的tuple元组详细介绍
Feb 02 Python
Python实现从URL地址提取文件名的方法
May 15 Python
python实现将excel文件转化成CSV格式
Mar 22 Python
python2与python3共存问题的解决方法
Sep 18 Python
详解在Python中以绝对路径或者相对路径导入文件的方法
Aug 30 Python
python获取Linux发行版名称
Aug 30 Python
python运用pygame库实现双人弹球小游戏
Nov 25 Python
Tensorflow的梯度异步更新示例
Jan 23 Python
Python中包的用法及安装
Feb 11 Python
基于python3.7利用Motor来异步读写Mongodb提高效率(推荐)
Apr 29 Python
Flask response响应的具体使用
Jul 15 Python
python 基于opencv操作摄像头
Dec 24 #Python
python 基于selenium实现鼠标拖拽功能
Dec 24 #Python
python实现简单猜单词游戏
Dec 24 #Python
Python 虚拟环境工作原理解析
Dec 24 #Python
python基于openpyxl生成excel文件
Dec 23 #Python
Python+unittest+requests+excel实现接口自动化测试框架
Dec 23 #Python
用python计算文件的MD5值
Dec 23 #Python
You might like
PHP中str_replace函数使用小结
2008/10/11 PHP
PHP无限分类(树形类)的深入分析
2013/06/02 PHP
简单介绍PHP的责任链编程模式
2015/08/11 PHP
phpMyAdmin无法登陆的解决方法
2017/04/27 PHP
PhpStorm本地断点调试的方法步骤
2018/05/21 PHP
使用Rancher在K8S上部署高性能PHP应用程序的教程
2020/07/10 PHP
js实现按一下删除键删除整个单词附demo
2014/09/05 Javascript
基于JS实现PHP的sprintf函数实例
2015/11/14 Javascript
JS实现图片平面旋转的方法
2016/03/01 Javascript
vue中用动态组件实现选项卡切换效果
2017/03/25 Javascript
Ionic项目中Native Camera的使用方法
2017/06/07 Javascript
js实现Tab选项卡切换效果
2020/07/17 Javascript
详解用Node.js写一个简单的命令行工具
2018/03/01 Javascript
基于layui数据表格以及传数据的方式
2018/08/19 Javascript
vue项目中在外部js文件中直接调用vue实例的方法比如说this
2019/04/28 Javascript
React传值 组件传值 之间的关系详解
2019/08/26 Javascript
Vue.js组件props数据验证实现详解
2019/10/19 Javascript
Vue列表如何实现滚动到指定位置样式改变效果
2020/05/09 Javascript
vue实现在线学生录入系统
2020/05/30 Javascript
[31:00]2014 DOTA2华西杯精英邀请赛5 24 NewBee VS iG
2014/05/25 DOTA
python自动化测试之连接几组测试包实例
2014/09/28 Python
Python functools模块学习总结
2015/05/09 Python
matplotlib在python上绘制3D散点图实例详解
2017/12/09 Python
Python操作MySQL数据库的方法
2018/06/20 Python
对python For 循环的三种遍历方式解析
2019/02/01 Python
pyqt5 从本地选择图片 并显示在label上的实例
2019/06/13 Python
为什么相对PHP黑python的更少
2020/06/21 Python
基于Python爬取51cto博客页面信息过程解析
2020/08/25 Python
详解HTML5中的拖放事件(Drag 和 drop)
2016/11/14 HTML / CSS
canvas实现高阶贝塞尔曲线(N阶贝塞尔曲线生成器)
2018/01/10 HTML / CSS
百货商场楼层班组长竞聘书
2014/03/31 职场文书
节约用水演讲稿
2014/05/21 职场文书
法人代表任命书范本
2014/06/05 职场文书
卫生标语大全
2014/06/21 职场文书
Python 高级库15 个让新手爱不释手(推荐)
2021/05/15 Python
基于Apache Hudi在Google云构建数据湖平台的思路详解
2022/04/07 Servers