python之装饰器

1. 什么是装饰器

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

换言之

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

2.举个栗子

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

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

<img src="http://qiniu.mykernel.cn/klh.png" alt="柯里化函数"/>

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

3.复杂的栗子

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

4.带参装饰器

1. 文档字符串

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

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

print("function name is {}\nfunction doc = {}\n\n".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 = {}\ndoc = {}".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. 解决方案一

三个函数:

  1. copy原函数的属性 copy_properties
  2. 装饰器 logger
  3. 功能函数 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={}\ndoc={}".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={}\ndoc={}".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也做成装饰器呢?

三个函数:

  1. copy原函数的装饰器 copy_properties1
  2. 装饰器 logger
  3. 功能函数 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={}\ndoc={}".format(add.__name__,add.__doc__))
        return x+y



print(add(4,11))
>->->->->->->->->->->->->->->->->->->->->->->->->->
name=add
doc='This is a function of add'
<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<-<
15

更多欢迎访问:http://www.mykernel.cn/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • 1.认识装饰器 在python中,对于一个函数,若想在其运行前后做点什么,那么装饰器是再好不过的选择,话不多说,上...
    howie6879阅读 1,413评论 4 20
  • Python之装饰器 装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功...
    ikaroskun阅读 354评论 0 0
  • 装饰器的作用: 装饰模式有很多经典的使用场景,例如插入日志、性能测试、事务处理等等,有了装饰器,就可以提取大量函数...
    冰西瓜大郎阅读 223评论 0 0
  • 我不是艺术家 我画出的蒙娜丽莎的微笑不如你 奏出贝多芬的乐章没有你绚丽 在这幕静默的爱你的舞台剧里 快要干涸的感性...
    芍药煮鱼阅读 253评论 0 0
  • 还记得我第一次写读后感的时候是在小学,是老师布置的一次家庭作业,那时候我皮的很,作业从来不想做,偏偏又怕老师怕的跟...
    乱玉阅读 166评论 0 0