这篇文章主要介绍了Python装饰器用法,结合实例形式总结分析了Python常用装饰器的概念、功能、使用方法及相关注意事项

一、装饰器是什么

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

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

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

二、为什么需要装饰器

  • 1、先来看一个简单例子: ```python

    In [12]: def foo():

    ...: print('I am foo')

    ...:

- 2、增加需求
现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:
```python
In [13]: def foo():
...: print('I am foo')
...: print('foo is running')
...:
  • 3、又有需求

    假设现在有100个函数需要增加这个需求,并且后续可能还要对这一百个函数都增加执行前打印日志的需求。此时再一个个改会造成大量雷同的代码。为了减少重复写代码,我们可以这样做,重新定义一个函数:专门处理日志 ,日志处理完之后再执行真正的业务代码。
In [14]: def use_logging(func):
...: print('%s is running' % func.__name__)
...: func()
...: def bar():
...: print('I am bar') ...: use_logging(bar)
...:
#输出:
bar is running
I am bar

通过以上use_logging函数我们增加了日志功能,不管以后有多少函数需要增加日志或者修改日志的格式我们只需要修改use_logging函数,并执行use_logging(被装饰的函数)就达到了我们想要的效果。

In [18]: def use_logging(func):  # use_logging装饰函数
...: print('%s is running' % func.__name__)
...: return func # 使用@ 语法糖要求装饰函数必须return一个函数对象
...:
...: @use_logging # @ 符号作为装饰器的语法糖
...: def bar():
...: print('i am bar')
...: bar()
...:
#输出
bar is running
i am bar

三、基础装饰器入门

  • 1、装饰器语法糖

    Python提供了@ 符号作为装饰器的语法糖,使我们更方便的应用装饰函数。但是用语法糖要求装饰函数必须return一个函数对象。因此我们将上面的func函数使用内嵌函数包裹并return。

装饰器相当于执行了装饰函数use_logging后又返回被装饰函数bar,因此bar()被调用的时候相当于执行了两个函数。等价于use_logging(bar)()

In [19]: def use_logging(func):
...: def _deco():
...: print('%s is running' %func.__name__)
...: func()
...: return _deco
...: In [20]: @use_logging #注意: 把@ use_logging放在bar()函数定义的地方,相当于执行了语句: bar = use_logging(bar)
...: def bar(): #程序首先调用use_logging(bar),得到的返回结果赋值给bar(注意:这里在Python中函数名只是个指向函数首地址的函数指针而已)
...: print(' i am bar')
...: In [21]: bar() # bar()被调用的时候相当于执行了两个函数。等价于use_logging(bar)()
bar is running
i am bar In [22]: bar
Out[22]: <function __main__.use_logging.<locals>._deco>

补充: 装饰器其实就是一个以函数作为参数并返回一个替换函数的可执行函数。让我们从简单的开始,直到能写出实用的装饰器。

In [9]: def outer(some_func):
...: def inner():
...: print('before some_func')
...: ret = some_func() #1
...: return ret+1
...: return inner
...: In [10]: def foo():
...: return 1
...: In [11]: decorated = outer(foo) #2 In [12]: decorated()
before some_func
Out[12]: 2

请仔细看这个装饰器示例。首先,定义了一个带单个参数 some_func 的名为 outer 的函数。然后在 outer 内部定义了一个内嵌函数 inner。inner 函数将打印一行字符串然后调用 some_func,并在 #1 处获取其返回值。在每次 outer 被调用时,some_func 的值可能都会不同,但不论 some_func 是什么函数,都将调用它。最后,inner 返回 some_func() 的返回值加 1。在 #2 处可以看到,当调用赋值给 decorated 的返回函数时,得到的是一行文本输出和返回值 2,而非期望的调用 foo 的返回值 1。

我们可以说变量 decorated 是 foo 的装饰版——即 foo 加上一些东西。事实上,如果写了一个实用的装饰器,可能会想用装饰版来代替 foo,这样就总能得到“附带其他东西”的 foo 版本。用不着学习任何新的语法,通过将包含函数的变量重新赋值就能轻松做到这一点:

