最近浏览《晦涩的python》看见其用函数写装璜器局部写的很好,想写一些本人的读书笔记。家喻户晓,装璜器是python学习过程中的一道门槛,初学者学习时往往是知其然,不知其所以然,这样的后果是导致一段时间后会忘记掉该局部内容,只好再次去学习,拉高了学习老本。

想学好python的装璜器,须要明确一下几点;

1:闭包

1)函数嵌套
2)外部函数应用内部函数的变量
3)内部函数的返回值为外部函数

接下来看看《晦涩的python》中的例子,我略微批改了一下:

>>> def make_averager(series=[]):...     def averager(new_value):...             series.append(new_value)...             total = sum(series)...             return total/len(series)...     return averager...>>> avg = make_averager()>>> avg<function make_averager.<locals>.averager at 0x10b82cb00>>>> avg(10)10.0>>> avg(11)10.5>>> avg(12)11.0

函数 make_averager 实现了一个 计算以后所有数字的平均值的性能,一直的增加一个值,而后计算以后的平均值。

avg这个对象内存地址指向了make_averager这个函数的外部函数中,而且avg通过一直的增加值进行平均值计算,按理说在这个外部函数没有存储new_value的空间,而且在make_averager对avg赋值后,函数返回后series这个变量也应该隐没了,然而avg却仍然能够进行计算。

这就是闭包,外部函数averager应用里面的自在变量,也就是属于make_averager的局部变量series

>>> avg.__code__.co_varnames('new_value', 'total')>>> avg.__code__.co_freevars('series',)

能够发现avg的自在变量是make_averager的局部变量,就是说闭包里的外部函数能够应用内部函数的变量,即咱们下面提到的第二点:“外部函数应用内部函数的变量”,注:自在变量只能read,并不能write,不然会提醒本地变量并没有赋值的谬误,咱们举的例子没遇到这个问题,因为咱们没有给 series 赋值,咱们只是调 用 series.append,并把它传给 sum 和 len。也就是说,咱们利用了 列表是可变的对象这一事实。下图是书中提供的闭包范围图:

2:装璜器的实现

所谓装璜器,就是在不扭转根底函数的性能上再次给它封装一层,达到咱们想要的目标,接下来我举个简略的例子:

deco_demo.py

  1 def col(func):  2     def inner(*args, **kwargs):  3         print(func.__name__)  4         print(locals())  5         print(inner.__code__.co_varnames)  6         print(inner.__code__.co_freevars)  7         return func(*args, **kwargs)  8     return inner  9 10 11 @col 12 def new_add(x): 13     return x+2 14 15 16 def new_add_1(x): 17     return x+3 18 19 20 print(new_add(3)) 21 22 new_add_1 = col(new_add_1) 23 print(new_add_1(3))

下方是它的返回后果:

new_add{'args': (3,), 'kwargs': {}, 'func': <function new_add at 0x10d32aa70>, 'inner': <function col.<locals>.inner at 0x10d32acb0>}('args', 'kwargs')('func', 'inner')5new_add_1{'args': (3,), 'kwargs': {}, 'func': <function new_add_1 at 0x10d32add0>, 'inner': <function col.<locals>.inner at 0x10d32a8c0>}('args', 'kwargs')('func', 'inner')6

1-8:是定义的一个简略装璜器,

3:打印当被装璜函数的名字

4:打印inner这个外部函数中的所有变量

5:打印以后inner的局部变量;

6:则打印自在变量;

11-13:润饰了一个简略函数

16,22,23:@这个语法糖,背地实现的过程;

也就是说col(new_add)返回的是以后的外部函数的内存地址,而这个调用这个外部函数时会应用自在变量func即col的局部变量,进而达到装璜器的目标;

有参数的装璜器实现

既然无参数的装璜器即@col ,通过外部函数的形式装璜根底函数,那么咱们调用有参数的装璜器 则能够再本来的根底即函数col再封装一层函数,使其达到能够通过装璜器传参数的目标

  1 from functools import wraps  2  3  4 def col(string="hello world"):  5     def decorate(func):  6         @wraps(func)  7         def inner(*args, **kwargs):  8             print(string)  9             return func(*args, **kwargs) 10         return inner 11     return decorate 12 13 14 @col() 15 def new_add(x): 16     return x+2 17 18 19 @col("hello python") 20 def new_add_1(x): 21     return x+3 22 23 24 def new_add_2(x): 25     return x+4 26 27 28 print(new_add(1)) 29 print(new_add_1(1)) 30 31 32 new_add_2 = col("hello china")(new_add_2) 33 print(new_add_2(1))

导入wrap是为了修复这个装璜器的名称,new_add.__name__调用时指向被装璜的函数,而不是外部函数,有趣味的小伙伴能够去理解一下;

4-11:实现了一个带参数的装璜器,最外层返回的是咱们真正的装璜器;

32-33:则是@这个装璜器语法糖背地的实现过程

能够发现new_add与new_add_1这两个函数的装璜器是两个不同值,而咱们的装璜器也返回了不同的对应状况

hello world3hello python4hello china5

间而言之:装璜器就是在咱们须要增加性能的函数上进而封装一层,而python的语法糖@背地,帮忙咱们省略掉了这些赋值的过程;

3:装璜器何时调用

