python解释器spython使用及原理解析


Posted in Python onAugust 24, 2019

简介

出于个人爱好和某种需求,我再16年对python的解释器产生了浓厚兴趣,并且下定决心重新实现一个版本。我个人再游戏服务器开发中,对c++嵌入lua和python都有着丰富应用经验,自认为对二者的优劣有着深刻的理解。

python针对lua的最大优势是python是完备的程序语言,类、模块包括丰富的库和方便好用的字符串操作,可以说python用来实现功能会优雅很多,而lua最大的优势就是小巧高效,另外lua的lua_state是可以有多个实例的,这样就可以多线程使用lua(一个线程单独一个lua_state),而python解释器因为有全局解释器锁,所以无法实现多python解释器实例。

考虑到在嵌入python的应用场景中,所用到python的功能都是比较简单通用的功能,比如类、模块,函数,一些复杂的类库也不常用,所以我就想实现一个不使用全局解释器锁,可以有多个python解释器锁的解释器。所以16年底,我自己实现了一下python解释器第一版,第一版是使用AST虚拟语法树直接解析的,虽然做了必要的优化,但是性能。。。。仍然不忍直视。

平常我一直吐槽python跑的没有lua快,但是吐槽是一码事,自己实现真的就是另一码事了。我仔细分析了第一版性能低的原因是选错了路!python的虚拟机是讲语法树翻译成ByteCode,然后有个Virtual Machine不断的解释bytecode,而vm的运行又分堆栈模式和寄存器模式,python就是堆栈模式的,而lua是寄存器模式的,寄存器模式是现在的趋势,这也是lua跑到更快的重要原因。我的第一版VM用AST直接跑,选错了路,无论如何也太快不了。

但是我仍然把这个第一版打了个分支,分享出来,因为当我实现用寄存器模式的VM的时候,感觉无论如何也无法设计的像AST直接解析的VM那样优雅、直接。AST直接解析的方式真的太直观了,虽然效率很低,但是其仍然有很大的应用价值。

比如protocolbuff、thrift这些通过定义语法文件生成代码的这类工具,对语法解析的效率要求不高,那么这个版本的VM再这些领域还是有很大的参考价值。

内部实现层次:

python解释器spython使用及原理解析

Python BNF

一提到实现脚本解释器,估计很多人都会挠头,不知道从何入手。刚开始我也是这样,我把大学里的编译原理从床底下一堆打入冷宫的数量翻出来,一顿猛看。但是仍然没有找到很大头绪,后来我就在python.org上一顿逛,也下载了python的源码分析,源码目录有python的BNF描述文件,因为我已经看过一遍编译原理了,BNF就看的很懂,从头到尾读了一遍了以后,灵光乍现啊!BNF就是完整的解析python语法的流程说明啊!截取一小段做个说明:

compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated
if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite]
while_stmt: 'while' test ':' suite ['else' ':' suite]
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
try_stmt: ('try' ':' suite
     ((except_clause ':' suite)+
     ['else' ':' suite]
     ['finally' ':' suite] |
     'finally' ':' suite))
with_stmt: 'with' with_item (',' with_item)* ':' suite
with_item: test ['as' expr]
# NB compile.c makes sure that the default except clause is last
except_clause: 'except' [test [('as' | ',') test]]
suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT

简单解释下,python的Grammar BNF是从顶之下递归描述的。上面最上边定义的是compound_stmt复杂语句,而compound_stmt有if、while、for、try、with、函数定义、类定义、修饰器定义几种,下面紧接着定义了if语句if_stmt的语法规则,这样在c++实现解析python语法的时候,就可以从顶向下按照这个BNF尝试解析,如果不满足这个BNF语法要求的就报错。我为了生成跟这个BNF一致的代码结构,写了个python脚本解析这个BNF自动生成C++的解析函数。生成的C++代码示例如下:

class Parser{
public:
 ExprASTPtr parse(Scanner& scanner);

