Python学习笔记之装饰器


Posted in Python onAugust 06, 2020

一. 什么是装饰器

知乎某大佬如是说:内裤可以用来遮羞,但是到了冬天它没法为我们防风御寒,聪明的人们发明了长裤,有了长裤后宝宝再也不冷了,装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。
装饰器本质上是Python函数,可以为已存在的对象添加额外的功能,同时装饰器还可以抽离出与函数无关的重用代码。具体应用场景如:插入日志、性能测试、事务处理、缓存、权限校验等。

换言之

装饰器不能影响原函数的功能,装饰器是独立出来的函数。谁调用它,谁就可以使用它的功能。

二.举个栗子

add的功能是计算x和y的值,我们称作功能函数。
logger的作业是在执行add函数的同时再打印了其他的信息,这部分的作为add的功能增强,我们称为装饰。
在logger里我们可以加入其他类似的功能函数,也能包装它,可以进行复用。

1.引子

#功能函数
def add(x,y):
 return x+y

#装饰函数
def logger(fn):
 print('frist')
 x = fn(4,5)
 print('second')
 return x 

print(logger(add))

#把函数add传给logger ,return x+y
#print('frist')
#print('secend')
# x = fn(4,5) ==> x = 4 y= 5 x= 4+5 = 9 
#return 9

frist
second
9

2.提取参数

x,y的参数都放在logger函数内部了,影响函数的灵活性,此处我们可以提取出来。

def add(x,y):
 return x + y

def logger(fn,*args,**kwargs):
 print('frist')
 x = fn(*args,**kwargs)
 print('second')
 return x

print(logger(add,1,y=11))

frist
second
12

3.柯里化

def add(x,y):
 return x + y
def logger(fn):
 def wrapper(*args,**kwargs):
  print('begin')
  x = fn(*args,**kwargs)
  print('end')
  return x
 return wrapper


print(logger(add)(5,y=11))

begin
end
16

懵逼ing

以下为个人理解,左边为非柯里化函数,右边是柯里化函数。

Python学习笔记之装饰器

柯里化函数

前面说过柯里化的定义,本来可以一次传入两个参数,柯里化之后。只需要传入一个函数了。。
左边传入add 和 两个参数。
右边的logger(add)是一个函数,只需要传入两个参数。logger(add)是个整体,结合成一个函数。当然这样写,我们看函数主题的部分也是不一样的。
函数的基础中说过,函数的传参必须和函数参数的定义一致。重点分析右边函数(柯里化)。
参数部分:参数传入的方式,logger函数需要传入个fn,fu的返回值是wrapper函数,wrapper函数的参数是(*args,**kwargs)所以此次就需要分两次传入参数。
第一次传入fn,再次传入wrapper函数需要的参数。所以就出现了最下边的调用方式。
print(logger(add)(5,y=50))。

返回值部分:右侧的logger函数是个嵌套函数,logger的返回值是wrapper,内层的wrapper函数返回值是x,x = fn(*args,**kwargs)。fn函数是最后调用时候传入的add函数。

懵逼 X 2。。。。

def add(x,y):
 return x + y

def logger(fn,*args,**kwargs):  def logger(fn): #参数剥离
           def newfunction(*args,**kwargs): #新定义一个函数,logger函数返回也是这个函数名字
 print('frist')       print('frist')
 x = fn(*args,**kwargs) == >    x = fn(*args,**kwargs)
 print('second')       print('second')
 return x        return x
          return newfunction

print(logger(add,1,y=11))   print(logger(add)(5,y=11)) #两次传入参数

效果如下:

def add(x,y):
 return x + y

def logger(fn): #参数剥离
 def newfunction(*args,**kwargs): #新定义一个函数,logger函数返回也是这个函数名字

  print('frist')
  x = fn(*args,**kwargs)
  print('second')
  return x

 return newfunction

print(logger(add)(5,y=11)) #两次传入参数

frist
second
16

继续懵逼的话就这样用吧。。。用多了就悟道了。。

4.装饰器语法糖

#再次变形。。。
def add(x,y):
 return x + y

def logger(fn):
 def wrapper(*args,**kwargs):
  print('begin')
  x = fn(*args,**kwargs)
  print('end')
  return x
 return wrapper

##调用方法1:
print(logger(add)(x=1111,y=1))

##调用方法2:
add = logger(add)
print(add(x=11,y=3))

##调用方法3: python给我们的语法糖 

@logger # 说明下边的函数,add 其实是 add = logger(add)
def add(x,y):
 return x + y


print(add(45,40))

begin
end
1112
begin
end
14
begin
end
85

三.复杂的栗子

import datetime
import time 

def logger(fn):
 def warp(*arges,**kwarges):
  print("arges={},kwarges={}".format(arges,kwarges)) #打印函数的两个参数
  start = datetime.datetime.now() #获取函数运行的开始时间
  ret = fn(*arges,**kwarges) #传入两个参数,调用add函数 此处有个return的值,需要一层一层的返回出去

  duratime = datetime.datetime.now() - start #获得函数的运行时间
  print("function {} took {}s".format(fn.__name__,duratime.total_seconds())) #打印函数的运行时间

  return ret #返回fn的结果 ,fn = x+y ==> 返回x+y的值。 x = 4 y= 11 ==> return 11
 return warp #返回warp的 return ==> ret 的return ==> return 11 函数的最终结果为11 

