ironpython console_IronPython 源码剖析系列(2):IronPython 引擎的运作流程

一、入口点

Python 程序的执行是从 hosting 程序 ipy.exe 开始的,而他的入口点则在控制台这个类中:

classPythonCommandLine {

[STAThread]staticintMain(string[] rawArgs) {//dot.gif//创建 Python 引擎engine=newPythonEngine(options);//创建 __main__ 模块CreateMainModule();//dot.gif//这里调用 Run 方法returnRun(engine, args==null?null: args.Count>0?args[0] :null);//dot.gif}//运行引擎privatestaticintRun(PythonEngine engine,stringfileName) {try{//输入语法://ipy -c "print 'ok'"if(ConsoleOptions.Command!=null) {//直接执行一个字符串表示的 python 代码returnRunString(engine, ConsoleOptions.Command); }elseif(fileName==null) {#if!IRONPYTHON_WINDOW//交互式执行returnRunInteractive(engine);#elsereturn0;#endif}else{//执行文件内容returnRunFile(engine, fileName);

}

}catch(System.Threading.ThreadAbortException tae) {if(tae.ExceptionStateisPythonKeyboardInterruptException) {

Thread.ResetAbort();

}return-1;

}

}

}

在这里我们看到可以用三种主要的方式来执行 python 代码,分别是:

1. 交互式

具体来说就是在命令行状态下,先开启一个控制台,然后在 shell 中输入 python 代码执行。

执行情况如下所示:

H:/ipy2>ipy

IronPython1.0(1.0.61005.1977) on .NET2.0.50727.42Copyright (c) Microsoft Corporation. All rights reserved.>>>print"OK"OK>>>

2. 直接以参数的形式指定一个字符串表示的代码片段来执行

在控制台下输入如下命令,执行情况:

H:/ipy2>ipy-c"print 'ok'"ok

H:/ipy2>

3. 通过源代码文件的方式执行

命令如下:

ipy b.py

注意这个命令还有个参数形式如下:

ipy-i b.py

这个命令的执行结果是,b.py 程序执行后,将自动打开一个 python 的 shell,以便允许在这里做一些操作。

下面我们依次来分析一下这几种情况下的执行流程。

交互式输入(1)和直接执行代码片段(2)的方式,实际的流程是类似的。见如下代码跟踪:

classPythonCommandLine {//让 Engine 执行 string 命令privatestaticintRunString(PythonEngine engine,stringcommand) {//一些初始化动作//dot.gif//执行engine.ExecuteToConsole(command);//dot.gif}privatestaticintRunInteractive(PythonEngine engine) {//一些初始化动作//dot.gifresult=RunInteractive();//dot.gif}privatestaticintRunInteractive() {returnRunInteractiveLoop(); }//循环的执行控制台交互privatestaticintRunInteractiveLoop() {boolcontinueInteraction=true;intresult=0;while(continueInteraction) {

result=TryInteractiveAction(delegate(outboolcontinueInteractionArgument) {//这个方法会读取一次交互输入,并通过 PythonEngine,//尝试用 Parser 解析输入的字符串。如失败则终止continueInteractionArgument=DoOneInteractive();return0;

},outcontinueInteraction);

}returnresult;

}//做一次交互publicstaticboolDoOneInteractive() {boolcontinueInteraction;//读取一个语句并尝试解析之strings=ReadStatement(outcontinueInteraction);//dot.gif//执行读入的内容engine.ExecuteToConsole(s);returntrue;

}

}

OK,这里我们看到情况 1 和 2 殊途同归,最终都调用了

engine.ExecuteToConsole(s);

这里的 PythonEngine (Python 引擎) 我们可以看作是整个 hosting 程序的核心调度器。

二、现在看看 engine 是如何执行以字符串方式传递过来的代码的。----CompiledCode(zcl:针对指令行)

publicclassPythonEngine : IDisposable {//在控制台上执行一个字符串publicvoidExecuteToConsole(stringtext, EngineModule engineModule, IDictionarylocals) {

ModuleScope moduleScope=GetModuleScope(engineModule, locals);

CompilerContext context=DefaultCompilerContext("");//创建 Parser. 利用此 Parser 来解析输入的字符串。Parser p=Parser.FromString(Sys, context, text);boolisEmptyStmt=false;//解析为语句Statement s=p.ParseInteractiveInput(false,outisEmptyStmt);if(s!=null) {//编译生成代码CompiledCode compiledCode=OutputGenerator.GenerateSnippet(context, s,true,false);

Exception ex=null;//如果有命令分派者,则交给他去执行。//命令分派者的机制允许代码被执行在另一个线程中,比如 winform 的控件里,//而不是固定在控制台if(consoleCommandDispatcher!=null) {//创建匿名委托CallTarget0 runCode=delegate() {//运行编译过的代码try{ compiledCode.Run(moduleScope);}catch(Exception e) { ex=e; }returnnull;

};//交给命令分派者去执行consoleCommandDispatcher(runCode);//We catch and rethrow the exception since it could have been thrown on another thread//捕获到异常,并重新抛出。因为它可能在另一个线程上被抛出了。if(ex!=null)throwex;

}else{//否则在当前线程直接执行//运行编译过的代码compiledCode.Run(moduleScope); }

}

}

}

