python解釋器spython使用及原理解析
簡(jiǎn)介
出于個(gè)人愛(ài)好和某種需求,我再16年對(duì)python的解釋器產(chǎn)生了濃厚興趣,并且下定決心重新實(shí)現(xiàn)一個(gè)版本。我個(gè)人再游戲服務(wù)器開(kāi)發(fā)中,對(duì)c++嵌入lua和python都有著豐富應(yīng)用經(jīng)驗(yàn),自認(rèn)為對(duì)二者的優(yōu)劣有著深刻的理解。
python針對(duì)lua的最大優(yōu)勢(shì)是python是完備的程序語(yǔ)言,類(lèi)、模塊包括豐富的庫(kù)和方便好用的字符串操作,可以說(shuō)python用來(lái)實(shí)現(xiàn)功能會(huì)優(yōu)雅很多,而lua最大的優(yōu)勢(shì)就是小巧高效,另外lua的lua_state是可以有多個(gè)實(shí)例的,這樣就可以多線(xiàn)程使用lua(一個(gè)線(xiàn)程單獨(dú)一個(gè)lua_state),而python解釋器因?yàn)橛腥纸忉屍麈i,所以無(wú)法實(shí)現(xiàn)多python解釋器實(shí)例。
考慮到在嵌入python的應(yīng)用場(chǎng)景中,所用到python的功能都是比較簡(jiǎn)單通用的功能,比如類(lèi)、模塊,函數(shù),一些復(fù)雜的類(lèi)庫(kù)也不常用,所以我就想實(shí)現(xiàn)一個(gè)不使用全局解釋器鎖,可以有多個(gè)python解釋器鎖的解釋器。所以16年底,我自己實(shí)現(xiàn)了一下python解釋器第一版,第一版是使用AST虛擬語(yǔ)法樹(shù)直接解析的,雖然做了必要的優(yōu)化,但是性能。。。。仍然不忍直視。
平常我一直吐槽python跑的沒(méi)有l(wèi)ua快,但是吐槽是一碼事,自己實(shí)現(xiàn)真的就是另一碼事了。我仔細(xì)分析了第一版性能低的原因是選錯(cuò)了路!python的虛擬機(jī)是講語(yǔ)法樹(shù)翻譯成ByteCode,然后有個(gè)Virtual Machine不斷的解釋bytecode,而vm的運(yùn)行又分堆棧模式和寄存器模式,python就是堆棧模式的,而lua是寄存器模式的,寄存器模式是現(xiàn)在的趨勢(shì),這也是lua跑到更快的重要原因。我的第一版VM用AST直接跑,選錯(cuò)了路,無(wú)論如何也太快不了。
但是我仍然把這個(gè)第一版打了個(gè)分支,分享出來(lái),因?yàn)楫?dāng)我實(shí)現(xiàn)用寄存器模式的VM的時(shí)候,感覺(jué)無(wú)論如何也無(wú)法設(shè)計(jì)的像AST直接解析的VM那樣優(yōu)雅、直接。AST直接解析的方式真的太直觀了,雖然效率很低,但是其仍然有很大的應(yīng)用價(jià)值。
比如protocolbuff、thrift這些通過(guò)定義語(yǔ)法文件生成代碼的這類(lèi)工具,對(duì)語(yǔ)法解析的效率要求不高,那么這個(gè)版本的VM再這些領(lǐng)域還是有很大的參考價(jià)值。
內(nèi)部實(shí)現(xiàn)層次:

