5.3 使用functools完成记忆化
Python库的functools模块中包含了记忆化的装饰器。可以重用这个模块而不必新建自己的可调用对象。
可像如下代码这样使用。
from functools import lru_cache
@lru_cache(None)
def pow6( x, n ):
if n == 0: return 1
elif n % 2 == 1:
return pow6(x, n-1)x
else: # n % 2 == 0:
t= pow6(x, n//2)
return tt
以上定义了一个函数pow6(),装饰了一个最近最少使用(Least Recently Used,LRU)缓存。会把之前的请求放入缓存。请求在缓存中的大小是有限的。存入最新的请求,移除最近最少使用的请求,正是LRU缓存机制的逻辑。
使用timeit,可以看到pow5()函数执行10000次迭代需要大约1秒,而pow6()函数执行相同次数需要8秒。
可以看到这里使用timeit时,错误地计算了记忆化算法的性能。timeit模块可以写的更复杂一些,为了在实际的应用场景中可以更恰当地使用缓存。只是简单的随机数只对部分问题模型适用。
使用可调用API简化
一个API只专注一个方法,正是可调用对象的思路。
一些对象有很多相关的方法。例如对于21点中的Hand对象,需要添加手中的牌并计算总值。21点中的Player需要下注、接牌和其他决定(例如,叫牌、停叫、分牌、加保险、双倍,等等)。这些复杂接口的场景不适合使用可调用对象实现。
可考虑把投注策略作为可调用对象。
投注策略可以被实现为一系列方法(一些setter和getter方法)或者是一个包含了一些公有属性的可调用接口。
如下是直接投注策略。
class BettingStrategy:
def init( self ):
self.win= 0
self.loss= 0
def call( self ):
return 1
bet= BettingStrategy()
这个API的逻辑是,Player的对象会通知投注策略输赢的数量。Player对象也可使用如下这样的方法来通知投注策略输赢结果。
def win( self, amount ):
self.bet.win += 1
self.stake += amount
def loss( self, amount ):
self.bet.loss += 1
self.stake -= amount
这些方法会通知投注策略对象(self.bet对象)的输赢结果。当需要下注时,Player会使用类似如下的操作来获取当前的下注级别。
def initial_bet( self ):
return self.bet()
这是一个简短的API。毕竟,投注策略只需封装一些相关的、简单的规则。
而正是这个接口实现的简短,体现了使用可调用对象所带来的简洁和高雅。对于这样一个简单的逻辑,没有定义太多方法,也没有使用很多复杂的语法。