对于装璜器何时运行,咱们分两种状况探讨,一种是当作脚本运行时,另一种是当作模块被导入时;

  1 registry = []  2  3  4 def register(func):  5     print(f"running register {func}")  6     registry.append(func)  7     return func  8  9 10 @register 11 def f1(): 12     print('running f1()') 13 14 15 @register 16 def f2(): 17     print('running f2()') 18 19 20 def f3(): 21     print('running f3()') 22 23 24 def main(): 25     print('running main()') 26     print('regisry ->', registry) 27     f1() 28     f2() 29     f3() 30 31 32 if __name__ == '__main__': 33     main()

当作独立脚本运行时:

running register <function f1 at 0x103f9dcb0>running register <function f2 at 0x103f9ddd0>running main()regisry -> [<function f1 at 0x103f9dcb0>, <function f2 at 0x103f9ddd0>]running f1()running f2()running f3()

被当作模块导入时:

>>> import registrationrunning register <function f1 at 0x1005a2710>running register <function f2 at 0x1005a2b90>>>> registration.registry[<function f1 at 0x1005a2710>, <function f2 at 0x1005a2b90>]

该段代码的装璜器次要性能是:记录了被装璜函数的个数,通常是web框架以这种形式把函数注册到地方注册器的某处。

总结:能够发现装璜器无论是作为模块被导入,还是独自的脚本运行,它都是优先执行的;

4:装璜器的罕用模块

之前介绍的function.wraps不用说了,接下来介绍两种神奇的装璜器;

1:singledispatch

何为singledispatch ?

就是在不扭转函数自身的性能上复用该函数,达到重复使用函数名的目标,有点相似多态的感觉;能够把整体计划拆分成多个模块,甚至能够为你无奈批改的类提供专门的函数。应用@singledispatch 装璜的一般函数会变成泛函数(generic function);依据第一个参数的类型,以不同形式执行雷同操作的一组函数

  1 from functools import singledispatch  2  3  4 @singledispatch  5 def hello(obj):  6     print(obj)  7  8  9 @hello.register(str) 10 def _(text): 11     print("hello world "+text) 12 13 14 @hello.register(int) 15 def _(n): 16     print(n) 17 18 19 hello({"what": "say"}) 20 print('*'*30) 21 hello('dengxuan') 22 print('*'*30) 23 hello(123)
{'what': 'say'}******************************hello world dengxuan******************************123

从该段代码中咱们能够发现,当应用singledispatch这个装璜器时,函数hello能够依据不同的参数返回不同的后果。这样的益处就是极大的缩小代码中的if/elif/else,并且能够复用函数名称 _ (下横线代表没用),升高了代码的耦合度,达到了多态的成果。

2:lru_cache

依据书上原话:

functools.lru_cache 是十分实用的装璜器,它实现了备忘 (memoization)性能。这是一项优化技术,它把耗时的函数的后果保留 起来,防止传入雷同的参数时反复计算。LRU 三个字母是“Least Recently Used”的缩写,表明缓存不会无限度增长,一段时间不必的缓存 条目会被扔掉。
  1 from my_tools.runtime import clock  2 import functools  3  4  5 @functools.lru_cache()  6 @clock  7 def fibonacci(n):  8     if n < 2:  9         return n 10     return fibonacci(n-2)+fibonacci(n-1) 11 12 13 if __name__ == '__main__': 14     print(fibonacci(6))

第5行:正文funtools.lru_cache()

返回后果:

[0.00000046] fibonacci(0) -> 0[0.00000053] fibonacci(1) -> 1[0.00006782] fibonacci(2) -> 1[0.00000030] fibonacci(1) -> 1[0.00000035] fibonacci(0) -> 0[0.00000037] fibonacci(1) -> 1[0.00001312] fibonacci(2) -> 1[0.00002514] fibonacci(3) -> 2[0.00010535] fibonacci(4) -> 3[0.00000030] fibonacci(1) -> 1[0.00000030] fibonacci(0) -> 0[0.00000037] fibonacci(1) -> 1[0.00001209] fibonacci(2) -> 1[0.00002376] fibonacci(3) -> 2[0.00000028] fibonacci(0) -> 0[0.00000038] fibonacci(1) -> 1[0.00001210] fibonacci(2) -> 1[0.00000028] fibonacci(1) -> 1[0.00000036] fibonacci(0) -> 0[0.00000034] fibonacci(1) -> 1[0.00001281] fibonacci(2) -> 1[0.00002466] fibonacci(3) -> 2[0.00004897] fibonacci(4) -> 3[0.00008414] fibonacci(5) -> 5[0.00020196] fibonacci(6) -> 88

当勾销掉第5行正文时;

[0.00000040] fibonacci(0) -> 0[0.00000049] fibonacci(1) -> 1[0.00008032] fibonacci(2) -> 1[0.00000066] fibonacci(3) -> 2[0.00009398] fibonacci(4) -> 3[0.00000063] fibonacci(5) -> 5[0.00010943] fibonacci(6) -> 88

能够发现,lru_cache()这个装璜器,极大的进步了计算性能;

functools.lru_cache(maxsize=128, typed=False)

maxsize 参数指定存储多少个调用的后果。缓存满了之后,旧的后果会被扔掉,腾出空间。为了失去最佳性能,maxsize 应该设为 2 的 幂。typed 参数如果设为 True,把不同参数类型失去的后果离开保留,即把通常认为相等的浮点数和整数参数(如 1 和 1.0)辨别开。顺 便说一下,因为 lru_cache 应用字典存储后果,而且键依据调用时传 入的定位参数和关键字参数创立,所以被 lru_cache 装璜的函数,它的所有参数都必须是可散列的。

5:多重装璜器

@d1@d2def f():  print('hello world')  ###########################def f():  print("hello world")f = d1(d2(f))

高低两块代码是等效成果;