我从以下几点,由浅入深详细讲解一下Python装饰器:什么是装饰器?

为什么用装饰器?

在哪里用装饰器?

然后以示例+讲解相结合的方式阐述,同时会讲解一些在很多教程和书籍中不会涉及到的内容。

什么是Python装饰器?

顾名思义,从字面意思就可以理解,它是用来"装饰"Python的工具,使得代码更具有Python简洁的风格。换句话说,它是一种函数的函数,因为装饰器传入的参数就是一个函数,然后通过实现各种功能来对这个函数的功能进行增强。

为什么用装饰器?

前面提到了,装饰器是通过某种方式来增强函数的功能。当然,我们可以通过很多方式来增强函数的功能,只是装饰器有一个无法替代的优势--简洁。

你只需要在每个函数上方加一个@就可以对这个函数进行增强。

在哪里用装饰器?

装饰器最大的优势是用于解决重复性的操作,其主要使用的场景有如下几个:计算函数运行时间

给函数打日志

类型检查

当然,如果遇到其他重复操作的场景也可以类比使用装饰器。

​简单示例

前面都是文字描述,不管说的怎么天花烂坠,可能都无法体会到它的价值,下面就以一个简单的例子来看一下它的作用。

如果你要对多个函数进行统计运行时间,不使用装饰器会是这样的,

from time import time, sleep

def fun_one():

start = time()

sleep(1)

end = time()

cost_time = end - start

print("func one run time {}".format(cost_time))

def fun_two():

start = time()

sleep(1)

end = time()

cost_time = end - start

print("func two run time {}".format(cost_time))

def fun_three():

start = time()

sleep(1)

end = time()

cost_time = end - start

print("func three run time {}".format(cost_time))

在每个函数里都需要获取开始时间start、结束时间end、计算耗费时间cost_time、加上一个输出语句。

使用装饰器的方法是这样的,

def run_time(func):

def wrapper():

start = time()

func() # 函数在这里运行

end = time()

cost_time = end - start

print("func three run time {}".format(cost_time))

return wrapper

@run_time

def fun_one():

sleep(1)

@run_time

def fun_two():

sleep(1)

@run_time

def fun_three():

sleep(1)

通过编写一个统计时间的装饰器run_time,函数的作为装饰器的参数,然后返回一个统计时间的函数wrapper,这就是装饰器的写法,用专业属于来说这叫闭包,简单来说就是函数内嵌套函数。然后再每个函数上面加上@run_time来调用这个装饰器对不同的函数进行统计时间。

可见,统计时间这4行代码是重复的,一个函数需要4行,如果100个函数就需要400行,而使用装饰器,只需要几行代码实现一个装饰器,然后每个函数前面加一句命令即可,如果是100个函数,能少300行左右的代码量。

带参数的装饰器

通过前面简单的例子应该已经明白装饰器的价值和它的简单用法:通过闭包来实现装饰器,函数作为外层函数的传入参数,然后在内层函数中运行、附加功能,随后把内层函数作为结果返回。

除了上述简单的用法还有一些更高级的用法,比如用装饰器进行类型检查、添加带参数的的装饰器等。它们的用法大同小异,关于高级用法,这里以带参数的装饰器为例进行介绍。

不要把问题想的太复杂,带参数的装饰器其实就是在上述基本的装饰器的基础上在外面套一层接收参数的函数,下面通过一个例子说明一下。

以上述例子为基础,前面的简单示例输出的信息是,

func three run time 1.0003271102905273

func three run time 1.0006263256072998

func three run time 1.000312328338623

现在我认为这样的信息太单薄,需要它携带更多的信息,例如函数名称、日志等级等,这时候可以把函数名称和日志等级作为装饰器的参数,下面来时实现以下。

def logger(msg=None):

def run_time(func):

def wrapper(*args, **kwargs):

start = time()

func() # 函数在这里运行

end = time()

cost_time = end - start

print("[{}] func three run time {}".format(msg, cost_time))

return wrapper

return run_time

@logger(msg="One")

def fun_one():

sleep(1)

@logger(msg="Two")

def fun_two():

sleep(1)

@logger(msg="Three")

def fun_three():

sleep(1)

fun_one()

fun_two()

fun_three()

可以看出,我在示例基本用法里编写的装饰器外层又嵌套了一层函数用来接收参数msg,这样的话在每个函数(func_one、func_two、func_three)前面调用时可以给装饰器传入参数,这样的输出结果是,

[One] func three run time 1.0013229846954346

[Two] func three run time 1.000720500946045

[Three] func three run time 1.0001459121704102

自定义属性的装饰器

上述介绍的几种用法中其实有一个问题,就是装饰器不够灵活,我们预先定义了装饰器run_time,它就会按照我们定义的流程去工作,只具备这固定的一种功能,当然,我们前面介绍的通过带参数的装饰器让它具备了一定的灵活性,但是依然不够灵活。其实,我们还可以对装饰器添加一些属性,就如同给一个类定义实现不同功能的方法那样。

