很多人觉得Python写起来很随意,变量随便用,函数传来传去,类型说变就变。这种“随意”背后,藏着解释器运行时的真实机制。我花了几个月时间,从零开始写一个Python解释器,就是想弄明白这些“随意”到底是怎么实现的。
写解释器第一个震撼来自变量。我原以为变量是盒子,里面放着数据。解释器告诉我,变量是标签,贴在一个叫“对象”的东西上。一个对象可以贴好几个标签,一个标签也能换到别的对象上。这就是为什么你能写 a = [1,2,3] 然后 b = a,改b的内容a也跟着变。标签只是名字,对象才是真实存在的东西。
继续写词法分析和语法分析,我发现Python处理代码的方式更像在讲故事。解释器逐行读取,遇到名字就去查当前环境里有没有这个标签。没有?那就报错。有?那就顺着标签找到对象,执行对应的行为。这段“查标签-找到对象-执行操作”的过程,每时每刻都在发生,而且完全发生在你写代码的那一瞬间。
动态语言最神奇的地方,是我可以在运行中改掉基本行为。比如给一个类动态添加方法,在C++或者Java里想都别想。但在我的解释器里,只需要在类字典里塞一个函数对象就行。class 定义的本质就是把函数和变量打包放进一个命名空间,而这个命名空间在运行时可以随便改。不管你之前怎么写代码,解释器每次执行都是全新查找。
写函数调用那部分让我重新理解了“函数是一等公民”。以前这句话只是背诵的知识点。在解释器层面,函数就是普通的对象,有一个类型叫“函数类型”。你可以把这东西扔进列表,传进另一个函数,甚至从函数里返回。没有特殊待遇,没有权限限制。Python解释器压根不区分“这段代码是函数”还是“那个值是整数”,它们都按照对象的规则在内存里躺着。
类型系统这块我花了大量时间。Python没有真正的编译时类型检查,解释器在运行时才去检查操作是否合法。比如你写“1 + 'a'”,解释器走到这一行,找到1这个整数对象,发现它没有和字符串相加的规则,然后才抛异常。不是语言不检查,是检查的时机放在了最后一刻,在你真的执行那一行代码之前,鬼知道会发生什么。
写循环和条件判断时,我发现解释器的工作方式特别笨但特别可靠。每次循环开始,解释器都重新计算条件表达式,拿到一个布尔对象。如果是真,执行一遍循环体,执行完再跳回去重新计算。从来不缓存结果,每次都是全新计算。这就导致你在运行时可以修改循环条件内部的变量,甚至替换掉条件里引用的整个函数,解释器都照单全收。
异常处理机制让我重新认识了Python的“动态”。try块里的代码正常执行,一旦出问题,解释器马上停止当前上下文,一层层往上找能处理的except。每层都用自己的环境。这个过程中,你可以捕获异常,也可以在except里改变全局变量来影响后续执行,解释器只管按你要求去做。没有静态分析帮你挡住错误,也没有编译时给你警告。
实现闭包的时候,我彻底明白了Python的作用域是怎么回事。函数定义的时候,解释器不光保存函数代码,还保存当时的环境。这个环境是一个字典,叫“闭包细胞”。等你真正调用这个函数,解释器把储存的环境翻出来,把里面的变量值和当前执行串联起来,形成一个完整的执行环境。环境不是一成不变的,你在函数内部改外部的变量,真的会改到外面。
写import实现那段,我理解了模块加载其实是执行代码。import模块的意思就是把那个文件的代码从头到尾执行一遍,把执行过程中产生的所有变量都存到一个字典里,这个字典就是模块的命名空间。所以模块里的全局变量可以随便改,你改完下次引用就变了,因为跑的还是同一份对象,没有编译时的拷贝。
解释器里最让我感慨的是垃圾回收。Python用引用计数来管内存,每当你创建一个对象,给它贴个标签,引用计数就+1。标签撕下来,引用计数就-1。计数到0,对象就被回收。这个过程完全自动,不需要你操心对象什么时候销毁,也没有像C++那样显式的析构时机。这种设计让程序员不用管内存布局,只管写逻辑。
写完整个解释器,我坐下来跑自己解释器执行Python代码,感觉非常奇特。屏幕上一行一行跑出来的结果,背后是词法分析、语法分析、字节码生成、运行时环境管理、对象查找、异常处理这一大串东西在运转。你写的那几行代码,解释器在背后做了一堆你根本看不到的工作。但这个过程中,所有决定都是在程序运行那一瞬间做的。
动态语言之所以动态,不是因为有很多语法糖,而是因为解释器把几乎所有的决策都推迟到了最后一刻。变量类型最后一刻才检查,函数调用最后一刻才查找,属性访问最后一刻才解析。每次执行,都是一次全新的现场决策。这就是Python没有编译时束缚的代价和好处。代价是慢一点,好处是你可以在运行时几乎改任何东西。
写这个解释器让我再也不把Python的“动态”当成理所当然。每写一个变量,我都知道它只是一个标签。每调一个函数,我都知道解释器在拼命查找。跑代码出错时,我知道解释器已经尽力做到了它能做的一切。知道这些东西后,写代码的心态变踏实了,不再困惑那些看似奇怪的行为,因为我知道解释器底下是怎么跑的。