zoukankan      html  css  js  c++  java
  • IronPython 源码剖析系列(2):IronPython 引擎的运作流程

    http://blog.csdn.net/inelm/article/details/4612987

    一、入口点

    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<stringobject> 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(falseout 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 完成,

     

    三、下面继续看一下源代码文件是怎么被处理的-----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 == nullthrow new ArgumentNullException("fileName");
            
    if (moduleName == nullthrow 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 产生的程序集及其执行原理。这也是有趣的部分。


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

  • 相关阅读:
    C#动态调用webservice方法
    WinForm客户端调用 WebService时 如何启用Session
    C# 调用 Web Service 时出现 : 407 Proxy Authentication Required错误的解决办法
    ms sql 在任何位置 添加列
    Python requests
    LookupError: unknown encoding: cp65001
    [转]HTTP请求模型和头信息参考
    【原】使用StarUML画用例图
    【微信转载】Google是如何做测试的
    手机SD卡损坏补救措施
  • 原文地址:https://www.cnblogs.com/carl2380/p/3204738.html
Copyright © 2011-2022 走看看