以输出日志为例,初学Python的同学都习惯用print打印输出信息,其实这不是一个好习惯,当开发商业工程时,你很用意把一些信息暴露给用户。在开发过程中,我更加鼓励使用日志进行输出,通过定义WARNING、DEBUG、INFO等不同等级来控制信息的输出,比如INFO是可以给用户看到的,让用户直到当前程序跑到哪一个阶段了。DEBUG是用于开发人员调试和定位问题时使用。WARING是用于告警和提示。

那么问题来了,如果我们预先定义一个打印日志的装饰器,

def logger_info(func):

logmsg = func.__name__

def wrapper():

func()

log.log(logging.INFO, "{} if over.".format(logmsg))

return wrapper

logger_info,那么它的灵活度太差了,因为如果我们要输出DEBUG、WARING等级的日志,还需要重新写一个装饰器。

解决这个问题,有两个解决方法:利用前面所讲的带参数装饰器,把日志等级传入装饰器

利用自定义属性来修改日志等级

由于第一种已经以统计函数运行时间的方式进行讲解,这里主要讲解第二种方法。

先看一下代码,

import logging

from functools import partial

def wrapper_property(obj, func=None):

if func is None:

return partial(wrapper_property, obj)

setattr(obj, func.__name__, func)

return func

def logger_info(level, name=None, message=None):

def decorate(func):

logmsg = message if message else func.__name__

def wrapper(*args, **kwargs):

log.log(level, logmsg)

return func(*args, **kwargs)

@wrapper_property(wrapper)

def set_level(newlevel):

nonlocal level

level = newlevel

@wrapper_property(wrapper)

def set_message(newmsg):

nonlocal logmsg

logmsg = newmsg

return wrapper

return decorate

@logger_info(logging.WARNING)

def main(x, y):

return x + y

这里面最重要的是wrapper_property这个函数,它的功能是把一个函数func编程一个对象obj的属性,然后通过调用wrapper_property,给装饰器添加了两个属性set_message和set_level,分别用于改变输出日志的内容和改变输出日志的等级。

看一下输出结果,

main(3, 3)

# 输出

# WARNING:Test:main

# 6

来改变一下输出日志等级,

main.set_level(logging.ERROR)

main(5, 5)

# 输出

# ERROR:Test:main

# 10

输出日志等级改成了ERROR。

保留元信息的装饰器

很多教程中都会介绍装饰器,但是大多数都是千篇一律的围绕基本用法在展开,少部分会讲一下带参数的装饰器,但是有一个细节很少有教程提及,那就是保留元信息的装饰器。

什么是函数的元信息?

就是函数携带的一些基本信息,例如函数名、函数文档等,我们可以通过func.__name__获取函数名、可以通过func.__doc__获取函数的文档信息,用户也可以通过注解等方式为函数添加元信息。

例如下面代码,

from time import time

def run_time(func):

def wrapper(*args, **kwargs):

start = time()

func() # 函数在这里运行

end = time()

cost_time = end - start

print("func three run time {}".format(cost_time))

return wrapper

@run_time

def fun_one():

'''func one doc.'''

sleep(1)

fun_one()

print(fun_one.__name__)

print(fun_one.__doc__)

# 输出

# wrapper

# None

可以看出,通过使用装饰器,函数fun_one的元信息都丢失了,那怎么样才能保留装饰器的元信息呢?

可以通过使用Python自带模块functools中的wraps来保留函数的元信息,

from time import time

from functools import wraps

def run_time(func):

@wraps(func) # <- 这里加 wraps(func) 即可

def wrapper(*args, **kwargs):

start = time()

func() # 函数在这里运行

end = time()

cost_time = end - start

print("func three run time {}".format(cost_time))

return wrapper

@run_time

def fun_one():

'''func one doc.'''

sleep(1)

fun_one()

print(fun_one.__name__)

print(fun_one.__doc__)

# 输出

# fun_one

# func one doc.

只需要在代码中加入箭头所指的一行即可保留函数的元信息。

推荐

本文仅围绕Python装饰器进行了介绍,如果想更加深入、系统的学习Python,建议选择一本优秀的Python书籍进行系统的学习一遍,Python方面优秀的书籍有很多,我在这里推荐一本《流畅的Python》,本书的特点是由浅入深,非常详细,此外覆盖内容非常全面,同时和大多数千篇一律的书籍不同,本书有很多与众不同的思想和视角,能够让学习者站在更高的角度去审视、学习Python知识,

干货干货 | 2019年共享免费资源整理(上):学习资源篇​mp.weixin.qq.com干货 | 2019年共享免费资源整理(下):实用工具篇​mp.weixin.qq.com

作品精选

