流畅的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
posted @ 2019-12-24 01:04  就是想学习  阅读(294)  评论(0编辑  收藏  举报