• 流畅的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('
    ', '<br>
    ')
        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>
    <li>'.join(htmlize(item) for item in seq)
        return '<ul>
    <li>' + inner +'</li>
    </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.
     -a game')                                                       
    Out[43]: '<p>Heimlich & Co.<br>
     -a game</p>'
    
    In [44]: htmlize(42)                                                                               
    Out[44]: '<pre>42 (0x2a)</pre>'
    
    In [45]: htmlize(['alpha',66,{3,2,1}])                                                             
    Out[45]: '<ul>
    <li><p>alpha</p></li>
    <li><pre>66 (0x42)</pre></li>
    <li><pre>{1, 2, 3}</pre></li>
    </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
    
  • 相关阅读:
    动画差值
    高达模型
    TCP/IP负载均衡
    FreeImage使用
    Game Programming Pattern
    Apple Instruments
    GLEW OpenGL Access violation when using glGenVertexArrays
    微服务了解
    summary
    事务传播行为
  • 原文地址:https://www.cnblogs.com/sidianok/p/12089279.html
Copyright © 2020-2023  润新知