2021-11-05

第7章 函数装饰器和闭包

函数装饰器用于“标记”函数,以某种方式增强函数的行为。理解装饰前,必须先理解闭包

本章思维导图:

7.1Python如何计算装饰器句法

@decorate
def target():
    print("running target")
等价于
def target():
    print("running target")
target=decorate(target)
#最终装饰器函数返回的不一定原来的target函数

def deco(func):
    def inner():
        print("running inner")
    return inner 
@deco 
def target():
    print("running target()")

target()
#审查对象,发现target现在是inner的引用。

装饰器的第一特性:
能把被装饰的函数替换成其他函数
第二个特性:
装饰器在加载模块时立即执行

7.2 Python何时执行装饰器

def register(func):
    print("runing register(%s)"%func)
    registry.append(func)
    return func 
@register
def f1():
    print("runing f1()")
@register
def f2():
    print("runing f2()")
def f3():
    print("runing f3()")
def main():
    print("runing main()")
    print("registry->",registry)
    f1()
    f2()
    f3()
if __name__ == '__main__':
    main()

示例7-2主要想强调:
函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行,其实在实际情况中:装饰器通常在一个模块中定义,然后应用到其他模块中的函数上。

7.3 使用装饰器改进“策略”模式

第六6章中,要计算最佳折扣,需要维护一个函数列表,promos=[]

如果忘记加入新的折扣策略,就会造成难以发现的错误。
用装饰器解决该问题:

promos=[]

def promotion(promo_func):
    promos.append(promo_func)
    return promo_func
@promotion
def method1()print("我是策略一")
@promotion
def method2():
    print("我是策略二")

这个方法的优点:

促销策略函数无需使用特殊的名称

@promotion

装饰器突出了被装饰的函数的作用
还便于临时禁用某个促销策略:只需把装饰器注释掉。

促销折扣策略可以在其他模块中定义,
在系统中的任何地方都行,只要使用
装饰器即可

这个例子返回的还是传入的参数,但是通常装饰器会返回一个内部函数。
使用内部函数(嵌套函数),就要深刻的理解闭包操作

运行该代码:
b=6
def f(a):
    print(a)
    print(b)
    b=9
    #赋值了,此时认为b是局部变量
查看结果:

在这里插入图片描述
因为:
Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量

#正确运行:
b=6
def f(a):
    global b
    #特别声明b是全局变量
    print(a)
    print(b)
    b=9

7.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()

问题是:
调用avg(10)
make_averager函数已经返回了,而它的本地作用域应该不存在了。

运行如下代码:

avg.__code__.co_varnames

avg.__code__.co_freevars

output:
在这里插入图片描述

闭包的变量作用域如下:

在这里插入图片描述
在总结一下
看下函数glabol和nonlocal的区别
global 声明全局变量,
nonlocal 声明嵌套函数中的自由变量

def make_averager():
    count=0
    total=0 
    def averager(new_value):
        count+=1
        total+=new_value
        #注意报错
        return total.count 
    return averager

这里,count,total都是不可变变量,此时如果运行,

count会被认为是局部变量,所以必须用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

7.7 实现一个简单的装饰器

# 装饰器用来计算一个函数的运行时间

import time
def clock(func):
    def clocked(*args):
        #接受任意个定位参数
        t0=time.perf_counter()
        result=func(*args)
        #func是自由变量
        t1=time.perf_counter()-t0
        #程序运行使用时间

        name=func.__name__
        arg_str=",".join(repr(arg) for arg in args)
        print('[%0.8fs]%s(%s)->%r'%(t1,name,arg_str, result))
        #%0.8f小数点后显示8位的s
        return result 
    return clocked


import time

from test import clock

@clock
def snooze(seconds):
    time.sleep(seconds)


@clock
def factorial(n):
    return 1 if n <2 else n*factorial(n-1)


if __name__ == '__main__':
    snooze(.123)
    factorial(6)

这种装饰器有以下缺点:

不支持关键字传参
覆盖了被装饰函数的__name__/__doc__属性

import time

import functools

def clock(func):
    @functools.wraps(func)
    def clocked(*args,**kwargs):
        #接受任意个定位参数
        t0=time.perf_counter()
        result=func(*args,**kwargs)
        #func是自由变量
        t1=time.perf_counter()-t0
        #程序运行使用时间

        name=func.__name__
        arg_lst=[]

        if args:
            arg_lst.append(",".join(repr(arg) for arg in args))
        if kwargs:
            pairs=['%s=%r'%(k,w)for k,w in sorted(kwargs.items())]
            #关键字传参保存在字典里。
            arg_lst.append(",".join(pairs))
        arg_str=','.join(arg_lst)
       

        print('[%0.8fs]%s->%r'%(t1,name,arg_str,result))
        #%0.8f小数点后显示8位的s
        return result 
    return clocked

在总结两个拿来即用的装饰器:
lru_cache 和 singledispatch

7.8 标准库中的装饰器

property、classmethod和staticmethod
先不讨论这三个装饰方法的函数

另一个常见的装饰器是functools.wraps,
它的作用是协助构建行为良好的装饰器
:支持关键字传参数
:不覆盖被装饰函数的属性

7.8.1 使用functools.lru_cache做备忘

这是一个优化技术,
把耗时的函数结果保存起来,避免传入相同参数的时候重复计算
所以可以用来优化递归算法
从Web中获取信息的应用中也能发挥巨大作用。

import functools
from test import clock 
@functools.lru_cache()
@clock
#装饰器叠用,真尼玛牛皮
def fibonacci(n):
    if n<2:
        return(n)
    return fibonacci(n-2)+fibonacci(n-1)
if __name__ == '__main__':
    print(fibonacci(10))

多次计算相同参数的值,浪费时间
使用缓存,速度更快
如果一个算法,用了递归,可以用这种方法优化

7.8.2 单分派泛函数

‘’’’’’'未完待续‘’‘’‘’‘

7.9叠放装饰器

@d1
@d2
def f():
    print("f")
等价于:
def f():
    print('f')
f=d1(d2(f))

7.10 参数化装饰器

‘’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’’'未完待续~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值