由Python历史「解密」Python底层逻辑

由Python历史「解密」Python底层逻辑

本文配图来自美剧《我们窃取秘密:维基解密的故事》。

一次纯粹的hacking

Python的作者,Guido von Rossum,荷兰人。1982年,Guido从阿姆斯特丹大学获得了数学和计算机硕士学位。尽管,他算得上是一位数学家,但他更加享受计算机带来的乐趣,热衷于做任何和编程相关的活儿。

80年代,掀起了个人电脑浪潮,但受限于个人电脑配置低,所有的编译器的核心是做优化,以便让程序能够运行。在那个时代,程序员恨不得用手榨取计算机每一寸的能力。有人甚至认为C语言的指针是在浪费内存,至于动态类型,内存自动管理,面向对象…… 别想了,那会让你的电脑陷入瘫痪。

而这种编程方式让Guido感到苦恼。Guido知道如何用C语言写出一个功能,但整个编写过程需要耗费大量的时间。

不过,他还有另一个选择shell。shell可以像胶水一样,将UNIX下的许多功能连接在一起。UNIX的管理员们常常用shell去写一些简单的脚本,以进行一些系统维护的工作,比如定期备份、文件系统管理等等。然而,shell的本质是调用命令,并不能全面的调动计算机的功能。

Guido希望有一种语言,这种语言能够像C语言那样,能够全面调用计算机的功能接口,又可以像shell那样轻松的编程。

**ABC语言让Guido看到希望。**ABC是由荷兰的数学和计算机研究所开发的,Guido在该研究所工作,并参与到ABC语言的开发。ABC 语言是一个致力于为初学者设计编程环境的长达 10 年的研究项目,与当时的大部分语言不同,ABC语言的目标是“让用户感觉更好”。

比如下面是一段来自Wikipedia的ABC程序,这个程序用于统计文本中出现的词的总数: HOW TO RETURN words document: PUT {} IN collection FOR line IN document: FOR word IN split line: IF word not.in collection: INSERT word IN collection RETURN collection

HOW TO用于定义一个函数。一个Python程序员应该很容易理解这段程序。ABC语言使用冒号和缩进来表示程序块。行 尾没有分号。for和if结构中也没有括号() 。赋值采用的是PUT,而不是更常见的等号。这些改动让ABC程序读起来像一段文字。

尽管ABC已经具备了良好的可读性和易用性,但最终却也没能流行起来。原因在于:

硬件上的困难:ABC语言编译器需要比较高配置的电脑才能运行,而当时电脑使用者,更多考虑程序效率,而非语言难度;一个语言设计的致命问题:其可拓展性较差,如果想在ABC语言中增加功能,比如对图形化的支持,就必须改动很多地方。不能直接进行IO:ABC语言不能直接操作文件系统。尽管你可以通过诸如文本流的方式导入数据,但ABC无法直接读写文件。

输入输出的困难对于计算机语言来说是致命的。你能想像一个打不开车门的跑车么?

ABC的前车之鉴,给Guido带来启示。

1989年,为了打发圣诞节假期,Guido开始写Python语言的编译器。Python这个名字,来自Guido所挚爱的电视剧Monty Python's Flying Circus。他希望这个新的叫做Python的语言,能符合他的理想创造一种C和shell之间,功能全面,易学易用,可拓展的语言。Guido作为一个语言设计爱好者,已经有过设计语言的尝试。这一次,也不过是一次纯粹的hacking行为。

Python解释器的诞生

1991 年,第一个 Python 解释器诞生,它是用 C 语言实现的,并能够调用 C 语言的库文件。从一出生,Python已经具有了:类,函数,异常处理,包含表和词典在内的核心数据类型,以及模块为基础的拓展系统。

这里需要牵扯一个“编译器”的概念,其主要作用是便于人编写,阅读,维护的高级计算机语言翻译为计算机能识别,运行的低级机器语言的程序。

编译器翻译语言方式有2种:编译、解释。

①编译型语言:需通过编译器(compiler)将源代码编译成机器码,之后才能执行的语言。

一般需经过编译(compile)、链接(linker)这两个步骤。编译是把源代码编译成机器码,链接是把各个模块的机器码和依赖库串连起来生成可执行文件。

