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

一、入口点

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

;
        }
    }
}

在这里我们看到可以用三种主要的方式来执行 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 -c "print 'ok'"
ok

H:/ipy2>

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

命令如下:

ipy b.py

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

ipy -i b.py

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

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

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

;
                },
                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 方法开始看起,一路跟踪下去:

;
            },
            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 产生的程序集及其执行原理。这也是有趣的部分。

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

IronPython 源码剖析系列(2):IronPython 引擎的运作流程的更多相关文章

  1. IronPython 源码剖析系列(1):IronPython 编译器

    自 IronPython 正式发布以来,由于对 Python 语言的喜爱所驱使,同时我想藉此去了解一下编程语言的编译器,分析器等程序是什么原理,如何运作的,所以我开始了对 IronPython 源代码 ...

  2. WorldWind源码剖析系列:数学引擎类MathEngine

    PluginSDK中的MathEngine类是密封类.不可继承,主要完成通用的数学计算功能.由于按平面展开层层划分,所以在WW里用到一个row,col的概念,类MathEngine封装了从行/列到经/ ...

  3. 【java集合框架源码剖析系列】java源码剖析之TreeSet

    本博客将从源码的角度带领大家学习TreeSet相关的知识. 一TreeSet类的定义: public class TreeSet<E> extends AbstractSet<E&g ...

  4. 【java集合框架源码剖析系列】java源码剖析之HashSet

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于HashSet的知识. 一HashSet的定义: public class HashSet&l ...

  5. 【java集合框架源码剖析系列】java源码剖析之TreeMap

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本.本博客将从源码角度带领大家学习关于TreeMap的知识. 一TreeMap的定义: public class TreeMap&l ...

  6. 【java集合框架源码剖析系列】java源码剖析之ArrayList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 本博客将从源码角度带领大家学习关于ArrayList的知识. 一ArrayList类的定义: public class Arr ...

  7. 【java集合框架源码剖析系列】java源码剖析之LinkedList

    注:博主java集合框架源码剖析系列的源码全部基于JDK1.8.0版本. 在实际项目中LinkedList也是使用频率非常高的一种集合,本博客将从源码角度带领大家学习关于LinkedList的知识. ...

  8. 【java集合框架源码剖析系列】java源码剖析之HashMap

    前言:之所以打算写java集合框架源码剖析系列博客是因为自己反思了一下阿里内推一面的失败(估计没过,因为写此博客已距阿里巴巴一面一个星期),当时面试完之后感觉自己回答的挺好的,而且据面试官最后说的这几 ...

  9. WorldWind源码剖析系列:星球类World

    星球类World代表通用的星球类,因为可能需要绘制除地球之外的其它星球,如月球.火星等.该类的类图如下. 需要说明的是,在WorldWind中星球球体的渲染和经纬网格的渲染时分别绘制的.经纬网格的渲染 ...

随机推荐

  1. Xcode中需要熟悉的常用快捷键

    因为工作需要,笔者最近开始接触Xcode这款Mac系统下的强大的编程软件.因为个人习惯,每当接触新的软件的时候总会先去了解它的一些常用快捷键.经过多方查阅总结出以下内容,希望对刚刚接触Xcode的初学 ...

  2. 这样写JS的方式对吗?

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. Codevs 3731 寻找道路 2014年 NOIP全国联赛提高组

    3731 寻找道路 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description 在有向图G中,每条边的长度均为1,现给定起点和终点,请你在图中找 ...

  4. linux 文件类型

    文件类型 1)windows中是以文件的扩展名来区分文件类型的 2)LINUX中文件扩展名和文件类型没有关系. 3)为了容易区分和兼容用户使用windows的习惯,我们也经常扩展名,但是在LINUX系 ...

  5. ubuntu 安装 桌面 awesome

    受了ubuntu 12.04自带的桌面,运行太卡了 http://www.linuxzen.com/awesometmuxgnomedoda-zao-gao-xiao-linuxzhuo-mian-h ...

  6. void void*

    void类型及void指针 1.概述 许多初学者对C/C 语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误.本文将对void关键字的深刻含义进行解说,并 详述void及void指 ...

  7. 【转】Ext.ajax.request 中的success和failure

    原文链接:Ext.ajax.request 中的success和failure Ajax request对象的success事件表示request过程中没有发生错误,和自己的业务逻辑无关, 如果访问不 ...

  8. linux下 链接 sqlserver数据库 驱动的安装

    1.必需安装freetds 安装pdo_dblib扩展首先需要安装freetds. freeTDS的最新稳定版是0.91,这个可以在官网上下载http://www.freetds.org/ ,也可以在 ...

  9. Linux - tar命令过滤某个目录

    tar -zcvf abc.20140325.tar.gz --exclude=./abc/kkk/--exclude=./abc/hhh/ ./abc/ 发现没有过滤成功,后来发现这种方法是不对的( ...

  10. git push用法和常见问题分析

    在使用git 处理对android的修改的过程之中总结的.但不完善 Git push $ git push origin test:master         // 提交本地test分支作为远程的m ...