Back to home

Wrap_cache 实现笔记

Wrap_cache 实现笔记

目的:解决一般调用函数的缓存控制,将诸如:

def b():
    c = t()
    time.sleep(2)
    d = trick["b"]()
    e = trick["bb"].time()
    return c

此类包含需要缓存调用结果的函数,转换为对调用进行缓存操作后的结果。
上例将会转换为:

__cacher1 = cache0(3)(t)
__cacher2 = cache0(3600)(time.sleep)

def b():
    c = __cacher1()
    __cacher2(2)
    d = trick["b"]()
    e = trick["bb"].time()
    print d, e
return c

实现原理

实现此类"语法糖"我们一般有两种方法,在编译(通过Python Virtual Machine编译为Python Bytecode)前将源码替换(对SourceCode改造),或者对于编译后的ByteCode进行改造。

从Python的源码到执行,一般经过一下几个步骤:

SourceCode -> Abstract Syntax Tree(下称AST) -> Bytecode

由Coder们输入的SourceCode,最后到Python Virtual Machine(下简称PVM)得到Bytecode执行,中间有一个AST的转换。
Python 脚本编译流程如下(From [PVMatour][]): python_compiler_and_interpreter.png

Python的源码经过几个步骤后变为了Bytecode成为PVM的目标码。
在执行时还需要PVM的Stack frame ,只有这里面才提供了实际的操作对象。
Bytecode 给出操作逻辑,Stack frame保存状态。

我们的目的就是改变Bytecode,将经过缓存包装后的函数压入当前Stack frame。

比较

决定了目的,我们开始着手修改逻辑。
改变逻辑的地方有三处,由前面的Python执行流程可以知道,我们分别可以从 SourceCode, AST,Byteco 下手。

Source Code

直接替换Source Code是不错的选择。
只要是遵循 Python Grammar 的替换都可以直接替换。
需要注意的是:

  • 无法采用缩进,直接字符串得到的解析是无法使用缩进的。这样带来的坏处是,作用域就是确定的呢,就是Module一级。 或者我们可以将执行结果状态空间更新到目标状态空间中。
  • 变量绑定名称需要提前确认
  • 多样的调用写法规则,因为是源码解析,带来的问题可能是太过于自由了。 甚至重写了某类的__add__方法,导致这种操作都成为了一次需要缓存的调用。

Abstract Syntax Tree

面对Source Code的歧义,AST(抽象语法树)的改造起来比较简便,唯一需要判断的是对于Stack Frame的引用。 需要注意的是:

  • 需要源码,也就是只能从字符串SourceCode中获得,无法直接的在运行时获取。 Python中有相应的实现,inspect.getsource 提供了从运行时对象获取对象实现的源代码的能力。 具体实现是通过变量所属Module的__file__以及当前对象的co_lnotab偏移计算得来。 获取的对象SourceCode包含所有的对象属性。比如函数也包括Decoratpr(装饰器)。这点需要注意,因为我们的实现是通过Decorator,当我们对源码对象改造的时候还需要移除这部分操作,即只改造一次。
  • 默认执行级别是Module

Bytecode

由于

Reference