博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
IronPython 源码剖析系列(2):IronPython 引擎的运作流程
阅读量:5122 次
发布时间:2019-06-13

本文共 10028 字,大约阅读时间需要 33 分钟。

一、入口点

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

class
 PythonCommandLine {
    [STAThread]
    
static
 
int
 Main(
string
[] rawArgs) {
        
//
 
        
//
 创建 Python 引擎
        engine 
=
 
new
 PythonEngine(options);
        
//
 创建 __main__ 模块
        CreateMainModule();
    
        
//
    
        
//
 这里调用 Run 方法
        
return
 Run(engine, args 
==
 
null
 
?
 
null
 : args.Count 
>
 
0
 
?
 args[
0
] : 
null
);
    
        
//
    }
    
//
 运行引擎
    
private
 
static
 
int
 Run(PythonEngine engine, 
string
 fileName) {
        
try
 {
    
//
 输入语法:
    
//
 ipy -c "print 'ok'"
            
if
 (ConsoleOptions.Command 
!=
 
null
) {
                
//
 直接执行一个字符串表示的 python 代码
                
return
 RunString(engine, ConsoleOptions.Command);
            } 
else
 
if
 (fileName 
==
 
null
) {
#if
 !IRONPYTHON_WINDOW
                
//
 交互式执行
                
return
 RunInteractive(engine);
#else
                
return
 
0
;
#endif
            } 
else
 {
                
//
 执行文件内容
                
return
 RunFile(engine, fileName);
            }
        } 
catch
 (System.Threading.ThreadAbortException tae) {
            
if
 (tae.ExceptionState 
is
 PythonKeyboardInterruptException) {
                Thread.ResetAbort();
            }
            
return
 
-
1
;
        }
    }
}

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

1. 交互式

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

H:/ipy2
>
ipy
IronPython 
1.0
 (
1.0
.
61005.1977
) on .NET 
2.0
.
50727.42
Copyright (c) Microsoft Corporation. All rights reserved.
>>>
 
print
 
"
OK
"
OK
>>>

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

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

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

3. 通过源代码文件的方式执行
命令如下:

ipy b.py

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

ipy 
-
i b.py

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

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

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

 

class
 PythonCommandLine {
    
//
 让 Engine 执行 string 命令
    
private
 
static
 
int
 RunString(PythonEngine engine, 
string
 command) {
        
//
 一些初始化动作
        
//
 
        
//
 执行
        engine.ExecuteToConsole(command);
        
        
//
 
    }
    
private
 
static
 
int
 RunInteractive(PythonEngine engine) {
        
//
 一些初始化动作
        
//
 
        result 
=
 RunInteractive();
        
        
//
 
    }
    
private
 
static
 
int
 RunInteractive() {
        
return
 RunInteractiveLoop();
    }
    
//
 循环的执行控制台交互
    
private
 
static
 
int
 RunInteractiveLoop() {
        
bool
 continueInteraction 
=
 
true
;
        
int
 result 
=
 
0
;
        
while
 (continueInteraction) {
            result 
=
 TryInteractiveAction(
                
delegate
(
out
 
bool
 continueInteractionArgument) {
                    
//
 这个方法会读取一次交互输入,并通过 PythonEngine,
                    
//
 尝试用 Parser 解析输入的字符串。如失败则终止
                    continueInteractionArgument 
=
 DoOneInteractive();
                    
return
 
0
;
                },
                
out
 continueInteraction);
        }
        
return
 result;
    }
    
//
 做一次交互
    
public
 
static
 
bool
 DoOneInteractive() {
        
bool
 continueInteraction;
        
//
 读取一个语句并尝试解析之
        
string
 s 
=
 ReadStatement(
out
 continueInteraction);
        
//
 
        
//
 执行读入的内容
        engine.ExecuteToConsole(s);
        
return
 
true
;
    }
}

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

engine.ExecuteToConsole(s);

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

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

public
 