@logger
def add(x,y):
 print("oooooook")
 time.sleep(1.5)
 return x+y

print(add(4,y=11))

#如果充分理解了每个小部件,这个简单的完整版本也是很好理解的了。
#1,logger是个装饰器,而且使用了柯里化技术
#2,add 传参给logger的fn 形参,add(4,y=5)的两个参数传入给warp函数的两个形参
#
#
arges=(4,),kwarges={'y': 11}
oooooook
function add took 1.5017s
15

再次翻译

import datetime
import time 

#####################################装饰开始############################################
def logger(fn): #拿到函数名称
 def warp(*arges,**kwarges): #拿到函数带过来的参数开始装饰
  print("arges={},kwarges={}".format(arges,kwarges)) #来试试打印两个参数
  start = datetime.datetime.now() #
  ret = fn(*arges,**kwarges) # 此处调用add函数。开始执行函数,发现return语句。。ret的结果就是return。 

  duratime = datetime.datetime.now() - start #
  print("function {} took {}s".format(fn.__name__,duratime.total_seconds()))

  return ret #加工完成开始返回。warp的返回值是ret ,ret的返回值是 add函数的执行结果(原函数的功能完整的保留了) 
 return warp # logger的返回结果是warp,warp的返回值是ret ,ret的返回值是 add函数的执行结果(原函数的功能完整的保留了) 

#####################################装饰完成############################################

@logger #装饰工厂
######add是需要被装饰的函数,当你有这个想法的事情,其实事情已经开始发生了。
def add(x,y): # 此时add = logger(add) 此处前面的@logger标记就是想要让logger装饰器像一个工厂一样对add函数进行加工。
 print("oooooook")
 time.sleep(1.5)
 return x+y

print(add(4,y=11))
arges=(4,),kwarges={'y': 11}
oooooook
function add took 1.501604s
15

四.带参装饰器

1. 文档字符串

我们约定,在python函数的第一行需要对函数进行说明,使用三引号表示。
如果是英文说明,惯例首字母大写,第一行写概述,空一行,第三行写详细描述。
如果函数中有文档字符串,默认会放在函数的doc属性中,可以直接访问。

def add(x,y):
 """This is a function of addition"""
 a = x+y
 return x + y

