5月9日读书笔记——流畅的python

闭包

闭包是延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。
函数与是不是匿名函数无关,关键是他能访问定义体之外定义的非全局变量。

class Avarage():
    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 = Avarage()
avg(10)
10.0
avg(11)
10.5
avg(12)
11.0
def make_average():
    series = []

    def averager(new_value):
        series.append(new_value)
        total = sum(series)
        return total/len(series)
    return averager
    
avg = make_average()
avg(10)
10.0
avg(11)
10.5
avg(12)
11.0

前面一个是类的实例调用,后者是函数式的实现方式。
在第二种方式中,average的闭包延伸至函数的作用域之外,包含自由变量series的绑定。

avg.__code__.co_varnames
('new_value', 'total')
avg.__code__.co_freevars
('series',)
avg.__closure__
(<cell at 0x10c63e198: list object at 0x10c4db308>,)
avg.__closure__[0].cell_contents
[10, 11, 12]

综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。

nonlocal声明

>>> def make_averager():
...     count = 0
...     total = 0
...     def averager(new_value):
...             count += 1
...             total += new_value
...             return total/ count
...     return averager
...             
>>> avg = make_averager()
>>> avg(10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in averager
UnboundLocalError: local variable 'count' referenced before assignment

在上面这个示例中,count虽然在make_averager中定义了,但是在averager函数中,由于count被重新赋值了,导致count成了局部变量。

之前那个例子中,用到了series.append()的方式,并非赋值,因此没有出现报错。

但是对于数字、字符串、元组等不可变的类型来说,只能读取,不能更新。如果尝试重新绑定赋值,那么会隐式的创建一个局部变量。因此,count不再是一个自由变量了,也不会保存在闭包中。

为了解决这个问题,Python3中引入了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()
>>> avg(10)
10.0

如上示例,此时再调用averager()方法就不会报错了。

实现一个简单的装饰器

>>> import time
>>> def clock(func):
...     def clocked(*args):
...             t0 = time.perf_counter()
...             result = func(*args)
...             elapsed = time.perf_counter() - t0
...             name = func.__name__
...             print('[%0.8fs] %s(%s) -> %r' % (elapsed, name, arg_str, result))
...             return result
...     return clocked

输出如下:

>>> @clock
... def snooze(seconds):
...     time.sleep(seconds)
... 
>>> @clock
... def factorial(n):
...     return 1 if n < 2 else n*factorial(n-1)
... 
>>> if __name__ == '__main__':
...     print('*' * 40, 'Calling snooze(.123)')
...     snooze(.123)
...     print('*' * 40, 'Calling factorial(6)')
...     print('6! =', factorial(6))
... 
**************************************** Calling snooze(.123)
[0.12820711s] snooze(0.123) -> None
**************************************** Calling factorial(6)
[0.00000095s] factorial(1) -> 1
[0.00002918s] factorial(2) -> 2
[0.00004905s] factorial(3) -> 6
[0.00006645s] factorial(4) -> 24
[0.00008395s] factorial(5) -> 120
[0.00011178s] factorial(6) -> 720
6! = 720

示例中,factorial保存的是clocked函数的引用。自此之后,每次调用factorial(n),执行的都是clocked(n)。

装饰器的典型行为:

把被装饰的函数替换成新函数,二者接受相同的参数,而且通常返回被装饰的函数本该返回的值,同时还会做些额外的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值