 //! single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
 ExprASTPtr parse_single_input();
 //! file_input: (NEWLINE | stmt)* ENDMARKER
 ExprASTPtr parse_file_input();
 //! eval_input: testlist NEWLINE* ENDMARKER
 ExprASTPtr parse_eval_input();
 //! decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
 ExprASTPtr parse_decorator();
 //! decorators: decorator+
 ExprASTPtr parse_decorators();
 //! decorated: decorators (classdef | funcdef)
 ExprASTPtr parse_decorated();
 //! funcdef: 'def' NAME parameters ':' suite
 ExprASTPtr parse_funcdef();
 //! parameters: '(' [varargslist] ')'
 ExprASTPtr parse_parameters();
 //! varargslist: ((fpdef ['=' test] ',')*
 //!        fpdef ['=' test] (',' fpdef ['=' test])* [','])
 ExprASTPtr parse_varargslist();
 //! fpdef: NAME | '(' fplist ')'
 ExprASTPtr parse_fpdef();
 //! fplist: fpdef (',' fpdef)* [',']
 ExprASTPtr parse_fplist();
 //! stmt: simple_stmt | compound_stmt
 ExprASTPtr parse_stmt();
 //! simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE
 ExprASTPtr parse_simple_stmt();
 //! small_stmt: (expr_stmt | print_stmt | del_stmt | pass_stmt | flow_stmt |
 //!       import_stmt | global_stmt | exec_stmt | assert_stmt)
 ExprASTPtr parse_small_stmt();
 //! expr_stmt: testlist (augassign (yield_expr|testlist) |
 ExprASTPtr parse_expr_stmt();
.................................

Scanner的实现

scanner负责解析python代码,把python代码分隔这一个个Token对象,并且Token对象的定义如下:

struct Token{
 Token():nTokenType(0), nVal(0), fVal(0.0), nLine(0){
 }

 std::string dump() const;
 int       nTokenType;
 int64_t     nVal;
 double     fVal;
 std::string   strVal;
 int       nLine;
};

enum ETokenType {
  TOK_EOF = 0,
  //TOK_DEF = -2,
  TOK_VAR = -4,
  TOK_INT = -5,
  TOK_FLOAT = -6,
  TOK_STR = -7,
  TOK_CHAR = -8,
};

nTokenType定义为ETokenType的枚举。Scanner只扫描python代码,而不解析语法,所有的python代码都会解析成要么整数,要么浮点数要么字符串。这个跟原生的python是有区别的,原生python的数字对象可以表达任意数字,但是为了实现简便,做了简化处理,这也是参考了lua的实现方式每token对象会记录所属的行号,方便语法报错提供有用的信息。

具体scanner的实现就不贴出来了,感兴趣的可以去查看源码,还是比较简单的。

Parser的实现

Parser的头文件是脚本解析BNF自动生成的。负责把scanner解析的token列表,按照BNF的规则构造成AST。AST节点对象定义为ExprAST:

class ExprAST {
public:
  ExprAST(){
  }
  virtual ~ExprAST() {}
  virtual PyObjPtr& eval(PyContext& context) = 0;

  unsigned int getFieldIndex(PyContext& context, PyObjPtr& obj);

  virtual PyObjPtr& getFieldVal(PyContext& context);
  virtual PyObjPtr& assignVal(PyContext& context, PyObjPtr& v){
    PyObjPtr& lval = this->eval(context);
    lval = v;
    return lval;
  }
  virtual void delVal(PyContext& context){
    PyObjPtr& lval = this->eval(context);
    lval = NULL;
  }

  virtual int getType() {
    return 0;
  }

public:
  std::string name;
  ExprLine  lineInfo;
  //std::vector<std::vector<int> > module2objcet2fieldIndex;
  std::vector<int>         module2objcet2fieldIndex;
};
class PyObj {
public:
  RefCounterData* getRefData(){
    return &refdata;
  }
  void release();
  typedef PySmartPtr<PyObj> PyObjPtr;
  PyObj():m_pObjIdInfo(NULL), handler(NULL){}
  virtual ~PyObj() {}

  int getType() const;
  virtual int getFieldNum() const { return m_objStack.size(); }
  static std::string dump(PyContext& context, PyObjPtr& self, int preBlank = 0);

  virtual PyObjPtr& getVar(PyContext& c, PyObjPtr& self, ExprAST* e);
  virtual const ObjIdInfo& getObjIdInfo() = 0;

