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

简介

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

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

(0)

相关推荐

  • linux查找当前python解释器的位置方法

    先进入python 输入 import sys sys.executable 即可! 以上这篇linux查找当前python解释器的位置方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • 在Python文件中指定Python解释器的方法

    以下针对Ubuntu系统,Windows系统没有测试过. Ubuntu中默认就安装有Python 2.x和Python 3.x,默认情况下python命令指的是Python 2.x.因此当将Python脚本设为可执行文件直接在命令行里执行时,系统调用的是Python 2.x的解释器. 如果在直接执行Python脚本(例如在命令行直接输入xxx.py)时,想调用Python 3.x解释器去解释脚本,一种方法是修改符号链接,让python命令指向Python3.这种方法在自己的系统上还行得通,如果脚

  • Win7下搭建python开发环境图文教程(安装Python、pip、解释器)

    安装Python 1.下载适合系统版本的Python 先到网址(http://www.python.org/getit/)下载适合自己windows的python版本,32位win7下载 Python 3.3.2 Windows x86 MSI installer, 64位win7下载Python 3.3.2 Windows x86-64 MSI installer. (注:右击"计算机"-->"属性",会显示系统信息,如下图,显示我的win7为32位 ) 2

  • MAC中PyCharm设置python3解释器

    MAC上的PyCharm中默认的python解释器是python2的,windows下的没用过不是很清楚,所以特来记录下设置python3解释器的过程. python3的查找与安装 如果电脑中没有安装python3可以用brewhome来安装,简单记录下 // 查看python版本 python --version python3 --version // 搜索python brew search python // 安装python3 brew install python3 然后经过一个漫长

  • Pycharm学习教程(4) Python解释器的相关配置

    Python解释器的相关配置,供大家参考,具体内容如下 1.准备工作 (1)Pycharm版本为3.4或者更高. (2)电脑上至少已经安装了一个Python解释器. (3)如果你希望配置一个远程解释器,则需要服务器的相关支持. 2.本地解释器配置 配置本地解释器的步骤相对简洁直观: (1)单击工具栏中的设置按钮. (2)在Settings/Preferences对话框中选中 Project Interpreter页面,在Project Interpreter对应的下拉列表中选择对应的解释器版本,

  • mac PyCharm添加Python解释器及添加package路径的方法

    一.背景 PyCharm执行Python时,找不到自己安装的package,例如pandas.numpy.scipy.scikit等,在执行时报如下错误ImportError: No module named pandas: Traceback (most recent call last): File "<input>", line 1, in <module> File "/Applications/PyCharm.app/Contents/hel

  • Python3解释器知识点总结

    Python3 解释器 Linux/Unix的系统上,一般默认的 python 版本为 2.x,我们可以将 python3.x 安装在 /usr/local/python3 目录中. 安装完成后,我们可以将路径 /usr/local/python3/bin 添加到您的 Linux/Unix 操作系统的环境变量中,这样您就可以通过 shell 终端输入下面的命令来启动 Python3 . $ PATH=$PATH:/usr/local/python3/bin/python3 # 设置环境变量 $

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

    简介 出于个人爱好和某种需求,我再16年对python的解释器产生了浓厚兴趣,并且下定决心重新实现一个版本.我个人再游戏服务器开发中,对c++嵌入lua和python都有着丰富应用经验,自认为对二者的优劣有着深刻的理解. python针对lua的最大优势是python是完备的程序语言,类.模块包括丰富的库和方便好用的字符串操作,可以说python用来实现功能会优雅很多,而lua最大的优势就是小巧高效,另外lua的lua_state是可以有多个实例的,这样就可以多线程使用lua(一个线程单独一个l

  • python线程定时器Timer实现原理解析

    这篇文章主要介绍了python线程定时器Timer实现原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.线程定时器Timer原理 原理比较简单,指定时间间隔后启动线程!适用场景:完成定时任务,例如:定时提醒-闹钟等等. # 导入线程模块 import threading timer = threading.Timer(interval, function, args=None, kwargs=None) 参数介绍: interval

  • python实现布隆过滤器及原理解析

    在学习redis过程中提到一个缓存击穿的问题, 书中参考的解决方案之一是使用布隆过滤器, 那么就有必要来了解一下什么是布隆过滤器.在参考了许多博客之后, 写个总结记录一下. 一.布隆过滤器简介 什么是布隆过滤器? 本质上布隆过滤器( BloomFilter )是一种数据结构,比较巧妙的概率型数据结构(probabilistic data structure),特点是高效地插入和查询,可以用来告诉你 "某样东西一定不存在或者可能存在". 相比于传统的 Set.Map 等数据结构,它更高效

  • Python迭代器模块itertools使用原理解析

    这篇文章主要介绍了Python迭代器模块itertools使用原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 介绍 今天介绍一个很强大的模块,而且是python自带的,那就是itertools迭代器模块. 使用 使用起来很简单,先导入模块 import itertools 下面,我们通过一些例子边学边练 三个无限迭代器 先告诉大家 control + C 可以强制停止程序哦 1.count() num = itertools.count

  • python垃圾回收机制(GC)原理解析

    这篇文章主要介绍了python垃圾回收机制(GC)原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 今天想跟大家分享的是关于python的垃圾回收机制,虽然本人这会对该机制没有很深入的了解, 但是本着热爱分享的原则,还是囫囵吞枣地坐下记录分享吧, 万一分享的过程中开窍了呢.哈哈哈. 首先还是做一下概述吧: 我们都知道, 在做python的语言编程中, 相较于java, c++, 我们似乎很少去考虑到去做垃圾回收,内存释放的工作, 其实是p

  • Python线程条件变量Condition原理解析

    这篇文章主要介绍了Python线程条件变量Condition原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Condition 对象就是条件变量,它总是与某种锁相关联,可以是外部传入的锁或是系统默认创建的锁.当几个条件变量共享一个锁时,你就应该自己传入一个锁.这个锁不需要你操心,Condition 类会管理它. acquire() 和 release() 可以操控这个相关联的锁.其他的方法都必须在这个锁被锁上的情况下使用.wait()

  • Python类继承和多态原理解析

    这篇文章主要介绍了python类继承和多态原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 现在属于是老年人的脑子,东西写着写着就忘了,东西记着记着就不知道了.之前学C++的时候就把类.对象这块弄得乱七八糟,现在是因为很想玩python,所以就看看python的类和对象. 就像说的,类有三个特征:封装.继承.多态. 1.封装:类封装了一些方法,可通过一定的规则约定方法进行访问权限. C++中的成员变量有public.private.pto

  • python next()和iter()函数原理解析

    这篇文章主要介绍了python next()和iter()函数原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 我们首先要知道什么是可迭代的对象(可以用for循环的对象)Iterable: 一类:list,tuple,dict,set,str 二类:generator,包含生成器和带yield的generatoe function 而生成器不但可以作用于for,还可以被next()函数不断调用并返回下一个值,可以被next()函数不断返回

  • Python chardet库识别编码原理解析

    这篇文章主要介绍了python chardet库识别编码原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 chardet库是python的字符编码检测器,能够检测出各种编码的类型,例如: import chardet import urllib.request testdata = urllib.request.urlopen('http://m2.cn.bing.com/').read() print(chardet.detect(te

  • Python接口自动化判断元素原理解析

    这篇文章主要介绍了Python接口自动化判断元素原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 背景: 在做接口自动化时,通常会判断接口返回中的数据信息,与数据库中返回的数据信息是否一致,比如:将接口返回信息的用户姓名存放到一个列表中,将数据库返回的用户姓名存放到另一个列表中,这时需要判断两个列表是否一致,如果不一致,将不同的元素信息分别回写到excel文件中,可以一目了然的看出哪些信息返回的不正确. 下列代码中直接存放列表信息,比较如

随机推荐