这篇文章主要介绍了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. spark通过合理设置spark.default.parallelism参数提高执行效率

    spark中有partition的概念(和slice是同一个概念,在spark1.2中官网已经做出了说明),一般每个partition对应一个task.在我的测试过程中,如果没有设置spark.def ...

  2. MathType公式行距设置的方法

    在使用普通的文档编辑器编辑数学公式的时候,大家会发现一些数学上特殊的符号.公式很难给编辑出来,有时候就算编辑出来了也不符号一些学术的规范.这个时候就可以使用MathType这款公式编辑器来编辑.但是在 ...

  3. 服务器之ipmitool

    一般命令 raw #发送一个原始的IPMI请求,并且打印回复信息. Lan #配置网络(lan)信道(channel) chassis #查看底盘的状态和设置电源 event #向BMC发送一个已经定 ...

  4. jQuery插件-json2.js

    from:http://blog.csdn.net/gjb724332682/article/details/46682743 前言 json2.js是一个json插件,下载地址:https://gi ...

  5. 排序算法 c实现

    c语言实现插入排序.冒泡排序.选择排序.快速排序.堆排序.归并排序.希尔排序示例,需要的朋友可以参考下     实现以下排序 插入排序O(n^2) 冒泡排序 O(n^2) 选择排序 O(n^2) 快速 ...

  6. 关于java后台如何接收xml格式的数据

    业务场景:用户发送下单请求,格式为xml格式,服务器接收数据完成下单,并返回结果给客户. 请求格式: <request> <head> <sign></sig ...

  7. CodeIgniter框架——访问方式 URI 分配变量 数据库操作

    1.访问方式: CodeIgniter 的访问URL使用的是pathinfo,入口文件/控制器/方法(/参数列表) eg:localhost/index.php/welcome/index/id 第一 ...

  8. hdu 5471(状压DP or 容斥)

    想了最复杂的思路,用了最纠结的方法,花了最长的时间,蒙了一种规律然后莫名其妙的过了. MD 我也太淼了. 后面想了下用状压好像还是挺好写的,而且复杂度也不高.推出的这个容斥的规律也没完全想透我就CAO ...

  9. 1028 大数乘法 V2(FFT or py)

    1028 大数乘法 V2 基准时间限制:2 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 给出2个大整数A,B,计算A*B的结果.   Input 第1行:大数A 第2行:大数B ...

  10. IDEA中的lombok插件安装以及各注解的详细介绍

    IDEA中的lombok插件安装以及各注解的详细介绍 其实对于我们来说, 写好实体类后,直接用快捷方式生成get,set方法,还有 构造方法就行了,但是对于字段比较多的, 如果修改一个属性的话,就要再 ...