②解释型语言:解释性语言的程序不需要编译,相比编译型语言省了道工序,解释性语言在运行程序的时候才逐行翻译。

Python是一种解释型语言,它的源代码不需要编译,可以直接从源代码运行程序。Python解释器将源代码转换为字节码,然后把编译好的字节码转发到Python虚拟机(Python Virtual Machine,PVM)中执行。

当我们执行Python代码的时候,在Python解释器用四个过程“拆解”我们的代码:

首先,当你把键入代码交给Python处理的时候会先进行词法分析,如果你键入关键字或者当输入关键字有误时,都会被词法分析所触发,不正确的代码将不会被执行。Python会进行语法分析,例如当"for i in test:"中,test后面的冒号如果被写为其他符号,代码依旧不会被执行。进入最关键的过程,在执行Python前,Python会生成.pyc文件,这个文件就是字节码。将编译好的字节码转发Python虚拟机中进行执行:由Python Virtual Machine(Python虚拟机)来执行这些编译好的字节码。什么是字节码(bytecode)?

简单的说它就是一个从源代码编译而来的中间文件(用于不同操作系统平台的解释器执行)。比如,a说日语,b说中文,沟通起来不畅通,请一个翻译,把a和b的语言都翻译成英语,这个英语就可以理解成bytecode, 一种中间语言。

bytecode的好处就是 加载快,而且可以跨平台, 同样一份bytecode,只要有操作系统平台上有相应的Python解释器,就可以执行,而不需要源代码。不同版本的Python编译的字节码是不兼容的,Python 2.6编译的bytecode拿到Python 2.7上去执行就不行了。

如何生成字节码?

Python解释器一般会自动把.py文件转换成bytecode,然后再执行它。当你第一次把.py文件当作module导入,或者对应的.py文件比.pyc文件的修改时间还要新时,Python解释器都会再从source code生成相应的新bytecode。这样当你下次再次运行程序时,就会直接从bytecode运行,从而节省便宜时间。

Ps:这里需要注意,有些情况bytecode并不会生成:

遇到目录写权限的问题时。(比如你编写代码和运行代码使用的具有不同权限的用户角色,Linux上很常见)运行一个script并不会被当成是import操作,所以可能也不会生成bytecode。(比如:你有个一个a.py的文件,其中在a.py里,你import了b.py,那么运行python a.py后,会生成b.pyc,而不会生成a.pyc)

☞拓展阅读:

(下文详细说明Python的工作机制和Python虚拟机内幕)

Python 字节码介绍

https://linux.cn/article-9816-1.html

.pyc文件是什么?

Python源码编译的结果就是

,每个作用域会编译出一个对应的代码对象,其中名为co_code的PyStringObject保存着代码对象的字节码。

一个Python源文件就是一个模块。每个模块顶层的代码对象通过marshal序列化之后就得到了.pyc文件。marshal以little-endian字节序来序列化数据。

那嵌套于顶层作用域里面的那些作用域,例如函数、类的定义,它们对应的代码对象在哪里?它们每一个都乖乖的躺在上一层作用域的代码对象的co_const(常量池)域里,所以其实顶层代码对象已经嵌套包含了底下其它作用域的代码对象。

☞拓展阅读:

(下文主要结合实例说明了.pyc文件结构)

Python 2.6.2的.pyc文件格式

https://www.iteye.com/topic/382423

如何对.pyc文件文件进行反编译?

python文件如果要发布的话,有时候还是难免想保护一下自己的源码,有些人就直接编译成了pyc文件,因为这样既可以保留跨平台的特性,又可以不能直接看到代码,也看到网上很多人说为了保护自己的代码可以编译成pyc文件。

