举例讲解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 相关文章推荐
python中字典(Dictionary)用法实例详解
May 30 Python
尝试使用Python多线程抓取代理服务器IP地址的示例
Nov 09 Python
开源Web应用框架Django图文教程
Mar 09 Python
Django实现全文检索的方法(支持中文)
May 14 Python
python3中的md5加密实例
May 29 Python
浅谈Python脚本开头及导包注释自动添加方法
Oct 27 Python
只需7行Python代码玩转微信自动聊天
Jan 27 Python
Python3.7基于hashlib和Crypto实现加签验签功能(实例代码)
Dec 04 Python
Python如何基于selenium实现自动登录博客园
Dec 16 Python
Python ArgumentParse的subparser用法说明
Apr 20 Python
Python用SSH连接到网络设备
Feb 18 Python
还在手动盖楼抽奖?教你用Python实现自动评论盖楼抽奖(一)
Jun 07 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
FCKeditor添加自定义按钮
2008/03/27 PHP
PHP常用数组函数介绍
2014/07/28 PHP
laravel admin实现分类树/模型树的示例代码
2020/06/10 PHP
JavaScript arguments 多参传值函数
2010/10/24 Javascript
nodejs实用示例 缩址还原
2010/12/28 NodeJs
js日历功能对象
2012/01/12 Javascript
js自定义事件及事件交互原理概述(一)
2013/02/01 Javascript
js根据给定的日期计算当月有多少天实现思路及代码
2013/02/25 Javascript
javascript ajax的5种状态介绍
2014/08/18 Javascript
jQuery中prevUntil()方法用法实例
2015/01/08 Javascript
最流行的Node.js精简型和全栈型开发框架介绍
2015/02/26 Javascript
jQuery实现页面内锚点平滑跳转特效的方法总结
2015/05/11 Javascript
js实现发送验证码后的倒计时功能
2015/05/28 Javascript
IE中document.createElement的iframe无法设置属性name的解决方法
2015/09/14 Javascript
使用JQuery FancyBox插件实现图片展示特效
2015/11/16 Javascript
jQuery+ajax实现文章点赞功能的方法
2015/12/31 Javascript
JavaScript知识点总结(十六)之Javascript闭包(Closure)代码详解
2016/05/31 Javascript
jQuery Ajax使用FormData上传文件和其他数据后端web.py获取
2017/06/11 jQuery
js中console在一行内打印字符串和对象的方法
2019/09/10 Javascript
js实现自定义右键菜单
2020/05/18 Javascript
JS使用Chrome浏览器实现调试线上代码
2020/07/23 Javascript
Python利用ORM控制MongoDB(MongoEngine)的步骤全纪录
2018/09/13 Python
Django如何自定义分页
2018/09/25 Python
在Pycharm中修改文件默认打开方式的方法
2019/01/17 Python
python 5个顶级异步框架推荐
2020/09/09 Python
Django前后端分离csrf token获取方式
2020/12/25 Python
Zalando Lounge瑞士:时尚与生活方式购物俱乐部
2020/03/12 全球购物
工程力学硕士生的自我评价范文
2013/11/16 职场文书
生物专业个人自荐信范文
2013/11/29 职场文书
滞留工资返还协议书
2014/10/19 职场文书
2014年纳税评估工作总结
2014/12/23 职场文书
助学感谢信范文
2015/01/21 职场文书
《我是什么》教学反思
2016/02/16 职场文书
2019开业庆典剪彩仪式主持词!
2019/07/22 职场文书
Python max函数中key的用法及原理解析
2021/06/26 Python
服务器间如何实现文件共享
2022/05/20 Servers