In [13]: foo = outer(foo)

In [14]: foo
Out[14]: <function __main__.outer.<locals>.inner> In [15]: foo()
before some_func
Out[15]: 2

现在任意调用 foo() 都不会得到原来的 foo,而是新的装饰器版!

文章来源:简单 12 步理解 Python 装饰器


  • 2、对带参数的函数进行装饰

    现在我们的参数需要传入两个参数并计算值,因此我们需要对内层函数进行改动传入我们的两个参数a和b,等价于use_logging(bar)(1,2)
In [23]: def use_logging(func):
...: def _deco(a,b):
...: print('%s is running' % func.__name__)
...: func(a,b)
...: return _deco
...: In [24]: @use_logging
...: def bar(a,b):
...: print('i am bar: %s' %(a+b))
...: bar(1,2)
...:
bar is running
i am bar: 3

我们装饰的函数可能参数的个数和类型都不一样,每一次我们都需要对装饰器做修改吗?这样做当然是不科学的,因此我们使用Python的变长参数 *args和**kwargs来解决我们的参数问题。

  • 3、函数参数数量不确定

    不带参数装饰器版本,这个格式适用于不带参数的装饰器。

经过以下修改我们已经适应了各种长度和类型的参数。这个版本的装饰器已经可以任意类型的无参数函数。

In [37]: def use_logging(func):
...: def _deco(*args,**kwargs):
...: print('%s is running' % func.__name__)
...: func(*args,**kwargs)
...: return _deco
...: In [38]: @use_logging #不带参数装饰器
...: def bar(a,b):
...: print(' i am bar: %s' %(a+b))
...: In [39]: @use_logging #不带参数装饰器
...: def foo(a,b,c):
...: print(' i am bar: %s' %(a+b+c))
...: In [40]: bar(2,3)
bar is running
i am bar: 5 In [41]: foo(1,4,7)
foo is running
i am bar: 12
  • 4、装饰器带参数

    带参数的装饰器,这个格式适用于带参数的装饰器。

某些情况,我们需要让装饰器带上参数,那就需要编写一个返回一个装饰器的高阶函数,写出来会更复杂。例如:

In [45]: def use_logging(level):
...: def _deco(func):
...: def _deco(*args,**kwargs):
...: if level == 'warn':
...: print('%s is running' % func.__name__)
...: return func(*args,**kwargs)
...: return _deco
...: return _deco
...: In [46]: @use_logging(level = 'warn') #带参数的装饰器
...: def bar(a,b):
...: print('i am bar: %s' %(a+b))
...: In [47]: bar(2,5)
bar is running
i am bar: 7
  • 5、functools.wraps

    使用装饰器极大地复用了代码,但是他有一个缺点就是原函数的元信息不见了,比如函数的docstring__name__参数列表,先看例子:
In [48]: def use_logging(func):
...: def _deco(*args,**kwargs):
...: print('%s is running' %func.__name__)
...: func(*args,**kwargs)
...: return _deco
...: In [49]: @use_logging
...: def bar():
...: print('i am bar')
...: print(bar.__name__)
...: In [50]: bar()
bar is running
i am bar
_deco #函数名变为_deco而不是bar,这个情况在使用反射的特性的时候就会造成问题。因此引入了functools.wraps解决这个问题。

使用functools.warps:

In [54]: import functools
...: def use_logging(func):
...: @functools.wraps(func)
...: def _deco(*args,**kwargs):
...: print("%s is running" % func.__name__)
...: func(*args,**kwargs)
...: return _deco
...: In [55]: @use_logging
...: def bar():
...: print('i am bar')
...: print(bar.__name__)
...: In [56]: bar()
bar is running
i am bar
bar #bar.__name__输出的结果是bar,正是我们想要的。
  • 6、实现带参数和不带参数的装饰器自适应
