流畅的python,Fluent Python 第七章笔记
函数装饰器于闭包。
装饰器于闭包前面我前面已经有简单的记录,这次我根据书中内容,对函数装饰器重新于闭包做个简要笔记。
def deco(func): def inner(): print('running inner()') return inner @deco def target(): print('running target') # target() '''下面的等于上面的''' target = deco(target) target()
这个是对装饰器的语法糖函数的直接认识,你运行的被装饰的函数,其实已经是装饰器内部的函数。
7.2Python何时执行装饰器
registry = [] def register(func): print('running register(%s)' % func) registry.append(func) return func # 务必记得返回原函数 @register def f1(): print('running f1()') @register def f2(): print('running f2()') def f3(): print('running f3()') def main(): print('running main()') print('registry', registry) f1() f2() f3() if __name__ == '__main__': main()
running register(<function f1 at 0x10501f9e0>) running register(<function f2 at 0x10501fd40>) running main() registry [<function f1 at 0x10501f9e0>, <function f2 at 0x10501fd40>] running f1() running f2() running f3()
from t7_2 import *
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第七章/t.py running register(<function f1 at 0x10cd570e0>) running register(<function f2 at 0x10cd57050>) Process finished with exit code 0
上面两种执行,一种主函数执行,一种导入该模块。
装饰器函数在导入时立即执行,被装饰的函数只在明确调用时运行。
主函数也一样也时一样的原理。
第六章讲过的优惠方案函数就可以通过装饰器的方式,将所有的优惠方案函数放入一个列表当中,所有来看,这个应该时非常不错的选择
promos = [] def promotion(promo_func): promos.append(promo_func) return promo_func
需要添加的优惠方案用这个装饰器装饰,就会把优惠方案函数放入promos
是个非常不错的选择。
7.4变量作用域规则。
这个其实对我还时有比较深刻的印象的。
比较颠覆我的思想时,并不是什么函数的内部变量不能改变外部变量(不可变对象)(其实并不是内部变量不能改变外部变量,底层的原因就是如果你未在函数内部赋值改变量假设x,你如果执行x = x + 1,在编辑器运行中,这个赋值语句将会把x定义为局部变量,然而你在内部函数中都未有x,所有肯定报错,这个才是所谓内部变量不能改变外部变量的原因。假如你在内部函数体赋值了x=1,那更加直接的告诉了编辑器,这个是局部变量,根本不可能改变外部的全局变量x),而是函数在执行前,编辑器会先判断在函数体内部的赋值,不管在哪个位置赋值,
一旦有赋值,则该变量将变成局部变量,在函数内部的参数调用中,将使用该局部变量。
如果外部有已经赋值的变量,函数内部没有该变量,可以引用该变量,但不能修改该变量内容(不可变对象),除非你在函数体内定义global变量为全局变量。
import dis a = 1 c = 2 def fn(arg): print(a) print(arg) print(c) c = 3 if __name__ == '__main__': print(dis.dis(fn)) fn(99)
上面的函数外部已经定义了c,但函数内部也定义了c,所以在编译函数体中,将c定义为局部变量,由于函数内部print(c)在c=3之前,所以对于print函数来说,c就是一个未定义的对象。
Traceback (most recent call last): File "/Users/shijianzhong/study/Fluent_Python/第七章/t7_4.py", line 12, in <module> fn(99) File "/Users/shijianzhong/study/Fluent_Python/第七章/t7_4.py", line 7, in fn print(c) UnboundLocalError: local variable 'c' referenced before assignment
5 0 LOAD_GLOBAL 0 (print) 2 LOAD_GLOBAL 1 (a) 4 CALL_FUNCTION 1 6 POP_TOP 6 8 LOAD_GLOBAL 0 (print) 10 LOAD_FAST 0 (arg) 12 CALL_FUNCTION 1 14 POP_TOP 7 16 LOAD_GLOBAL 0 (print) 18 LOAD_FAST 1 (c) # c为局部函数 20 CALL_FUNCTION 1 22 POP_TOP 8 24 LOAD_CONST 1 (3) 26 STORE_FAST 1 (c) 28 LOAD_CONST 0 (None) 30 RETURN_VALUE None 1 99
解决方法有很多,
1、可以在顶部定义 global c
2、在函数体内部删除c=3,直接引用全局变量
3、将c=3移到print(c)之前,就可以调用局部参数c了
7.5闭包
书中的翻译,闭包指的是延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体定义的非全局变量。
这个不知道是翻译不好,还是我的理解有问题,好拗口。
做到后面刚好可以理解了,就是一个函数,有一个变量,函数体里面引用了,但这个变量为非全局变量,而且不在这个函数体内部定义的。
重点的重点
1、不在定义体定义
2、非全局变量
3、函数定义体引用
下面用了一个求平均值的方法来做案例,分别用了类于高阶函数(复习高阶函数定义,能接收函数对象的,或者返回为函数的都叫高阶函数)
class Averager: def __init__(self): self.series = [] def __call__(self, new_value): self.series.append(new_value) total = sum(self.series) return total / len(self.series) avg = Averager() print(avg(3)) print(avg(4)) print(avg(5))
通过类的调用,很简单。
通过闭包的使用,稍微理解复杂一点。
def make_averager(): series = [] def averager(new_value): series.append(new_value) total = sum(series) return total / len(series) return averager avg = make_averager() print(avg(3)) print(avg(4)) print(avg(5)) print(avg.__code__.co_varnames) # 局部变量名 print(avg.__code__.co_freevars) # 自由变量,就是series print(avg.__closure__[0].cell_contents) # 取出series内的内容。 '''闭包是一个函数,它会保留定义函数时存在的自由变量的绑定,这样调用 函数时,虽然定义作用域不可用了,但是仍能使用那些绑定'''
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第七章/t7_9.py 3.0 3.5 4.0 ('new_value', 'total') ('series',) [3, 4, 5] Process finished with exit code 0
7.6nonlocal声明
nonlocal感觉就是为闭包使用的,当闭包函数绑定的自由变量为不可变类型参数时,就需要nonlocal了。
def make_averager(): count = 0 total = 0 def averager(new_value): nonlocal count, total # 其实是将变量标记为自由变量 count += 1 total += new_value return total / count return averager avg = make_averager() print(avg(3)) print(avg(4)) print(avg(5)) print(avg.__code__.co_varnames) # 局部变量名 print(avg.__code__.co_freevars) # 自由变量,就是series print([i.cell_contents for i in avg.__closure__]) # 取出series内的内容。 '''闭包是一个函数,它会保留定义函数时存在的自由变量的绑定,这样调用 函数时,虽然定义作用域不可用了,但是仍能使用那些绑定'''
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第七章/t7_13.py 3.0 3.5 4.0 ('new_value',) ('count', 'total') [3, 12] Process finished with exit code 0
标记为自由变量后,就可以对该函数的附属值(不可变对象)进行重新创建。
7.7 实现一个简单的装饰器
根据我在敲代码过程中的思考,函数装饰器肯定就是一种闭包函数,满足闭包函数的所有条件。
装饰器内部的函数,运行的就是不在函数内部定义的非全局变量(因为通过外部的函数传递进来,编程局部变量),而且还对其进行了引用,(其实就是运行被装饰的函数。)
# t7_15.py import time from functools import wraps def clock(func): @wraps(func) # 把被装饰的函数的属性__doc__与__name__属性转移到装饰器内部函数 def clocked(*args): t0 = time.perf_counter() result = func(*args) elapsed = time.perf_counter() - t0 name = func.__name__ arg_src = ', '.join(repr(arg) for arg in args) print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_src, result)) return result return clocked
import time from t7_15 import clock @clock def snooze(seconds): '''This is snooze''' time.sleep(seconds) '''根据闭包的解释闭包指的是延伸了作用域的函数 ,其中包含函数定义体中引用、但是不在定义体定义的非全局变量。 这个snooze函数里面包含了<function snooze at 0x10c0feb90>, 就是snooze原来的函数,这个就是不在函数定义体定义的非全局变量。 但具体又要执行,包含了定义体中的引用。 ''' @clock def factorial(n): return 1 if n < 2 else n * factorial(n-1) if __name__ == '__main__': print(snooze.__name__) print(snooze.__doc__) print(snooze.__code__.co_name) print(snooze.__closure__[0].cell_contents) print(factorial.__closure__[0].cell_contents) print('*' * 40) snooze(.123) print(factorial(6))
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第七章/t7_16.py t7_15 snooze This is snooze clocked <function snooze at 0x10ed97b90> <function factorial at 0x10ed48200> **************************************** [0.12730196s] snooze(0.123) -> None [0.00000109s] factorial(1) -> 1 [0.00002362s] factorial(2) -> 2 [0.00003878s] factorial(3) -> 6 [0.00005274s] factorial(4) -> 24 [0.00006689s] factorial(5) -> 120 [0.00008470s] factorial(6) -> 720 720 Process finished with exit code 0
7.8标准库中的装饰器
7.8.1functool.lru_cahe
@functools.lru_cache(maxsize=128, typed=True) # maxsize保存最多的缓存值,满了以后旧的扔掉 @clock # typed设置为true,可以把不同参数类型分开保存,列如1和1.0分开 def deco(x, y): print(f'x is {x},y is {y}') return x + y @functools.lru_cache() @clock def fibonacci(n): if n < 2: return n return fibonacci(n - 2) + fibonacci(n - 1)
deco(1, 2) deco(1, 2) deco(2, 3) deco(2, 3) fibonacci(10) # 可以起到加速的作用
x is 1,y is 2 [0.00001129s] deco(1, 2) -> 3 x is 2,y is 3 [0.00000936s] deco(2, 3) -> 5 [0.00000091s] fibonacci(0) -> 0 [0.00000081s] fibonacci(1) -> 1 [0.00003022s] fibonacci(2) -> 1 [0.00000161s] fibonacci(3) -> 2 [0.00005840s] fibonacci(4) -> 3 [0.00000134s] fibonacci(5) -> 5 [0.00008966s] fibonacci(6) -> 8 [0.00000139s] fibonacci(7) -> 13 [0.00011810s] fibonacci(8) -> 21 [0.00000135s] fibonacci(9) -> 34 [0.00014750s] fibonacci(10) -> 55
缓存里面已经有计算结果的值,就不重新执行了,所以相对来说运算速度快了很多,避免了很多重复运算。
7.8.2 单分派泛函数
我从书中的案例理解,可以将一个函数注册给不同的子类,子类根据传入的参数性质分别进去不同的定义函数,如果子函数没有,就执行主装饰函数。
from functools import singledispatch from collections import abc import numbers import html @singledispatch def htmlize(obj): content = html.escape(repr(obj)) return '<pre>{}</pre>'.format(content) @htmlize.register(str) # 字符串进这里 def _(text): content = html.escape(text).replace('\n', '<br>\n') return '<p>{}</p>'.format(content) @htmlize.register(numbers.Integral) # 数字进这里 def _(n): return '<pre>{0} (0x{0:x})</pre>'.format(n) @htmlize.register(tuple) # 元祖进这里 @htmlize.register(abc.MutableSequence) # 可变序列进这里 def _(seq): inner = '</li>\n<li>'.join(htmlize(item) for item in seq) return '<ul>\n<li>' + inner +'</li>\n</ul>'
In [40]: from t7_21 import htmlize In [41]: htmlize({1,2,3}) Out[41]: '<pre>{1, 2, 3}</pre>' In [42]: htmlize(abs) Out[42]: '<pre><built-in function abs></pre>' In [43]: htmlize('Heimlich & Co.\n -a game') Out[43]: '<p>Heimlich & Co.<br>\n -a game</p>' In [44]: htmlize(42) Out[44]: '<pre>42 (0x2a)</pre>' In [45]: htmlize(['alpha',66,{3,2,1}]) Out[45]: '<ul>\n<li><p>alpha</p></li>\n<li><pre>66 (0x42)</pre></li>\n<li><pre>{1, 2, 3}</pre></li>\n</ul>'
打个不合适的比方,@singledispatch有点像父亲,注册了很多子函数,需要注册在父亲之下。
这个比用if的话,可以降低函数之间的耦合程度。
带参数的函数装饰器最外层的函数就是一个制造装饰器的函数。
7.10.2参数化clock装饰器
# t7_25.py import time from functools import wraps DEFAULT_LMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}' def clock(fmt = DEFAULT_LMT): def decorate(func): @wraps(func) # 把被装饰的函数的属性__doc__与__name__属性转移到装饰器内部函数 def clocked(*args): t0 = time.perf_counter() result = func(*args) elapsed = time.perf_counter() - t0 name = func.__name__ arg_src = ', '.join(repr(arg) for arg in args) print(fmt.format(**locals())) # 通过**locals()解包来传递格式化参数,真的非常骚操作。 return result return clocked return decorate if __name__ == '__main__': # 只需要在clock()函数的参数内填写需要格式化输出的样式,就可以根据自己喜欢的形式输出 @clock() def snooze(seconds): time.sleep(seconds) for i in range(3): snooze(.123)
/usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第七章/t7_25.py [0.12805624s] snooze((0.123,)) -> None [0.12805513s] snooze((0.123,)) -> None [0.12333814s] snooze((0.123,)) -> None Process finished with exit code 0