  void clear(){
    m_objStack.clear();
  }
  inline PyObjHandler* getHandler() { return handler; }
  inline const PyObjHandler* getHandler() const { return handler; }
public:
  std::vector<PyObjPtr>  m_objStack;
  ObjIdInfo*        m_pObjIdInfo;
  PyObjHandler*      handler;
  RefCounterData      refdata;
};
typedef PyObj::PyObjPtr PyObjPtr;

ExprAST抽象了AST节点的几个操作。最主要的就是求值操作eval。比如100求值就是100,'abc'求值就是字符串'abc',生成对应的值对象。每个值对象都继承PyObj。每个PyObj都会定义ObjHander接口用于实现python对象的各个操作,比如+、-、/等,不同的python值对象,响应的操作是不一样,这里利用了c++的多态。

class PyObjHandler{
public:
 virtual ~PyObjHandler(){}

 virtual int getType() const = 0;

 virtual std::string handleStr(PyContext& context, const PyObjPtr& self) const;
 virtual std::string handleRepr(PyContext& context, const PyObjPtr& self) const;
 virtual int handleCmp(PyContext& context, const PyObjPtr& self, const PyObjPtr& val) const;
 virtual bool handleBool(PyContext& context, const PyObjPtr& self) const;
 virtual bool handleEqual(PyContext& context, const PyObjPtr& self, const PyObjPtr& val) const;
 virtual bool handleLessEqual(PyContext& context, const PyObjPtr& self, const PyObjPtr& val) const;
 virtual bool handleGreatEqual(PyContext& context, const PyObjPtr& self, const PyObjPtr& val) const;
 virtual bool handleContains(PyContext& context, const PyObjPtr& self, const PyObjPtr& val) const;

 virtual bool handleLess(PyContext& context, const PyObjPtr& self, const PyObjPtr& val) const;
 virtual bool handleGreat(PyContext& context, const PyObjPtr& self, const PyObjPtr& val) const;

