fluent python 读书笔记(七)函数装饰器和闭包

这一章我们来探讨函数装饰器和闭包

1.python模块在导入时会执行装饰器,对于被装饰器装饰的函数,需要明确调用才会执行。


2.函数可以读取不在本定义域内的变量

b = 6
def f():
  a = 6
  print(a)
  print(b)

但同时有时候是另人吃惊的

b = 6
def f():
    a = 6
    print(a)
    print(b)
    b = 7

运行这个示例就会发现,当我们有一个在函数体内给b赋值的语句时,Python会在编译函数体时就认定b是函数内定义的一个变量,而不是一个在外部的变量。有了这个假设,当python遇到print(b)时,它只会在函数体内找值,因为赋值在打印后,所以找不到会报错。

怎么解决?我们需要告诉Python, 我们这个b是一个全局的,你应该在全局环境中去找,而不是在函数体内去找。

所以加句global b


3.闭包

我认为,闭包是一种函数,在这个函数内部还有若干函数,也就是说,只有嵌套函数才存在闭包。

闭包这种函数,它的定义域是延伸了的,也就是说它能找不在本定义体内的非全局变量。注意是非全局的。

接下来我们定义一个能保存并且利用历史值计算平均值的函数

class Avarger:
    def __init__(self):
        self.data = []

    def __call__(self, x):
        self.data.append(x)
        return sum(self.data)/len(self.data)

但这是通过call, 通过类来实现的,我们怎么通过函数来实现呢?

这就用到了闭包!

def Avarger():
    data = []

    def avg(x):
        data.append(x)
        return sum(data)/len(data)

    return avg

当我们这样测试时

avg = Avarger()

当Avarget返回时,data已经消失了,那avg怎么找历史值呢?其实那个data作为一个自由变量,已经被绑定在了avg函数上!

这就是闭包,这里不详述怎么找这个绑定。


但是闭包有时会遇到一些问题

比如下面的示例

def A():
    count = 0
    total = 0

    def avg(x):
        count += 1
        total += x
        return total/count

这个代码是会失败的,因为你在使用count += 1时, 也就是count = count + 1,这时python看到这句话它会认为count是一个avg内部的本地变量,而不是绑定在avg上的自由变量。所以我们要告诉Python, nonlocal count, total, 它们不是本地变量,你不要妄想在avg内找了,去外面用闭包机制找。


7.7自己建立一个简单的装饰器

之前我们定义的装饰器都是很简单的,比如那个注册的函数。一般来说在装饰器内部还会有定义的函数,例如:

一个记录作用时间的函数

def clock(func):

      def clocked(*args):

            ......

这里我省略了实现细节,只是说说关键点。我们在这里的装饰器是clock,因为它接受一个func,一旦它接受了这个func,func在clock内部会成为本地变量,同时根据闭包规则它会成为clocked的自由变量。也就是说我们在clocked内部可以直接使用func。

但同时这个clock有个问题

比如我们让f1 = clock(f1), 这是f1.__name__不是f1 ,是clocked。你如果不喜欢,在def clocked前加一句@functools.wraps(func)


7.8标准库中的装饰器

之前其实提过functools.wraps(func),接下来介绍几个更强大的。

如果你想避免某些递归函数的重复计算,比如找第n个斐波那契数。

@clock
def f(n):
    if n<2:
        return n
    return f(n-1) + f(n-2)

这时若我们在@clock上加@functools.lru_cache()

你可以看到我们重复过的计算是不会再做的,可以极大提升递归效率。


另一个不介绍。


7.9叠放装饰器的意义

@d1

@d2

def f():pass

代表着f = d1(d2(f))


7.10参数化装饰器

如果我们想在某些时候传点参数给我们的装饰器该怎么办呢?

还是只能利用闭包,因为真正的装饰器只能有一个参数,获取其他变量只能靠闭包。于是我们需要在真正的装饰器外部再套一个函数

这里迫于时间只列举一个例子:

registry = set()

def register(active = True):
    def decorter(func):
        if active:
            registry.add(func)
        else:
            registry.discard(func)
        return func
    return decorter

@register()
def f1():pass

这里包了另外一层register,并且在装饰时要先调用register(),返回得到decorter,再@


接下来总结一下{}字符格式的用法:

"{变量名}" 表示输出时这个大括号里的内容只能靠关键词传入

“{name:10s}表示包括name最少占据10个位.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值