用pyc文件可以保护python代码的想法其实是不正确的 ,pyc文件是可以很容易被反编译的,比如说比较著名的uncompyle6库(https://github.com/rocky/python-uncompyle6),用来反编译文件最爽不过了,几乎支持python全版本的pyc文件的反编译。

为什么要做代码分析?

一般来说,代码分析重要性的判断比较主观,不同的人有不同的认识。Python是用C来实现的,所以对于Python的性能或代码质量的评估可以通过 dis模块 获取到对应的字节码指令来进行评估。

一般来说一个Python语句会对应若干字节码指令,Python的字节码是一种类似汇编指令的中间语言,但是一个字节码指令并不是对应一个机器指令(二进制指令),而是对应一段C代码,而不同的指令的性能不同,所以不能单独通过指令数量来判断代码的性能,而是要通过 查看调用比较频繁的指令的代码 来确认一段程序的性能。

一个Python的程序会有若干代码块组成,例如一个Python文件会是一个代码块,一个类,一个函数都是一个代码块,一个代码块会对应一个运行的上下文环境以及一系列的字节码指令。

dis模块主要是用来分析字节码的一个内置模块。dis 模块的文档 可以让你遍历它的内容,并且提供一个字节码指令能够做什么和有什么样的参数的完整清单。

☞拓展阅读:

(下文主要说明了dis模块的使用)

Python反编译之字节码

https://mp.weixin.qq.com/s/syXR549apPDn34qIYc7Rdg

Python开发者如何写出高质量的代码?

要不这样吧,如果编程语言里有个地方你弄不明白,而正好又有个人用了这个功能,那就开枪把他打死。这比学习新特性要容易些,然后过不了多久,那些活下来的程序员就会开始用 0.9.6 版的 Python,而且他们只需要使用这个版本中易于理解的那一小部分就好了(眨眼)。

—— Tim Peters

传奇的核心开发者,“Python 之禅”作者

给 comp.lang.python Usenet 小组的留言,2002 年 12 月 23 日,“Acrimony in c.l.p”。

Python 官方教程的开头是这样写的:“Python 是一门既容易上手又强大的编程语言。”这句话本身并无大碍,但需要注意的是,正因为它既好学又好用,所以很多 Python 程序员只用到了其强大功能的一小部分。

只需要几个小时,经验丰富的程序员就能学会用 Python 写出实用的程序。然而随着这最初高产的几个小时变成数周甚至数月,在那些先入为主的编程语言的影响下,开发者们会慢慢地写出带着“口音”的 Python 代码。与此同时,你会发现,自己在持续陷入基本的熟练程度,却无从提升自己的编程技能。

其实,掌握Python编程不仅要掌握该语言的理论方面, 理解和采用社区使用的惯例和最佳实践也同样重要。 而且这些技巧可以很好的帮助你避免重复劳动,写出简洁、流畅、易读、易维护的代码。

☞拓展资料:

《流畅的Python》

http://www.ituring.com.cn/book/1564

PSF研究员、知名PyCon演讲者心血之作Python核心开发人员担纲技术审校全面深入,对Python语言关键特性剖析到位大量详尽代码示例,并附有主题相关高质量参考文献和视频链接兼顾Python 3和Python 2

本书致力于帮助Python开发人员挖掘这门语言及相关程序库的优秀特性,写出简洁、流畅、易读、易维护的代码。特别是深入探讨了针对数据库处理时生成器的具体应用、特性描述符(ORM的关键),以及Python式的对象:协议与接口、抽象基类及多重继承。

《深入理解Python特性》

http://www.ituring.com.cn/book/2582

上市两个月获 Amazon 百余条五星评价影响全球1 000 000以上程序员的PythonistaCafe社区创始人Dan Bader手把手带你提升Python实践技能与《流畅的Python》互为补充,Python进阶必备

本书致力于帮助Python开发人员挖掘这门语言及相关程序库的优秀特性,避免重复劳动,同时写出简洁、流畅、易读、易维护的代码。用好Python需要了解的最重要的特性、Python 2过渡到Python 3需要掌握的现代模式、有其他编程语言背景想快速上手Python的程序员需要特别注意的问题,等等,本书都可以解决。

参考资料:

https://blog.csdn.net/miaodalengshui/article/details/77451262

https://mp.weixin.qq.com/s/qqHQYyqFsCYVIYjmWOF4jQ

https://linux.cn/article-9816-1.html

https://blog.csdn.net/helloxiaozhe/article/details/78104975

https://www.cnblogs.com/mlgjb/p/7899534.html