 virtual PyObjPtr& handleAdd(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual PyObjPtr& handleSub(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual PyObjPtr& handleMul(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual PyObjPtr& handleDiv(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual PyObjPtr& handleMod(PyContext& context, PyObjPtr& self, PyObjPtr& val);


 virtual PyObjPtr& handleIAdd(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual PyObjPtr& handleISub(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual PyObjPtr& handleIMul(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual PyObjPtr& handleIDiv(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual PyObjPtr& handleIMod(PyContext& context, PyObjPtr& self, PyObjPtr& val);

 virtual PyObjPtr& handleCall(PyContext& context, PyObjPtr& self, std::vector<ArgTypeInfo>& allArgsVal,
                std::vector<PyObjPtr>& argAssignVal);
 virtual size_t  handleHash(PyContext& context, const PyObjPtr& self) const;
 virtual bool handleIsInstance(PyContext& context, PyObjPtr& self, PyObjPtr& val);
 virtual long handleLen(PyContext& context, PyObjPtr& self);
 virtual PyObjPtr& handleSlice(PyContext& context, PyObjPtr& self, PyObjPtr& startVal, int* stop, int step);
 virtual PyObjPtr& handleSliceAssign(PyContext& context, PyObjPtr& self, PyObjPtr& k, PyObjPtr& v);
 virtual void handleSliceDel(PyContext& context, PyObjPtr& self, PyObjPtr& k){}

 virtual void handleRelese(PyObj* data);
};

Python库的实现

实现的python库列表如下:

  • list dict tuple copy string
  • datetime
  • json
  • math
  • os
  • random
  • open stringio
  • struct
  • sys
  • weak

总结

spython就是small python,本来想实现最简版本的python解释器,后来实现的比较顺,一口气把常用的python库都实现了。spython最成功的部分就是ast的解析和执行,代码结构清晰完全按照bnf的流程来,很直接明了。

缺点主要有二。一是语法报错还是太简陋,不够友好。二是性能达不到原生python的性能。前文已经说过了,要达到甚至超过原生python的水平,必须要实现基于寄存器的VM,这个已经着手再弄了,暂时还不会放出代码,等差不多成型了再放出来吧。

代码地址:https://git.oschina.net/ownit/spython

构建:Linux下直接make就可以了,win下需要用dev c++

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
python读取oracle函数返回值
Jul 18 Python
Python中struct模块对字节流/二进制流的操作教程
Jan 21 Python
python2.7无法使用pip的解决方法(安装easy_install)
Apr 03 Python
详解flask表单提交的两种方式
Jul 21 Python
python字符串循环左移
Mar 08 Python
Python批量查询关键词微信指数实例方法
Jun 27 Python
Python实现的对一个数进行因式分解操作示例
Jun 27 Python
python买卖股票的最佳时机(基于贪心/蛮力算法)
Jul 05 Python
在django中实现页面倒数几秒后自动跳转的例子
Aug 16 Python
基于Python新建用户并产生随机密码过程解析
Oct 08 Python
Python 实现黑客帝国中的字符雨的示例代码
Feb 20 Python
在Keras中CNN联合LSTM进行分类实例
Jun 29 Python
python爬虫之爬取百度音乐的实现方法
Aug 24 #Python
python3 实现爬取TOP500的音乐信息并存储到mongoDB数据库中
Aug 24 #Python
python处理自动化任务之同时批量修改word里面的内容的方法
Aug 23 #Python
解决在pycharm运行代码,调用CMD窗口的命令运行显示乱码问题
Aug 23 #Python
简单分析python的类变量、实例变量
Aug 23 #Python
python3 selenium自动化 frame表单嵌套的切换方法
Aug 23 #Python
python-Web-flask-视图内容和模板知识点西宁街
Aug 23 #Python
You might like
mysql 全文搜索 技巧
2007/04/27 PHP
PHP 常用函数库和一些实用小技巧
2009/01/01 PHP
PHP操作MySQL事务实例
2014/11/05 PHP
php生成4位数字验证码的实现代码
2015/11/23 PHP
PHP+MySQL统计该库中每个表的记录数并按递减顺序排列的方法
2016/02/15 PHP
JavaScript进阶教程(第四课第一部分)
2007/04/05 Javascript
JS实现定时自动关闭DIV层提示框的方法
2015/05/11 Javascript
再谈JavaScript线程
2015/07/10 Javascript
js clearInterval()方法的定义和用法
2015/11/11 Javascript
Angularjs 实现分页功能及示例代码
2016/09/14 Javascript
简单理解js的冒泡排序
2016/12/19 Javascript
jQuery实现web页面樱花坠落的特效
2017/06/01 jQuery
jQuery Easyui Treegrid实现显示checkbox功能
2017/08/08 jQuery
微信小程序云开发如何使用云函数生成二维码
2019/05/18 Javascript
使用p5.js临摹动态图片
2019/11/04 Javascript
分享8个JavaScript库可更好地处理本地存储
2020/10/12 Javascript
pymssql ntext字段调用问题解决方法
2008/12/17 Python
Python实现的tab文件操作类分享
2014/11/20 Python
Python内置数据结构与操作符的练习题集锦
2016/07/01 Python
Python中的错误和异常处理简单操作示例【try-except用法】
2017/07/25 Python
Python 打印中文字符的三种方法
2018/08/14 Python
对PyQt5的输入对话框使用(QInputDialog)详解
2019/06/25 Python
Python matplotlib画曲线例题解析
2020/02/07 Python
pycharm设置python文件模板信息过程图解
2020/03/10 Python
Python稀疏矩阵及参数保存代码实现
2020/04/18 Python
使用OpenCV对车道进行实时检测的实现示例代码
2020/06/19 Python
python exit出错原因整理
2020/08/31 Python
canvas 橡皮筋式线条绘图应用方法
2019/02/13 HTML / CSS
int和Integer有什么区别
2013/05/25 面试题
《陶罐和铁罐》教学反思
2014/02/19 职场文书
保卫科工作岗位职责
2014/03/01 职场文书
机关干部个人对照检查材料思想汇报
2014/09/28 职场文书
国防教育标语
2014/10/08 职场文书
Canvas跟随鼠标炫彩小球的实现
2021/04/11 Javascript
MySQL 数据类型详情
2021/11/11 MySQL
JavaScript原型链中函数和对象的理解
2022/06/16 Javascript