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去掉字符串中重复字符的方法
Feb 27 Python
Python中除法使用的注意事项
Aug 21 Python
基于python yield机制的异步操作同步化编程模型
Mar 18 Python
Python实现图片转字符画的示例代码
Aug 21 Python
python smtplib发送带附件邮件小程序
May 22 Python
python+flask实现API的方法
Nov 21 Python
django 自定义过滤器(filter)处理较为复杂的变量方法
Aug 12 Python
详解Matplotlib绘图之属性设置
Aug 23 Python
使用PyCharm进行远程开发和调试的实现
Nov 04 Python
numpy.transpose()实现数组的转置例子
Dec 02 Python
python 用opencv实现霍夫线变换
Nov 27 Python
Python matplotlib多个子图绘制整合
Apr 13 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捕获Fatal error错误的方法
2014/06/11 PHP
PHP中ini_set与ini_get用法实例
2014/11/04 PHP
php中解析带中文字符的url函数分享
2015/01/20 PHP
php打开本地exe程序,js打开本地exe应用程序,并传递相关参数方法
2018/02/06 PHP
fckeditor 获取文本框值的实现代码
2009/02/09 Javascript
js解析与序列化json数据(三)json的解析探讨
2013/02/01 Javascript
js运动框架_包括图片的淡入淡出效果
2013/05/11 Javascript
jQuery常用操作方法及常用函数总结
2014/06/19 Javascript
AngularJS基础学习笔记之指令
2015/05/10 Javascript
由ReactJS的Hello world说开来
2015/07/02 Javascript
JQuery中解决重复动画的方法
2016/10/17 Javascript
基于vue.js路由参数的实例讲解——简单易懂
2017/09/07 Javascript
vue+SSM实现验证码功能
2018/12/07 Javascript
Angular2 自定义表单验证器的实现方法
2018/12/14 Javascript
express启用https使用小记
2019/05/21 Javascript
使用p5.js临摹动态图形
2019/10/23 Javascript
JavaScript变量Dom对象的所有属性
2020/04/30 Javascript
解决Mint-ui 框架Popup和Datetime Picker组件滚动穿透的问题
2020/11/04 Javascript
python 全局变量的import机制介绍
2017/09/07 Python
windows下安装Python的XlsxWriter模块方法
2018/05/03 Python
对python函数签名的方法详解
2019/01/22 Python
python3 实现函数写文件路径的正确方法
2019/11/27 Python
python django中8000端口被占用的解决
2019/12/17 Python
TensorFlow自定义损失函数来预测商品销售量
2020/02/05 Python
基于python 将列表作为参数传入函数时的测试与理解
2020/06/05 Python
网易微博Web App用HTML5开发的过程介绍
2012/06/13 HTML / CSS
John Varvatos官方网站:设计师男士时装
2017/02/08 全球购物
西班牙香水和化妆品连锁店:Druni
2019/05/05 全球购物
拖鞋店创业计划书
2014/01/15 职场文书
机修工工作职责
2014/02/21 职场文书
自我介绍演讲稿范文
2014/08/21 职场文书
销售员自我评价
2015/03/11 职场文书
行政助理岗位职责范本
2015/04/11 职场文书
Apache Calcite 实现方言转换的代码
2021/04/24 Servers
一篇文章带你搞懂Python类的相关知识
2021/05/20 Python
解决Redis启动警告问题
2022/02/24 Redis