引入装璜器

如果想在一个函数执行前后执行一些别的代码,比方打印一点日志用来输入这个函数的调用状况那应该怎么做呢?

#!/usr/bin/env python# coding=utf-8def logger(fn):                                    # 函数作为参数即fn能够为任何参数    def wrap(*args, **kwargs):                    # 可变参数args和kwargs        print('call {}'.format(fn.__name__))            ret = fn(*args, **kwargs)                # 函数调用时的参数解构        print('{} called'.format(fn.__name__))        return ret                                # 返回函数的返回值    return wrapdef add(x, y):    return x + ylogger_add = logger(add)print(logger_add.__name__)print(logger_add)ret = logger_add(3, 5)print(ret)#输入后果:wrap<function logger.<locals>.wrap at 0x7fba35f4fe18>call addadd called8

<!--more-->

也能够用以下形式来实现这种成果

@logger                                                                                  def add(x, y):                                                                                return x + y                                                                         ret = add(3, 5)                                                                      print(ret) # 输入后果:call addadd called8

这就是Python装璜器的一个简略应用

什么是装璜器?

装璜器是用于软件设计模式的名称。 装璜器能够动静地扭转函数,办法或类的性能,而不用间接应用子类或扭转被装璜的函数的源代码。Python装璜器是对Python语法的一种非凡扭转,它容许咱们更不便地批改函数,办法以及类。

当咱们依照以下形式编写代码时:

@loggerdef add(x, y):    ...

和独自执行上面的步骤是一样的:

def add(x, y):    ...logger_add = logger(add)

装璜器外部的代码个别会创立一个新的函数,利用*args**kwargs来承受任意的参数,上述代码中的wrap()函数就是这样的。在这个函数外部,咱们须要调用原来的输出函数(即被包装的函数,它是装璜器的输出参数)并返回它的后果。然而也能够增加任何想要增加的代码,比方在上述代码中输入函数的调用状况,也能够增加计时解决等等。这个新创建的wrap函数会作为装璜器的后果返回,取代了原来的函数。

所以在Python中,装璜器的参数是一个函数, 返回值是一个函数的函数

装璜器的示例:计时解决

写一个装璜器,用来计算一个函数的执行工夫

import timedef timethis(fn):    def wrap(*args, **kwargs):        start = time.time()        ret = fn(*args, **kwargs)        end = time.time()        print(fn.__name__, end - start)        return ret    return wrap

如果要对add函数计时:

@timethisdef add(x, y):    return x + yret = add(3, 5)print(ret)# 输入后果add 1.9073486328125e-068

如果要对sleep函数计时:

@timethisdef sleep(x):    time.sleep(x)sleep(3)# 输入后果sleep 3.003262519836426

保留被装璜函数的元信息

什么是函数的元信息

比方装璜器的名称,装璜器的doc等等。咱们能够应用dir函数列出函数的所有元信息:dir(sleep),输入后果如下

