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

原作:木野狐,2006-11-9,转载请注明出处。
上一篇:IronPython 源码剖析系列(1):IronPython 编译器
 
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 是如何执行以字符串方式传递过来的代码的。

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, truefalse );
            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 完成,下面继续看一下源代码文件是怎么被处理的。

我们从刚才跳过的 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 和 CompiledCode 所依赖的 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 产生的程序集及其执行原理。这也是有趣的部分。


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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值