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表示矩阵的方法分析
May 26 Python
Python中定时任务框架APScheduler的快速入门指南
Jul 06 Python
Python语言描述KNN算法与Kd树
Dec 13 Python
python随机在一张图像上截取任意大小图片的方法
Jan 24 Python
彻底理解Python中的yield关键字
Apr 01 Python
Python3.8中使用f-strings调试
May 22 Python
Python button选取本地图片并显示的实例
Jun 13 Python
python selenium循环登陆网站的实现
Nov 04 Python
python 实现多线程下载视频的代码
Nov 15 Python
Python使用进程Process模块管理资源
Mar 05 Python
pycharm配置安装autopep8自动规范代码的实现
Mar 02 Python
Python中requests库的用法详解
Jun 05 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
解析dedecms空间迁移步骤详解
2013/05/15 PHP
php递归调用删除数组空值元素的方法
2015/04/28 PHP
PHP数组实例详解
2016/06/26 PHP
基于Laravel 多个中间件的执行顺序详解
2019/10/21 PHP
js实现简单模态窗口,背景灰显
2008/11/14 Javascript
在VS2008中使用jQuery智能感应的方法
2010/12/30 Javascript
JS实现至少包含字母、大小写数字、字符的密码等级的两种方法
2015/02/03 Javascript
JavaScript设置获取和设置属性的方法
2015/03/04 Javascript
基于AngularJS实现页面滚动到底自动加载数据的功能
2015/10/16 Javascript
jQuery简单动画变换效果实例分析
2016/07/04 Javascript
微信小程序登录态控制深入分析
2017/04/12 Javascript
详解react native页面间传递数据的几种方式
2018/11/07 Javascript
微信小程序页面传多个参数跳转页面的实现方法
2019/05/17 Javascript
Vue 3.0 前瞻Vue Function API新特性体验
2019/08/12 Javascript
解决vue项目input输入框双向绑定数据不实时生效问题
2020/08/05 Javascript
js实现拖拽与碰撞检测
2020/09/18 Javascript
javascript全局自定义鼠标右键菜单
2020/12/08 Javascript
[42:20]Secret vs Liquid 2019国际邀请赛小组赛 BO2 第二场 8.15
2019/08/17 DOTA
python正则表达式match和search用法实例
2015/03/26 Python
Python实现Linux中的du命令
2017/06/12 Python
浅谈python中np.array的shape( ,)与( ,1)的区别
2018/06/04 Python
python2 与 python3 实现共存的方法
2018/07/12 Python
Python3获取电脑IP、主机名、Mac地址的方法示例
2019/04/11 Python
Pycharm创建项目时如何自动添加头部信息
2019/11/14 Python
CSS3中:nth-child和:nth-of-type的区别深入理解
2014/03/10 HTML / CSS
html5 video标签屏蔽右键视频另存为的js代码
2013/11/12 HTML / CSS
使用html5 canvas 画时钟代码实例分享
2015/11/11 HTML / CSS
如何实现jdbc性能优化
2012/07/30 面试题
vue+django实现下载文件的示例
2021/03/24 Vue.js
少年闰土教学反思
2014/02/22 职场文书
交通事故协议书
2014/04/15 职场文书
2015财务年终工作总结范文
2015/05/22 职场文书
实践论读书笔记
2015/06/29 职场文书
反邪教教育心得体会
2016/01/15 职场文书
python munch库的使用解析
2021/05/25 Python
面试官问我Mysql的存储引擎了解多少
2022/08/05 MySQL