class
 PythonEngine : IDisposable {
    
//
 在控制台上执行一个字符串
    
public
 
void
 ExecuteToConsole(
string
 text, EngineModule engineModule, IDictionary
<
string
object
>
 locals) {
        ModuleScope moduleScope 
=
 GetModuleScope(engineModule, locals);
        CompilerContext context 
=
 DefaultCompilerContext(
"
<stdin>
"
);
        
//
 创建 Parser. 利用此 Parser 来解析输入的字符串。
        Parser p 
=
 Parser.FromString(Sys, context, text);
        
bool
 isEmptyStmt 
=
 
false
;
        
//
 解析为语句
        Statement s 
=
 p.ParseInteractiveInput(
false
out
 isEmptyStmt);
    
        
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; }
                    
return
 
null
;
                };
                
//
 交给命令分派者去执行
                consoleCommandDispatcher(runCode);
                
//
 We catch and rethrow the exception since it could have been thrown on another thread
                
//
 捕获到异常,并重新抛出。因为它可能在另一个线程上被抛出了。
                
if
 (ex 
!=
 
null
)
                    
throw
 ex;
            } 
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 是真正可执行的对象了。

 

public
 
class
 CompiledCode {
    
//
 这就是该 CompiledCode 得以执行的代码的委托
    
private
 CompiledCodeDelegate code;
    
//
 执行
    
internal
 
object
 Run(ModuleScope moduleScope) {
        
//
 复制将要运行的模块范围
        moduleScope 
=
 (ModuleScope)moduleScope.Clone();
        
        
//
 在其中设定需要的静态数据
        moduleScope.staticData 
=
 staticData;
        
        
//
 通过委托调用该段代码
        
return
 code(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 方法开始看起,一路跟踪下去: 

class
 PythonCommandLine {
    
private
 
static
 
int
 RunFile(PythonEngine engine, 
string
 fileName) {
        
//
 
    
#if
 !IRONPYTHON_WINDOW
        
//
 如果打开了 -i 选项
        
if
 (ConsoleOptions.Introspection) {
            RunFileWithIntrospection(fileName);
        } 
else
 {
            OptimizedEngineModule engineModule 
=
 engine.CreateOptimizedModule(fileName, 
"
__main__
"
true
);
            engineModule.Execute();
        }
#else
        OptimizedEngineModule engineModule 
=
 engine.CreateOptimizedModule(fileName, 
"
__main__
"
true
);
        engineModule.Execute();
#endif
        result 
=
 
0
;
            
    }
#if
 !IRONPYTHON_WINDOW
    
//
 执行文件后打开控制台
    
public
 
static
 
void
 RunFileWithIntrospection(
string
 fileName) {
        
bool
 continueInteraction;
        TryInteractiveAction(
            
delegate
(
out
 
bool
 continueInteractionArgument) {
                
//
 创建模块
                OptimizedEngineModule engineModule 
=
 engine.CreateOptimizedModule(fileName, 
"
__main__
"
true
);
                engine.DefaultModule 
=
 engineModule;
                
//
 执行
                engineModule.Execute();
                continueInteractionArgument 
=
 
true
;
                
return
 
0
;
            },
            
out
 continueInteraction);
        
if
 (continueInteraction)
            
//
 如果指定了 -i 选项,则运行完文件后进入控制台
            RunInteractiveLoop();
    }
#endif
    
    
//
 用最优化代码创建 module. 其限制是,用户不能任意指定 globals 字典。    
    
public
 OptimizedEngineModule CreateOptimizedModule(
string
 fileName, 
string
 moduleName, 
bool
 publishModule) {
        
if
 (fileName 
==
 
null
throw
 
new
 ArgumentNullException(
"
fileName
"
);
        
if
 (moduleName 
==
 
null
throw
 
new
 ArgumentNullException(
"
moduleName
"
);
        CompilerContext context 
=
 
new
 CompilerContext(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 
=
 
new
 ModuleScope(module);
        
        
//
 EngineModule
        OptimizedEngineModule engineModule 
=
 
new
 OptimizedEngineModule(moduleScope);
        module.SetAttr(module, SymbolTable.File, fileName);
        
//
 如果发布,则将模块添加到 Sys 的模块字典中去
        
if
 (publishModule) {
            Sys.modules[moduleName] 
=
 module;
        }
        
return
 engineModule;
    }
}

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

 

static
 
class
 OutputGenerator {
    
//
 产生模块
    
public
 
static
 PythonModule GenerateModule(SystemState state, CompilerContext context, Statement body, 
string
 moduleName) {
        
//
 
        
return
 DoGenerateModule(state, context, gs, moduleName, context.SourceFile, suffix);
        
        
//
 
    }
    
private
 
static
 PythonModule DoGenerateModule(SystemState state, CompilerContext context, GlobalSuite gs, 
string
 moduleName, 
string
 sourceFileName, 
string
 outSuffix) {
        
//
 
        AssemblyGen ag 
=
 
new
 AssemblyGen(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);
        
//
 注意这里
        PythonModule pmod 
=
 CompiledModule.Load(moduleName, ret, state);
        
return
 pmod;
    }
}

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

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

public
 
class
 OptimizedEngineModule : EngineModule {
    
bool
 globalCodeExecuted;
    
internal
 OptimizedEngineModule(ModuleScope moduleScope)
        : 
base
(moduleScope) {
        Debug.Assert(GlobalsAdapter 
is
 CompiledModule);
    }
    
public
 
void
 Execute() {
        
//
 确保只执行一次 global 代码
        
if
 (globalCodeExecuted)
            
throw
 
new
 InvalidOperationException(
"
Cannot execute global code multiple times
"
);
        globalCodeExecuted 
=
 
true
;
        Module.Initialize();
    }
}

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

public
 
class
 EngineModule {
    
internal
 PythonModule Module { 
get
 { 
return
 defaultModuleScope.Module; } }
}

PythonModule 代码如下:

[PythonType(
"
module
"
)]
public
 
class
 PythonModule : ICustomAttributes, IModuleEnvironment, ICodeFormattable {
    
private
 InitializeModule initialize;
    
public
 
void
 Initialize() {            
        Debug.Assert(__dict__ 
!=
 
null
"
Generated modules should always get a __dict__
"
);
        
if
 (initialize 
!=
 
null
) {
            initialize();
        }
    }
}

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

public
 
delegate
 
void
 InitializeModule();

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

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

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

2. 语法层面上的一些类。比如 Statement, Expression 等。
3. 代码生成相关的内容。涉及到 CodeGen, TypeGen, OutputGenerator 等类别。基本上是通过 Emit 方式发送 IL 代码来进行,代码比较复杂琐碎。
4. Python 的类型系统,以及其特性的实现,这个是重点!
5. 从反编译的角度来分析 Python 产生的程序集及其执行原理。这也是有趣的部分。

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

转载于:https://www.cnblogs.com/carl2380/p/3204738.html

你可能感兴趣的文章
MySql学习路线00
查看>>
mysql几种存储引擎介绍
查看>>
转-Android客户端和服务端如何使用Token和Session
查看>>
IOS第14天(2, Modal控制)
查看>>
删除确认代码
查看>>
刻意练习
查看>>
学习笔记13_第三方js控件&EasyUI使用
查看>>
Java变量的初始化问题探究
查看>>
DSU on tree——令人惊叹的想法
查看>>
javascript 闭包
查看>>
约瑟夫环问题
查看>>
c++ __int64
查看>>
IP封锁 (防火墙维护一张IP黑名单)
查看>>
【模板】trie树(字典树)
查看>>
JSON.stringify 语法实例讲解
查看>>
Python6 模块
查看>>
P3377 【模板】左偏树(可并堆)
查看>>
Djang 用户登录
查看>>
Java同步锁——lock与synchronized 的区别【转】
查看>>
洛谷-校门外的树-数组
查看>>