Python BNF
一提到實(shí)現(xiàn)腳本解釋器,估計(jì)很多人都會(huì)撓頭,不知道從何入手。剛開(kāi)始我也是這樣,我把大學(xué)里的編譯原理從床底下一堆打入冷宮的數(shù)量翻出來(lái),一頓猛看。但是仍然沒(méi)有找到很大頭緒,后來(lái)我就在python.org上一頓逛,也下載了python的源碼分析,源碼目錄有python的BNF描述文件,因?yàn)槲乙呀?jīng)看過(guò)一遍編譯原理了,BNF就看的很懂,從頭到尾讀了一遍了以后,靈光乍現(xiàn)??!BNF就是完整的解析python語(yǔ)法的流程說(shuō)明啊!截取一小段做個(gè)說(shuō)明:
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
簡(jiǎn)單解釋下,python的Grammar BNF是從頂之下遞歸描述的。上面最上邊定義的是compound_stmt復(fù)雜語(yǔ)句,而compound_stmt有if、while、for、try、with、函數(shù)定義、類(lèi)定義、修飾器定義幾種,下面緊接著定義了if語(yǔ)句if_stmt的語(yǔ)法規(guī)則,這樣在c++實(shí)現(xiàn)解析python語(yǔ)法的時(shí)候,就可以從頂向下按照這個(gè)BNF嘗試解析,如果不滿(mǎn)足這個(gè)BNF語(yǔ)法要求的就報(bào)錯(cuò)。我為了生成跟這個(gè)BNF一致的代碼結(jié)構(gòu),寫(xiě)了個(gè)python腳本解析這個(gè)BNF自動(dòng)生成C++的解析函數(shù)。生成的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的實(shí)現(xiàn)
scanner負(fù)責(zé)解析python代碼,把python代碼分隔這一個(gè)個(gè)Token對(duì)象,并且Token對(duì)象的定義如下:
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代碼,而不解析語(yǔ)法,所有的python代碼都會(huì)解析成要么整數(shù),要么浮點(diǎn)數(shù)要么字符串。這個(gè)跟原生的python是有區(qū)別的,原生python的數(shù)字對(duì)象可以表達(dá)任意數(shù)字,但是為了實(shí)現(xiàn)簡(jiǎn)便,做了簡(jiǎn)化處理,這也是參考了lua的實(shí)現(xiàn)方式每token對(duì)象會(huì)記錄所屬的行號(hào),方便語(yǔ)法報(bào)錯(cuò)提供有用的信息。
具體scanner的實(shí)現(xiàn)就不貼出來(lái)了,感興趣的可以去查看源碼,還是比較簡(jiǎn)單的。
Parser的實(shí)現(xiàn)
Parser的頭文件是腳本解析BNF自動(dòng)生成的。負(fù)責(zé)把scanner解析的token列表,按照BNF的規(guī)則構(gòu)造成AST。AST節(jié)點(diǎn)對(duì)象定義為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節(jié)點(diǎn)的幾個(gè)操作。最主要的就是求值操作eval。比如100求值就是100,'abc'求值就是字符串'abc',生成對(duì)應(yīng)的值對(duì)象。每個(gè)值對(duì)象都繼承PyObj。每個(gè)PyObj都會(huì)定義ObjHander接口用于實(shí)現(xiàn)python對(duì)象的各個(gè)操作,比如+、-、/等,不同的python值對(duì)象,響應(yīng)的操作是不一樣,這里利用了c++的多態(tài)。
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庫(kù)的實(shí)現(xiàn)
實(shí)現(xiàn)的python庫(kù)列表如下:
- list dict tuple copy string
- datetime
- json
- math
- os
- random
- open stringio
- struct
- sys
- weak
總結(jié)
spython就是small python,本來(lái)想實(shí)現(xiàn)最簡(jiǎn)版本的python解釋器,后來(lái)實(shí)現(xiàn)的比較順,一口氣把常用的python庫(kù)都實(shí)現(xiàn)了。spython最成功的部分就是ast的解析和執(zhí)行,代碼結(jié)構(gòu)清晰完全按照bnf的流程來(lái),很直接明了。
缺點(diǎn)主要有二。一是語(yǔ)法報(bào)錯(cuò)還是太簡(jiǎn)陋,不夠友好。二是性能達(dá)不到原生python的性能。前文已經(jīng)說(shuō)過(guò)了,要達(dá)到甚至超過(guò)原生python的水平,必須要實(shí)現(xiàn)基于寄存器的VM,這個(gè)已經(jīng)著手再弄了,暫時(shí)還不會(huì)放出代碼,等差不多成型了再放出來(lái)吧。
代碼地址:https://git.oschina.net/ownit/spython
構(gòu)建:Linux下直接make就可以了,win下需要用dev c++
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
分享一下Python數(shù)據(jù)分析常用的8款工具
Python是數(shù)據(jù)處理常用工具,可以處理數(shù)量級(jí)從幾K至幾T不等的數(shù)據(jù),具有較高的開(kāi)發(fā)效率和可維護(hù)性,還具有較強(qiáng)的通用性和跨平臺(tái)性,這里就為大家分享幾個(gè)不錯(cuò)的數(shù)據(jù)分析工具,需要的朋友可以參考下2018-04-04
python線(xiàn)程安全及多進(jìn)程多線(xiàn)程實(shí)現(xiàn)方法詳解
這篇文章主要介紹了python線(xiàn)程安全及多進(jìn)程多線(xiàn)程實(shí)現(xiàn)方法詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09
Python基于yield遍歷多個(gè)可迭代對(duì)象
這篇文章主要介紹了Python基于yield遍歷多個(gè)可迭代對(duì)象,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03
淺談Python的條件判斷語(yǔ)句if/else語(yǔ)句
這篇文章主要介紹了Python的條件判斷語(yǔ)句if/else語(yǔ)句,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Python實(shí)現(xiàn)正則表達(dá)式匹配任意的郵箱方法
今天小編就為大家分享一篇Python實(shí)現(xiàn)正則表達(dá)式匹配任意的郵箱方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-12-12
python利用JMeter測(cè)試Tornado的多線(xiàn)程
這篇文章主要介紹了python利用JMeter測(cè)試Tornado的多線(xiàn)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
Python打包文件執(zhí)行報(bào)錯(cuò):ModuleNotFoundError: No module 
這篇文章給大家介紹了Python打包文件執(zhí)行報(bào)錯(cuò):ModuleNotFoundError: No module named ‘pymssql‘的解決方法,如果有遇到相同問(wèn)題的朋友可以參考閱讀一下本文2023-10-10
Python使用字典實(shí)現(xiàn)的簡(jiǎn)單記事本功能示例
這篇文章主要介紹了Python使用字典實(shí)現(xiàn)的簡(jiǎn)單記事本功能,結(jié)合實(shí)例形式分析了基于字典的數(shù)據(jù)存儲(chǔ)、讀取、刪除等相關(guān)操作技巧,需要的朋友可以參考下2019-08-08

