读 Werkzeug 源码 抽出一个 Python 交互器
编程    Python    2015-10-11 03:49:28    1338    0    0
  Python

Werkzeug 中有一个方便查找问题的 Python 命令交互器,我想把他单独拿出来.一方面做一个网页版的 Python 交互器,也可以进而封装成基于http协议的远程 Python 脚本调用包.想着是放到 SAE 上,然后就可以很方便的使用 SAE 的计算资源和其他特有资源.

总体上最核心的一个函数就是 eval,这个函数其实可以接受三个参数.第一个参数是一个字符串,它会把字符串当成是 Python 源码而直接解析.第二个和第三个参数是可选的,意思是要传入的上下文环境的局部变量字典和全局变量字典. locals() 和 globals() 是 Python内置环境,任何时候调用都返回当前运行环境中变量和对象的列表.

因为 HTTP 是无状态的,所以如果要输入一句执行一句,就需要把上一句执行之后的 locals() 和 globals() 保存下来,下一次 eval的时候再传进去,再把 locals() 和 globals() 保存下来,已备后用.基本上就是这个原理. Werkzeug 的 console.py 已经把这部分封装好了,对于网页版的 Python 交互器就直接用,对于远程 Python 脚本调用库,我觉得还是得返回最原始的内容,格式不需要这么复杂.

页面部分,我改用 flask 来处理,直接 show 出来就好,js 里面的 SECRET 没用,我用 session 来区别不同的客户端和用户.

@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')

但是 flask 的 session 其实是很特别的.正常的 session 工作流程是,将 session_id 写在 cookies 里面,请求的时候拿到 session_id 就可以在后端的内存中找到真正的存储了内容的 session. 但是 flask 的 session 是直接把 session 的内容加密,全部写在 cookies 里,我不知道这是出于什么原因,但是只能被迫接受.

由于不能在 session 里存储对象实例,我就只能在 session 里做一个 uid, 做了一个全局的字典 gConsolePool = {}, 用 uid做键值映射的对象.封装成修饰器就是这样:

def checkusr(httphandle):
    @wraps(httphandle)
    def wrapper(*argc, **argv):
        if "uid" not in session or session["uid"] not in gConsolePool:
            session["uid"] = uuid.uuid4()
            gConsolePool[session["uid"]] = Console()
        print session["uid"]
        g.console = gConsolePool[session["uid"]]
        return httphandle(*argc, **argv)
    return wrapper

这样对象实例就好像永远存在在 g 对象里了.

调用方法非常简单:

@app.route('/console', methods=['POST'])
@checkusr
def console():
    cmd = request.form['cmd']
    return g.console.eval(cmd)

基本功能在本地已经调试通过,但是部署到云环境 SAE / BAE 上还是有问题,会莫名其妙的丢失全局变量里的数据,SAE链接 BAE链接.

丢失数据

哦,原来是因为云端用的都是集群环境,集群环境下访问的应用可能不是同一个,所以全局变量里的数据会消失掉.看来只能将对象序列化存到 redis 里了.

但是发现 code.InteractiveInterpreter() 类的对象如果操作过,是无法被序列化的, 引发 TypeError: can't pickle ellipsis objects . [Python 2.7]

Python3 下似乎没有这个问题.[Python 3.4]

但是因为云平台都还停留在 Python 2 的时代,所以这个项目对我没有太大的意义了.

文档导航