流畅的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
浙公网安备 33010602011771号