In [57]: import functools

In [58]: def use_logging(arg):
...: if callable(arg): #判断传入的参数是否是函数,不带参数的装饰器调用这个分支
...: @functools.wraps(arg)
...: def _deco1(*args,**kwargs):
...: print('%s is running' %arg.__name__)
...: arg(*args,**kwargs)
...: return _deco1
...: else: #带参数的装饰器调用这个分支
...: def _deco1(func):
...: @functools.wraps(func)
...: def _deco2(*args,**kwargs):
...: if arg == 'warn':
...: print('warn %s is running' %func.__name__)
...: return func(*args,**kwargs)
...: return _deco2
...: return _deco1
...: In [59]: @use_logging('warn') #带有参数的装饰器执行else分支语句
...: def bar():
...: print('i am bar')
...: print(bar.__name__)
...: In [60]: bar()
warn bar is running #执行的是print('warn %s is running' %func.__name__)
i am bar
bar In [61]: @use_logging #不带参数的装饰器执行if分支语句
...: def bar():
...: print('i am bar')
...: print(bar.__name__)
...: In [62]: bar()
bar is running #执行的是 print("%s is running" % arg.__name__)
i am bar
bar

三、类装饰器

使用类装饰器可以实现带参数装饰器的效果,但实现的更加优雅简洁,而且可以通过继承来灵活的扩展。

  • 1、类装饰器
In [68]:   class loging(object):  #类装饰器
...: def __init__(self,level="warn"):
...: self.level = level
...:
...: def __call__(self,func):
...: @functools.wraps(func)
...: def _deco(*args, **kwargs):
...: if self.level == "warn":
...: self.notify(func)
...: return func(*args, **kwargs)
...: return _deco
...:
...: def notify(self,func):
...: # loging只打日志,不做别的
...: print ("%s is running" % func.__name__)
...:
...:
...: @loging(level="warn") #执行__call__方法
...: def bar(a,b):
...: print('i am bar:%s'%(a+b)) In [69]: bar(1,3)
bar is running
i am bar:4
  • 2、继承扩展类装饰器
In [72]: class email_loging(loging):   #括号内的loging参数为上面的loging类装饰器,email_loging继承并扩展了loging类装饰器
...: """
...: 一个loging的实现版本,可以在函数调用时发送email给管理员
...: """
...: def __init__(self,email = 'admin = chj@ncu.edu.com',*args,**kwargs):
...: self.email = email
...: super(email_loging,self).__init__(*args,**kwargs)
...:
...: def notify(self,func):
...: #发送一份email到self.email
...: print('%s is running' %func.__name__)
...: print('sending email to %s' %self.email)
...: In [73]: @email_loging(level = 'warn')
...: def bar(a,b):
...: print('i am bar: %s' %(a+b))
...: In [74]: bar(1,3)
bar is running
sending email to admin = chj@ncu.edu.com
i am bar: 4

更多参考:

  1. 知乎:如何理解Python装饰器?
  2. 伯乐在线:简单 12 步理解 Python 装饰器
  3. Python装饰器用法实例总结
  4. Python装饰器原理与简单用法实例分析