这个方法比较短,我就全部贴上来了。

我们可以看到一个很清晰的执行步骤:

从输入的字符串开始

-> 解析器(Parser)

-> 解析的产物是语句(Statement)

-> 利用 OutputGenerator 的 GenerateSnippet 方法生成 CompiledCode.

-> 最终调用 compiledCode.Run(moduleScope),在一个模块范围中执行编译过的代码。

解析器(Parser) 的作用是语法分析。在其内部,他会调用到词法分析器(Tokenizer),词法分析器是完成词法分析,将源代码字符串解析为一个一个的标识符(Token). 解析器反复判断词法分析器分析的结果,将一个个的标识符构造为语句(Statement),并构造出语法树。

在这里,语句(Statement) 分为很多种,比如 IfStatement, ForStatement 等,并且语句具备了可以执行的能力,其原理是通过其 Emit 方法,发送 IL 代码给代码生成器(CodeGen 或者 TypeGen)。另外由于有 SuiteStatement 等子类的帮助,语句自身就可以是一个复合的结构(Composition pattern)。

在得到语法树之后,Python 引擎调用了 OutputGenerator 这个生成器。其 GenerateSnippet 方法负责产生最终可调用的代码 CompiledCode, 这个方法比较琐碎,就不列举了。

CompiledCode 中,有一个供调用者使用的委托 CompiledCodeDelegate,这表明 CompiledCode 是真正可执行的对象了。

publicclassCompiledCode {//这就是该 CompiledCode 得以执行的代码的委托privateCompiledCodeDelegate code;//执行internalobjectRun(ModuleScope moduleScope) {//复制将要运行的模块范围moduleScope=(ModuleScope)moduleScope.Clone();//在其中设定需要的静态数据moduleScope.staticData=staticData;//通过委托调用该段代码returncode(moduleScope); }

}

我们看到,编译过的代码需要在一个所谓的模块范围(ModuleScope) 中执行。那么这个模块范围又是什么东西呢?

IronPython 中,代表 python 语义上的模块的类是 PythonModule. 通常的文件形式的 IronPython 代码是被编译为 CompiledModule 来执行的,它对应于一个 PythonModule. 而代码片段 (包括交互输入和其他情况下的小段代码,统称代码片段(Code Snippet)) 本身作为字符串被传递的时候,并不具有执行环境(Context 或者说 Scope)的概念(所在的模块,全局变量之类)。所以 IronPython 的引擎内就设计了一个 ModuleScope 的概念,代表代码片段赖以执行的语义环境。

ModuleScope 包括一个语义上的 PythonModule, 以及附加的一些全局变量之类的信息。在默认情况下,代码片段在 IronPython 引擎负责创建的 __main__ 模块中工作。

这里需要注意的是,ModuleScope 并不唯一对应于PythonModule. 一个 PythonModule 可以有多个 ModuleScope.

OK,以上我们看清了代码片段的执行是最终通过 CompiledCode 完成,

三、下面继续看一下源代码文件是怎么被处理的-----CompiledModule (zcl:针对文件)

我们从刚才跳过的 RunFile 方法开始看起,一路跟踪下去:

classPythonCommandLine {privatestaticintRunFile(PythonEngine engine,stringfileName) {//dot.gif#if!IRONPYTHON_WINDOW//如果打开了 -i 选项if(ConsoleOptions.Introspection) {

RunFileWithIntrospection(fileName); }else{

OptimizedEngineModule engineModule=engine.CreateOptimizedModule(fileName,"__main__",true);

engineModule.Execute();

}#elseOptimizedEngineModule engineModule=engine.CreateOptimizedModule(fileName,"__main__",true); engineModule.Execute();#endifresult=0;

}#if!IRONPYTHON_WINDOW//执行文件后打开控制台publicstaticvoidRunFileWithIntrospection(stringfileName) {boolcontinueInteraction;

TryInteractiveAction(delegate(outboolcontinueInteractionArgument) {//创建模块OptimizedEngineModule engineModule=engine.CreateOptimizedModule(fileName,"__main__",true);

engine.DefaultModule=engineModule;//执行engineModule.Execute();

continueInteractionArgument=true;return0;

},outcontinueInteraction);if(continueInteraction)//如果指定了 -i 选项,则运行完文件后进入控制台RunInteractiveLoop();

}#endif//用最优化代码创建 module. 其限制是,用户不能任意指定 globals 字典。publicOptimizedEngineModule CreateOptimizedModule(stringfileName,stringmoduleName,boolpublishModule) {if(fileName==null)thrownewArgumentNullException("fileName");if(moduleName==null)thrownewArgumentNullException("moduleName");

