热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Python学习笔记之装饰器_python

这篇文章主要介绍了Python装饰器的相关资料,文中讲解非常细致,代码帮助大家更好的理解和学习,感

一. 什么是装饰器

知乎某大佬如是说:内裤可以用来遮羞,但是到了冬天它没法为我们防风御寒,聪明的人们发明了长裤,有了长裤后宝宝再也不冷了,装饰器就像我们这里说的长裤,在不影响内裤作用的前提下,给我们的身子提供了保暖的功效。
装饰器本质上是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

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

柯里化函数

前面说过柯里化的定义,本来可以一次传入两个参数,柯里化之后。只需要传入一个函数了。。
左边传入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))

推荐阅读
  • vue使用
    关键词: ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 展开全部下面的代码是创建一个立方体Thisexamplescreatesanddisplaysasimplebox.#Thefirstlineloadstheinit_disp ... [详细]
  • 不同优化算法的比较分析及实验验证
    本文介绍了神经网络优化中常用的优化方法,包括学习率调整和梯度估计修正,并通过实验验证了不同优化算法的效果。实验结果表明,Adam算法在综合考虑学习率调整和梯度估计修正方面表现较好。该研究对于优化神经网络的训练过程具有指导意义。 ... [详细]
  • 在重复造轮子的情况下用ProxyServlet反向代理来减少工作量
    像不少公司内部不同团队都会自己研发自己工具产品,当各个产品逐渐成熟,到达了一定的发展瓶颈,同时每个产品都有着自己的入口,用户 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 本文介绍了机器学习手册中关于日期和时区操作的重要性以及其在实际应用中的作用。文章以一个故事为背景,描述了学童们面对老先生的教导时的反应,以及上官如在这个过程中的表现。同时,文章也提到了顾慎为对上官如的恨意以及他们之间的矛盾源于早年的结局。最后,文章强调了日期和时区操作在机器学习中的重要性,并指出了其在实际应用中的作用和意义。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
author-avatar
嘉兴布奇乐乐园
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有