Python的装饰器实例用法小结的更多相关文章

  1. Python函数装饰器高级用法

    在了解了Python函数装饰器基础知识和闭包之后,开始正式学习函数装饰器. 典型的函数装饰器 以下示例定义了一个装饰器,输出函数的运行时间: 函数装饰器和闭包紧密结合,入参func代表被装饰函数,通过 ...

  2. Python @函数装饰器及用法

    1.函数装饰器的工作原理 函数装饰器的工作原理是怎样的呢?假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示: #funA 作为装饰器函数 def funA(fn): #... fn ...

  3. Python @函数装饰器及用法(超级详细)

    函数装饰器的工作原理是怎样的呢?假设用 funA() 函数装饰器去装饰 funB() 函数,如下所示: #funA 作为装饰器函数 def funA(fn): #... fn() # 执行传入的fn参 ...

  4. Python中装饰器的用法

    定义: 装饰器本身就是一个函数 为其他函数提供附加功能 不改变源代码 不改变原调用方式 装饰器=高阶函数+嵌套函数 知识点: 函数本身就是一个变量(意味着可以被复制给一个变量:test=test(1) ...

  5. Python 装饰器实例

    retry 偶然看到一篇文章,想到了前几天的一个需求,git pull性能不稳,需要加入重试机制,正好这个装饰器的实例符合这样的场景. # coding:utf-8 import time impor ...

  6. Python函数装饰器原理与用法详解《摘》

    本文实例讲述了Python函数装饰器原理与用法.分享给大家供大家参考,具体如下: 装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值 ...

  7. 简单说明Python中的装饰器的用法

    简单说明Python中的装饰器的用法 这篇文章主要简单说明了Python中的装饰器的用法,装饰器在Python的进阶学习中非常重要,示例代码基于Python2.x,需要的朋友可以参考下   装饰器对与 ...

  8. python 装饰器(三):装饰器实例(一)

    示例 7-15 定义了一个装饰器,它会在每次调用被装饰的函数时计时,然后把经过的时间.传入的参数和调用的结果打印出来.示例 7-15 一个简单的装饰器,输出函数的运行时间 import time de ...

  9. 详解Python的装饰器

    Python中的装饰器是你进入Python大门的一道坎,不管你跨不跨过去它都在那里. 为什么需要装饰器 我们假设你的程序实现了say_hello()和say_goodbye()两个函数. def sa ...

随机推荐

  1. Word常用操作笔记

    总忘,在这记一下,以下以WPS为例,WORD大同小异,别看网上那些乱写的,就哥这个稳定好使: 1. 在指定页面及其后开始插入页码 2. 修改页码中的总页数 -> 选中总页数的域 -> SH ...

  2. SSH框架解析

    当也许能够说是与你的初恋.妙龄的少女開始有些羞涩.是时候说说SHH的故事了.SHH是指Spring+Struts+Hibernate.还记研究生复试那天的面试,老师说了一句话Spring的核心是控制反 ...

  3. C#二进制序列化和反序列化

    public class WRSerializable { public static void SerializeToFile<T>(T _description, string _fi ...

  4. windows下 兼容Python2和Python3

    windows下同时安装了python2和python3时,都可以配置环境变量,如果在命令行里输入python命令,windows会去环境变量里寻找Python的安装位置,如果先找到pytoon2的, ...

  5. 【BZOJ1812】[Ioi2005]riv 树形DP

    [BZOJ1812][Ioi2005]riv Description 几乎整个Byteland王国都被森林和河流所覆盖.小点的河汇聚到一起,形成了稍大点的河.就这样,所有的河水都汇聚并流进了一条大河, ...

  6. EasyNVR现场部署搭配EasyNVS云端集中控制应用于幼儿园直播场景的最佳方案!

    在之前的介绍中,我们已经介绍了很多EasyNVR成功应用于幼儿园类教育直播的场景,例如<EasyDarwin幼教云视频平台在幼教平台领域大放异彩!>.<基于EasyDarwin云视频 ...

  7. 第七课 nodejs请求响应

    1 server.js 接收请求接收请求参数 和接收完成需要对request增加两个监听事件 var http = require('http');var url = require('url');f ...

  8. window子对象

    Window 子对象 (1)Location 对象 Location 对象包含有关当前 URL(统一资源定位符) 的信息.(Uniform Resource Location) Location 对象 ...

  9. gnu libiconv(可以下载)

    Chinese EUC-CN, HZ, GBK, CP936, GB18030, EUC-TW, BIG5, CP950, BIG5-HKSCS, BIG5-HKSCS:2004, BIG5-HKSC ...

  10. 浅析 Pycharm 内存、cpu 占用率

    浅析 Pycharm  内存.cpu 占用率 本机配置参数: ------------------------------------------ Windows 10 专业版   X64 ----- ...