0%

Fluent Python 读书笔记(3)

包括使用一等函数实现设计模式(第6章)和装饰器(第7章)

Chap 6 使用一等函数实现设计模式

6.1 “策略”模式

策略模式是定义一系列算法,将它们一一封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。

  • 上下文 context
    把一些计算委托给实现不同算法的可互换组件,它提供服务。
  • 策略 strategy
    实现不同算法的组件的共同接口
  • 具体策略
    “策略”的具体子类

在经典的“策略”模式中,具体策略都是一个类,它们没有状态,只有一个函数,可以用函数重构。

策略对象通常是很好的享元。
享元是可共享的对象,可以同时在多个上下文中使用。

共享是推荐的做法,这样不用在多个上下文中使用相同的策略时不断新建具体策略对象,减少消耗。
函数比用户定义的类轻量级,而且无需使用“享元”模式,因为各个策略函数字Python编译模块时只会创建一次。

6.2 “命令”模式

命令模式的目的是解耦调用操作的对象(调用者)和提供实现的对象(接收者)。

利用一等函数重构命令模式的方法与策略模式相似:将实现单方法接口的类的实例替换为可调用对象。

Chap 7 函数装饰器和闭包

7.1 装饰器基础知识

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。装饰器可能会处理被装饰的函数,然后将它返回,或者将其替换成另一个函数或者可调用对象。

1
2
3
4
5
6
7
8
9
10
11
12
def deco(func):
def inner():
print('running inner()')
return inner

@deco
def target():
print('running target()')


>>> target()
running inner()

此时的target()是对inner()的引用。

7.2 Python何时执行装饰器

装饰器在被装饰的函数定义之后立即运行,即导入时(Python加载模块时)。
函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。

装饰器常有以下用法:

  • 装饰器通常在一个模块中定义,然后应用到其他模块的函数上
  • 大多数装饰器会在内部定义一个函数,然后将其返回

7.4 变量作用域规则

Python不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。

在函数中复制时想让解释器将变量当成全局变量,使用global声明。

1
2
3
4
5
6
b = 6
def f3(a):
global b
print(a)
print(b)
b = 9

7.5 闭包

闭包指延伸了作用域的函数,其中包含定义体内引用、但是不在定义体中定义的非全局变量

1
2
3
4
5
6
7
def make_average():
series = []
def averager(new_value):
series.append(new_value)
total = sum(series)
return total / len(series)
return averager

series是自由变量(指未在本地作用域中绑定的变量)

series的绑定在返回avg函数的__closure__属性中,avg.__closure__中的各个元素对应于avg.__code__.co_freevars中的一个名称。这些元素是cell对象,有个cell_contents属性,保存着真正的值。

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

7.6 nonlocal声明

如果将7.5中的程序修改为只保存总值和个数

1
2
3
4
5
6
7
8
def make_average():
count = 0
total = 0
def averager(new_value):
count += 1
total += new_value
return total / count
return averager

这样的代码是,count+=1的操作相当于对count赋值,把count当作局部变量,会出现未声明先引用的问题。在7.5的代码中,列表是可变的对象使得这个问题不会出现,但是数字、字符串、元组等是不可变类型,只能读取,不能更新。

为了解决这个问题,Python 3 引入了nonlocal声明,将变量标记为自由变量

1
2
3
4
5
6
7
8
9
def make_average():
count = 0
total = 0
def averager(new_value):
nonlocal count, total
count += 1
total += new_value
return total / count
return averager

7.8 标准库中的装饰器

functools.wraps

协助构建行为良好的装饰器

functools.lru_cache

实现了备忘功能,是一项优化技术,把耗时的函数的结果保存起来,避免传入相同的参数时重复计算(eg: 斐波那契数递归函数)
LRU是Least Recently Used的缩写

1
2
3
4
5
6
7
8
9
10
import functools
from clockdeco import clock
@functools.lru_cache()
@clock
def fibonacci(n):
if n < 2 :
return n
return fibonacci(n-2) + fibonacci(n-2)
if __name__ == '__main__':
print(fibonacci(6))

单分派泛函数

functools.singledispatch装饰器将整体方案拆分成多个模块,甚至可以为你无法修改的类提供专门的函数。
使用@singledispatch装饰的普通函数会变成泛函数:根据第一个参数的类型,以不同方式执行相同操作的一组函数,

7.9 叠放装饰器

@d1@d2两个装饰器按顺序应用到f函数上,作用相当于f = d2(d2(f))

7.10 参数化装饰器

Python会将被装饰函数作为第一个参数传给装饰器,要传入其他参数,需要创建一个装饰器工厂函数,把参数传给他,返回一个装饰器,然后再把它应用到要装饰的函数上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
registry = []
def register(active = True): #装饰器工厂函数
def decorate(func): #装饰器
print('running register(active=%s)->decorete(%s)' % (active, func)) # 从闭包中获取active的值
if active :
registry.add(func)
else:
registry.discard(func)
return func # 装饰器返回函数
return decorate # 返回装饰器

@register(active=False)
def f1():
print('running f1()')
@register() # 工厂函数必须作为函数调用
def f2():
print('running f2()')

如果要想常规函数使用register,将f添加到registry中,需要使用register()(f)register(active=False)(f)(删除/不想添加)

参数化装饰器通常会把被装饰的函数替换掉,而且结构上需要多一层嵌套