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 t
    t

    以上定义了一个函数pow6(),装饰了一个最近最少使用(Least Recently Used,LRU)缓存。会把之前的请求放入缓存。请求在缓存中的大小是有限的。存入最新的请求,移除最近最少使用的请求,正是LRU缓存机制的逻辑。

    使用timeit,可以看到pow5()函数执行10000次迭代需要大约1秒,而pow6()函数执行相同次数需要8秒。

    可以看到这里使用timeit时,错误地计算了记忆化算法的性能。timeit模块可以写的更复杂一些,为了在实际的应用场景中可以更恰当地使用缓存。只是简单的随机数只对部分问题模型适用。

    使用可调用API简化

    一个API只专注一个方法,正是可调用对象的思路。

    一些对象有很多相关的方法。例如对于21点中的Hand对象,需要添加手中的牌并计算总值。21点中的Player需要下注、接牌和其他决定(例如,叫牌、停叫、分牌、加保险、双倍,等等)。这些复杂接口的场景不适合使用可调用对象实现。

    可考虑把投注策略作为可调用对象。

    投注策略可以被实现为一系列方法(一些settergetter方法)或者是一个包含了一些公有属性的可调用接口。

    如下是直接投注策略。

    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。毕竟,投注策略只需封装一些相关的、简单的规则。

    而正是这个接口实现的简短,体现了使用可调用对象所带来的简洁和高雅。对于这样一个简单的逻辑,没有定义太多方法,也没有使用很多复杂的语法。