侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

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

Python  /  管理员 发布于 7年前   137

简介

出于个人爱好和某种需求,我再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 BNF

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

compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decoratedif_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)* ':' suitewith_item: test ['as' expr]# NB compile.c makes sure that the default except clause is lastexcept_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爬取QQ音乐评论并制成词云图的实例
    下一条:
    python爬虫之爬取百度音乐的实现方法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第四课:僵尸作战系统(0个评论)
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客