CompilerContext context=newCompilerContext(fileName);//创建解析器Parser p=Parser.FromFile(Sys, context, Sys.EngineOptions.SkipFirstLine,false);//解析出语法树Statement s=p.ParseFileInput();//这里实际产生一个类型PythonModule module=OutputGenerator.GenerateModule(Sys, context, s, moduleName);//模块范围ModuleScope moduleScope=newModuleScope(module);//EngineModuleOptimizedEngineModule engineModule=newOptimizedEngineModule(moduleScope);

module.SetAttr(module, SymbolTable.File, fileName);//如果发布,则将模块添加到 Sys 的模块字典中去if(publishModule) {

Sys.modules[moduleName]=module;

}returnengineModule;

}

}

词法和语法分析的部分,和前面类似。我们循着 OutputGenerator 跟下去:

staticclassOutputGenerator {//产生模块publicstaticPythonModule GenerateModule(SystemState state, CompilerContext context, Statement body,stringmoduleName) {//dot.gifreturnDoGenerateModule(state, context, gs, moduleName, context.SourceFile, suffix);//dot.gif}privatestaticPythonModule DoGenerateModule(SystemState state, CompilerContext context, GlobalSuite gs,stringmoduleName,stringsourceFileName,stringoutSuffix) {//dot.gifAssemblyGen ag=newAssemblyGen(moduleName+outSuffix, outDir, fileName+outSuffix+".exe",true);

ag.SetPythonSourceFile(fullPath);

TypeGen tg=GenerateModuleType(moduleName, ag);

CodeGen cg=GenerateModuleInitialize(context, gs, tg);

CodeGen main=GenerateModuleEntryPoint(tg, cg, moduleName,null);

ag.SetEntryPoint(main.MethodInfo, PEFileKinds.ConsoleApplication);

ag.AddPythonModuleAttribute(tg, moduleName);

Type ret=tg.FinishType();

Assembly assm=ag.DumpAndLoad();

ret=assm.GetType(moduleName);//注意这里PythonModulepmod=CompiledModule.Load(moduleName, ret, state);returnpmod;

}

}

这里我们可以发现,源文件形式的代码,是被创建为 CompiledModule 来执行的。CompiledModule (zcl:针对文件)和 CompiledCode(zcl:针对指令行) 所依赖的 ModuleScope 一样,都会对应于一个语义上的 PythonModule, 但其区别是 CompiledModule 并不包含该 PythonModule 的状态信息。

接下来的代码创建了 OptimizedEngineModule, 然后调用其 Execute 方法:

publicclassOptimizedEngineModule : EngineModule {boolglobalCodeExecuted;internalOptimizedEngineModule(ModuleScope moduleScope)

:base(moduleScope) {

Debug.Assert(GlobalsAdapterisCompiledModule);

}publicvoidExecute() {//确保只执行一次 global 代码if(globalCodeExecuted)thrownewInvalidOperationException("Cannot execute global code multiple times");

globalCodeExecuted=true;

Module.Initialize(); }

}

Module 是其父类中定义的一个属性,代表 PythonModule:

publicclassEngineModule {internalPythonModule Module {get{returndefaultModuleScope.Module; } }

}

PythonModule 代码如下:

[PythonType("module")]publicclassPythonModule : ICustomAttributes, IModuleEnvironment, ICodeFormattable {privateInitializeModule initialize;publicvoidInitialize() {

Debug.Assert(__dict__!=null,"Generated modules should always get a __dict__");if(initialize!=null) {

initialize();

}

}

}

其中被调用的 Initialize 方法是一个委托:

publicdelegatevoidInitializeModule();

而这个委托所指向的方法是被 OutputGenerator 创建出来的。

现在为止,我们已经走马观花一般的领略了 IronPython 的主要执行步骤,其中涉及了下列几个技术细节并未阐述,在后续文章中,我将选择其中有意思的部分进行一些分析。

这些细节是:

1. 词法分析,语法分析涉及的类 Parser, Token, Tokenizer 之类,比较简单。

2. 语法层面上的一些类。比如 Statement, Expression 等。

3. 代码生成相关的内容。涉及到 CodeGen, TypeGen, OutputGenerator 等类别。基本上是通过 Emit 方式发送 IL 代码来进行,代码比较复杂琐碎。

4. Python 的类型系统,以及其特性的实现,这个是重点!

5. 从反编译的角度来分析 Python 产生的程序集及其执行原理。这也是有趣的部分。

有兴趣的朋友请继续期待后续系列文章。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值