python装饰器-如何更通俗地讲解Python的装饰器?相关推荐

  1. python装饰器详解-如何更通俗地讲解Python的装饰器?

    我从以下几点,由浅入深详细讲解一下Python装饰器:什么是装饰器? 为什么用装饰器? 在哪里用装饰器? 然后以示例+讲解相结合的方式阐述,同时会讲解一些在很多教程和书籍中不会涉及到的内容. 什么是P ...

  2. python事件处理函数_事件驱动的简明讲解(python实现)

    关键词:编程范式,事件驱动,回调函数,观察者模式 作者:码匠信龙 举个简单的例子: 有些人喜欢的某个公众号,然后去关注这个公众号,哪天这个公众号发布了篇新的文章,没多久订阅者就会在微信里收到这个公众号 ...

  3. python函数设置默认参数_深入讲解Python函数中参数的使用及默认参数的陷阱

    这篇文章主要介绍了Python函数中参数的使用及默认参数的陷阱,文中将函数的参数分为必选参数.默认参数.可变参数和关键字参数来讲,要的朋友可以参考下 C++里函数可以设置缺省参数,Java不可以,只能 ...

  4. 按复杂度有效性递减排序_十大经典排序算法:python源码实现,通俗深入讲解

    概述 提示:本文上万字,陆陆续续疏理知识点加测试代码,耗时近一个月.阅读时长40分钟左右. 本文将十大经典排序算法进行汇总,从源码实现.复杂度.稳定性进行分析,并对每种排序的特性进行点评.对典型算法, ...

  5. python编程中条件句_简单讲解Python编程中namedtuple类的用法

    Python的Collections模块提供了不少好用的数据容器类型,其中一个精品当属namedtuple. namedtuple能够用来创建类似于元祖的数据类型,除了能够用索引来访问数据,能够迭代, ...

  6. python中的运算符举例_举例讲解Python中的身份运算符的使用方法

    Python身份运算符 身份运算符用于比较两个对象的存储单元 以下实例演示了Python所有身份运算符的操作: #!/usr/bin/python a = 20 b = 20 if ( a is b ...

  7. python中的运算符举例_举例讲解Python中的算数运算符的用法

    下表列出了所有Python语言支持的算术运算符.假设变量a持有10和变量b持有20,则: 例子: 试试下面的例子就明白了所有的Python编程语言提供了算术运算符: #!/usr/bin/python ...

  8. python中if else语句用法_讲解Python中if语句的嵌套用法

    可能有这样一种情况,当你想检查其他条件后一个条件解析为真.在这种情况下,可以使用嵌套的if结构. 在嵌套的 if 语句结构,可以在一个 if... elif... else 结构里面可有另外一个 if ...

  9. python若干整数的最大值_实例讲解Python中整数的最大值输出

    在Python中可以存储很大的值,如下面的Python示例程序: x = 10000000000000000000000000000000000000000000; x = x + 1 print ( ...

最新文章

  1. Tungsten Fabric SDN — 操作实践 — Virtual Networks L2/L3 互联
  2. GCT之数学公式(代数部分)
  3. 集群镜像:实现高效的分布式应用交付
  4. 爬虫:突破有道翻译js加密(最新)
  5. mycat 不得不说的缘分
  6. linux的dhcp的安装,linux下DHCP的安装配置
  7. java中substring的使用方法
  8. dbvisulizer 存储过程
  9. DTU是什么 DTU种类及应用领域分析
  10. 合并两张图片php,php多张图片合并方法分享
  11. bt磁力种子与php文件,视频 | BT 种子和磁力链接是如何工作的?
  12. 网络 4.0 防火墙概述
  13. iPhone无法连接Wi-Fi解决方法
  14. 线下停摆,线上狂欢,疫情下“云健身”火了!
  15. 旅游网站的设计与实现
  16. 【日常填坑】启动WebLogic时提示:此时不应有XXXX
  17. c语言除法结果溢出怎么办,关于C ++:导致除法溢出错误(x86)
  18. 华硕的笔记本为什么按Fn+F9禁用触摸板不起作用了?
  19. android模拟器有什么作用,为什么要用安卓模拟器?安卓模拟器是什么?
  20. 用友U8调拨单、组装拆卸单、盘点单审核后自动审核对应的其他出入库单

热门文章

  1. springboot和flowable modeler整合
  2. IDEA 打可执行jar包(maven项目)
  3. Go的50坑:新Golang开发者要注意的陷阱、技巧和常见错误[2]
  4. 牛客网-阿里巴巴2017
  5. js控制网页滚动条往下滚动
  6. wordpress评论插件:多说
  7. c# 获取本机 MAC地址\序列号\硬盘序列号
  8. 使用 Microsoft .NET Framework 精简版中的 MessageWindow 类
  9. WinForms多线程编程之多线程计算器
  10. install ros indigo tf2