['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

能够看到有很多的元信息,咱们比拟罕用的是__name____doc__这两个属性\

而且__doc__属性也就是函数的文档信息,能够通过help函数查看失去

为什么要保留被装璜函数的元信息

改写装璜器的利用1:计时解决中的sleep函数如下:

@timeitdef sleep(x):    '''This function is sleep.'''    time.sleep(x)sleep(3)print(sleep.__name__)print(sleep.__doc__)

以上代码输入后果如下:

3.0032713413238525wrapNone

能够发现sleep函数的__name__是wrap,而不是sleep,而__doc__属性为空,而不是sleep函数的docstring。也就是说通过装璜器装璜过后的函数的元信息产生了扭转,这时候如果程序须要函数的元信息,那么就有问题了。

如何保留被装璜函数的元信息

计划1:手动给被装璜函数的元信息赋值

__name____doc__这两个属性为例

import timedef timeit(fn):    def wrap(*args, **kwargs):        start = time.time()        ret = fn(*args, **kwargs)        end = time.time()        print(end - start)        return ret    wrap.__doc__ = fn.__doc__    # 手动赋值__doc__信息    wrap.__name__ = fn.__name__    # 手动赋值__name__信息    return wrap@timeitdef sleep(x):    '''This function is sleep.'''    time.sleep(x)if __name__ == "__main__":    sleep(3)    # print(dir(sleep))    print(sleep.__name__)    print(sleep.__doc__)

输入后果如下

3.004547119140625sleepThis function is sleep.

能够发现,__name____doc__这两个属性的确赋值胜利了。

咱们能够将元信息赋值的过程改写为函数,如下

import timedef copy_properties(src, dst):    # 将元信息赋值的过程改成函数copy_properties    dst.__name__ = src.__name__    dst.__doc__ = src.__doc__def timeit(fn):    def wrap(*args, **kwargs):        start = time.time()        ret = fn(*args, **kwargs)        end = time.time()        print(end - start)        return ret    copy_properties(fn, wrap)    # 调用copy_properties函数批改元信息    return wrap@timeitdef sleep(x):    '''This function is sleep.'''    time.sleep(x)if __name__ == "__main__":    sleep(3)    # print(dir(sleep))    print(sleep.__name__)    print(sleep.__doc__)

这样批改后,同样能够解决问题。

持续批改copy_properties函数,使得copy_properties能够返回一个函数

def copy_properties(src):    def _copy(dst):    # 内置一个_copy函数便于返回        dst.__name__ = src.__name__        dst.__doc__ = src.__doc__    return _copydef timeit(fn):    def wrap(*args, **kwargs):        start = time.time()        ret = fn(*args, **kwargs)        end = time.time()        print(end - start)        return ret    copy_properties(fn)(wrap)    # 调用copy_properties函数    return wrap

同样能够问题。

如果持续批改copy_properties函数,使得_copy函数是一个装璜器,传入dst,返回dst,批改如下:

def copy_properties(src):    # 先固定dst,传入src    def _copy(dst):    # 传入dst        dst.__name__ = src.__name__        dst.__doc__ = src.__doc__        return dst    # 返回dst    return _copy    # 返回一个装璜器def timeit(fn):    @copy_properties(fn)    # 带参数装璜器的应用办法    def wrap(*args, **kwargs):        start = time.time()        ret = fn(*args, **kwargs)        end = time.time()        print(end - start)        return ret    return wrap

copy_properties在此处返回一个带参数的装璜器,因而能够间接依照装璜器的应用办法来装璜wrap函数,这个批改copy_properties函数的过程称为函数的柯里化。

计划2:应用functools库的@wraps装璜器

functools库的@wraps装璜器实质上就是copy_properties函数的高级版本:蕴含更多的函数元信息。首先查看wrap装璜器的帮忙信息:

import functoolshelp(functools.wraps)

wrap装璜器函数的原型是:

wraps(wrapped, assigned=('module', 'name', 'qualname', 'doc', 'annotations'), updated=('dict',))

所以这个装璜器会复制module等元信息,然而也不是所有的元信息,并且会更新dict。

应用示例如下:

import timeimport functoolsdef timeit(fn):    @functools.wraps(fn)    # wraps装璜器的应用    def wrap(*args, **kwargs):        start = time.time()        ret = fn(*args, **kwargs)        end = time.time()        print(end - start)        return ret    return wrapdef sleep(x):    time.sleep(x)print(sleep.__name__)print(sleep.__doc__)

编写一个带参数的装璜器

如果上述的timeit装璜器,咱们须要输入执行工夫超过若干秒(比方一秒)的函数的名称和执行工夫,那么就须要给装璜器传入一个参数s,示意传入的工夫距离,默认为1s。

咱们能够给写好的装璜器外面包一个函数timeitS,工夫距离s作为这个函数的参数传入,并且对内层的函数可见,而后这个函数返回写好的装璜器。

import timeimport functoolsdef timeitS(s):    def timeit(fn):        @functools.wraps(fn)        def wrap(*args, **kwargs):            start = time.time()            ret = fn(*args, **kwargs)            end = time.time()            if end - start > s:                print('call {} takes {}s'.format(fn.__name__, end - start))            else:                print('call {} takes {}s less than {}'.format(fn.__name__, end - start, s))            return ret        return wrap    return timeit@timeitS(2)def sleep(x):    time.sleep(x)sleep(3)sleep(1)

输入后果如下:

call sleep takes 3.001342535018921scall sleep takes 1.000471830368042s less than 2

所以,咱们能够将带参数的装璜器了解为:

  • 带参数的装璜器就是一个函数, 这个函数返回一个不带参数的装璜器

记得帮我点赞哦!

精心整顿了计算机各个方向的从入门、进阶、实战的视频课程和电子书,依照目录正当分类,总能找到你须要的学习材料,还在等什么?快去关注下载吧!!!

朝思暮想,必有回响,小伙伴们帮我点个赞吧,非常感谢。

我是职场亮哥,YY高级软件工程师、四年工作教训,回绝咸鱼争当龙头的斜杠程序员。

听我说,提高多,程序人生一把梭

如果有幸能帮到你,请帮我点个【赞】,给个关注,如果能顺带评论给个激励,将不胜感激。

职场亮哥文章列表:更多文章

自己所有文章、答复都与版权保护平台有单干,著作权归职场亮哥所有,未经受权,转载必究!