社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

起底 Python 的底层逻辑

编程派 • 4 年前 • 312 次点击  


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


文 | 图灵社群

推荐 | 编程派公众号(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上很常见)

  • 运行一个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源码编译的结果就是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


回复下方「关键词」,获取优质资源


回复关键词「 pybook03」,立即获取主页君与小伙伴一起翻译的《Think Python 2e》电子版

回复关键词「pybooks02」,立即获取 O'Reilly 出版社推出的免费 Python 相关电子书合集

回复关键词「书单02」,立即获取主页君整理的 10 本 Python 入门书的电子版



豆瓣 9.1 分,中文版销量 30 多万,零基础也能用这本书学会 Python

你想要的 IT 电子资源,这里可能都有


Python 或将超越 C、Java,成为最受欢迎的语言

Python 容器使用的 5 个技巧和 2 个误区

如何写出优雅的 Python 函数?

题图:pexels,CC0 授权。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/36038
 
312 次点击