print("function name is {}
function doc = {}

".format(add.__name__, add.__doc__))
print(help(add))
function name is add
function doc = This is a function of addition


Help on function add in module __main__:

add(x, y)
 This is a function of addition

None

2. 前面装饰器的副作用

前面装饰器基本上已经可以完成对函数进行加强的功能了,但是还有些瑕疵。比如原来函数的原属性已经被替换为装饰器的属性了。如下:

def add(x,y):
 return x + y

def logger(fn):
 "This is logger doc"
 def wrapper(*args,**kwargs):
  "This is wrapper doc"
  print('begin')
  x = fn(*args,**kwargs)
  print('end')
  return x
 return wrapper


@logger # add = logger(add)
def add(x,y):
 "This is add doc "
 print("name = {}
doc = {}".format(add.__name__,add.__doc__))
 return x + y


print(add(45,40))

#可以看出来add被装饰出来的函数(新的add)的属性已经全部改变了。

begin
name = wrapper
doc = This is wrapper doc
end
85

3. 解决方案一

三个函数:

第一个:copy原函数的属性 copy_properties
第二个:装饰器 logger
第三个:功能函数 add

def copy_properties(src, dst): # 把src的相关属性赋值给dst (fn,wrap)
 dst.__name__ = src.__name__
 dst.__doc__ = src.__doc__


def logger(fn):
 """'This is a function of logger'"""
 def wrap(*arges,**kwarges): # 
  """'This is a function of wrap'"""
  print('<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>')
  x = fn(*arges,**kwarges)
  #print("name={}
doc={}".format(add.__name__,add.__doc__))
  print('<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>') 
  return x 
 copy_properties(fn,wrap) #思考1:为什么放在这个位置调用
 return wrap

@logger
def add(x,y):
  """'This is a function of add'"""
  print("name={}
doc={}".format(add.__name__,add.__doc__))
  return x+y


print(add(4,6))

<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>
name=add
doc='This is a function of add'
<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>-<>
10

4. 解决方案二

但凡使用装饰器都会出现属性的这个问题,为什么不把copy_properties也做成装饰器呢?

三个函数:

第一个:copy原函数的装饰器 copy_properties1
第二个:装饰器 logger
第三个:功能函数 add

def copy_properties(src, dst): # 把src的相关属性赋值给dst (fn,wrap)
 dst.__name__ = src.__name__
 dst.__doc__ = src.__doc__

#利用前面的知识我们可以对copy_properties轻松进行变形
def copy_properties1(src): # 把src的相关属性赋值给dst (fn,wrap) 
 def _copy(dst):
  dst.__name__ = src.__name__
  dst.__doc__ = src.__doc__
  return dst 
 return _copy

带参装饰器:

def logger(fn): 
 """'This is a function of logger'"""
 @copy_properties1(fn) #wrap = copy_properties(fn)(wrap) 
 #== > 柯里化 两次传入参数 src = fn , dst = wrap 新的wrap函数的属性已经替换为原函数的。

 def wrap(*arges,**kwarges): #wrap = copy_properties(fn)(wrap)(*arges,**kwarges)  
  """'This is a function of wrap'"""
  print('>->->->->->->->->->->->->->->->->->->->->->->->->->')
  x = fn(*arges,**kwarges)
  print('<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<')
  return x 

 return wrap

@logger #add =logger(add)
def add(x,y):
  """'This is a function of add'"""
  print("name={}
doc={}".format(add.__name__,add.__doc__))
  return x+y



print(add(4,11))

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

Python 相关文章推荐
Python 随机生成中文验证码的实例代码
Mar 20 Python
python使用append合并两个数组的方法
Apr 28 Python
Python通过DOM和SAX方式解析XML的应用实例分享
Nov 16 Python
玩转python爬虫之爬取糗事百科段子
Feb 17 Python
python在每个字符后添加空格的实例
May 07 Python
Django 根据数据模型models创建数据表的实例
May 27 Python
由面试题加深对Django的认识理解
Jul 19 Python
浅谈Python3识别判断图片主要颜色并和颜色库进行对比的方法
Oct 25 Python
Django重设Admin密码过程解析
Feb 10 Python
Python中remove漏删和索引越界问题的解决
Mar 18 Python
Python configparser模块应用过程解析
Aug 14 Python
ubuntu16.04升级Python3.5到Python3.7的方法步骤
Aug 20 Python
用python实现前向分词最大匹配算法的示例代码
Aug 06 #Python
Python爬虫防封ip的一些技巧
Aug 06 #Python
Python无损压缩图片的示例代码
Aug 06 #Python
通过实例简单了解python yield使用方法
Aug 06 #Python
Python切片列表字符串如何实现切换
Aug 06 #Python
Python爬虫爬取微信朋友圈
Aug 06 #Python
Python变量及数据类型用法原理汇总
Aug 06 #Python
You might like
PHP实现绘制3D扇形统计图及图片缩放实例
2014/10/01 PHP
php curl 上传文件代码实例
2015/04/27 PHP
thinkphp3.2点击刷新生成验证码
2016/02/16 PHP
详解Laravel服务容器的绑定与解析
2019/11/05 PHP
利用javascript移动div层-javascript 拖动层
2009/03/22 Javascript
jquery数组过滤筛选方法grep()简介
2014/06/06 Javascript
jQuery中insertAfter()方法用法实例
2015/01/08 Javascript
Jquery中find与each方法用法实例
2015/02/04 Javascript
JavaScript实现移动端滑动选择日期功能
2016/06/21 Javascript
AngularJS 模块化详解及实例代码
2016/09/14 Javascript
原生js实现拖拽功能基本思路详解
2018/04/18 Javascript
JavaScript时间与时间戳的转换操作实例分析
2018/12/07 Javascript
JavaScript数值类型知识汇总
2019/11/17 Javascript
JSON stringify方法原理及实例解析
2020/10/23 Javascript
[03:17]2016完美“圣”典风云人物:冷冷专访
2016/12/08 DOTA
Python中常见的数据类型小结
2015/08/29 Python
详解Python验证码识别
2016/01/25 Python
python中类变量与成员变量的使用注意点总结
2017/04/29 Python
对python修改xml文件的节点值方法详解
2018/12/24 Python
Django框架文件上传与自定义图片上传路径、上传文件名操作分析
2019/05/10 Python
pyqt5使用按钮进行界面的跳转方法
2019/06/19 Python
解决在pycharm运行代码,调用CMD窗口的命令运行显示乱码问题
2019/08/23 Python
python 默认参数相关知识详解
2019/09/18 Python
Python如何定义接口和抽象类
2020/07/28 Python
image-set实现Retina屏幕下图片显示详细介绍
2012/12/24 HTML / CSS
迪卡侬比利时官网:Decathlon比利时
2019/12/28 全球购物
如何提高SQL Server的安全性
2016/07/25 面试题
Python里面如何拷贝一个对象
2014/02/17 面试题
机电专业个人求职信范文
2013/12/30 职场文书
捐书倡议书
2014/08/29 职场文书
2014新生大学四年计划书
2014/09/21 职场文书
2015年乡镇民政工作总结
2015/05/13 职场文书
化工厂员工工作总结
2015/10/15 职场文书
深入理解java.lang.String类的不可变性
2021/06/27 Java/Android
JavaScript中document.activeELement焦点元素介绍
2021/11/27 Javascript
详解Alibaba Java诊断工具Arthas查看Dubbo动态代理类
2022/04/08 Java/Android