python語言程序設計,python的解釋器spython介紹

 2023-11-18 阅读 27 评论 0

摘要:Python解釋器spython介紹 簡介   出于個人愛好和某種需求,我再16年對python的解釋器產生了濃厚興趣,并且下定決心重新實現一個版本。我個人再游戲服務器開發中,對c++嵌入lua和python都有著豐富應用經驗,自認為對二者的優劣有著深刻的理

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再這些領域還是有很大的參考價值。
  內部實現層次:

Alt text

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 NEWLINEExprASTPtr parse_single_input();//! file_input: (NEWLINE | stmt)* ENDMARKERExprASTPtr parse_file_input();//! eval_input: testlist NEWLINE* ENDMARKERExprASTPtr parse_eval_input();//! decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINEExprASTPtr parse_decorator();//! decorators: decorator+ExprASTPtr parse_decorators();//! decorated: decorators (classdef | funcdef)ExprASTPtr parse_decorated();//! funcdef: 'def' NAME parameters ':' suiteExprASTPtr 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_stmtExprASTPtr parse_stmt();//! simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINEExprASTPtr 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的實現

python語言程序設計。  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庫列表如下:

  1. list dict tuple copy string
  2. datetime
  3. json
  4. math
  5. os
  6. random
  7. open stringio
  8. struct
  9. sys
  10. weak

總結

python怎么用、  spython就是small python,本來想實現最簡版本的python解釋器,后來實現的比較順,一口氣把常用的python庫都實現了。spython最成功的部分就是ast的解析和執行,代碼結構清晰完全按照bnf的流程來,很直接明了。缺點主要有二。一是語法報錯還是太簡陋,不夠友好。二是性能達不到原生python的性能。前文已經說過了,要達到甚至超過原生python的水平,必須要實現基于寄存器的VM,這個已經著手再弄了,暫時還不會放出代碼,等差不多成型了再放出來吧。
  代碼地址:https://git.oschina.net/ownit/spython
  構建:Linux下直接make就可以了,win下需要用dev c++

更多精彩文章 http://h2cloud.org/

轉載于:https://www.cnblogs.com/zhiranok/p/spython_intro.html

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/4/174387.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息