原标题:起底 Python 的底层逻辑
本文配图来自美剧《我们窃取秘密:维基解密的故事》。
文 | 图灵社区
推荐 | 编程派公众号(ID:codingpy)
01
一次纯粹的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行为。
02
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虚拟机)来执行这些编译好的字节码。
03
什么是字节码(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上很常见)
运行一个并不会被当成是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源码编译的结果就是PyCodeObject,每个作用域会编译出一个对应的代码对象,其中名为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
04
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编程不仅要掌握该语言的理论方面,理解和采用社区使用的惯例和最佳实践也同样重要。而且这些技巧可以很好的帮助你避免重复劳动,写出简洁、流畅、易读、易维护的代码。
参考资料:
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返回搜狐,查看更多
责任编辑: