Python的做者,Guido von Rossum,荷兰人。1982年,Guido从阿姆斯特丹大学得到了数学和计算机硕士学位。尽管,他算得上是一位数学家,但他更加享受计算机带来的乐趣,热衷于作任何和编程相关的活儿。html
80年代,掀起了我的电脑浪潮,但受限于我的电脑配置低,全部的编译器的核心是作优化,以便让程序可以运行。在那个时代,程序员巴不得用手榨取计算机每一寸的能力。有人甚至认为C语言的指针是在浪费内存,至于动态类型,内存自动管理,面向对象…… 别想了,那会让你的电脑陷入瘫痪。python
而这种编程方式让Guido感到苦恼。Guido知道如何用C语言写出一个功能,但整个编写过程须要耗费大量的时间。linux
不过,他还有另外一个选择shell。shell能够像胶水同样,将UNIX下的许多功能链接在一块儿。UNIX的管理员们经常用shell去写一些简单的脚本,以进行一些系统维护的工做,好比按期备份、文件系统管理等等。然而,shell的本质是调用命令,并不能全面的调动计算机的功能。git
Guido但愿有一种语言,这种语言可以像C语言那样,可以全面调用计算机的功能接口,又能够像shell那样轻松的编程。程序员
**ABC语言让Guido看到但愿。**ABC是由荷兰的数学和计算机研究所开发的,Guido在该研究所工做,并参与到ABC语言的开发。ABC 语言是一个致力于为初学者设计编程环境的长达 10 年的研究项目,与当时的大部分语言不一样,ABC语言的目标是“让用户感受更好”。github
好比下面是一段来自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 collectionshell
HOW TO用于定义一个函数。一个Python程序员应该很容易理解这段程序。ABC语言使用冒号和缩进来表示程序块。行 尾没有分号。for和if结构中也没有括号() 。赋值采用的是PUT,而不是更常见的等号。这些改动让ABC程序读起来像一段文字。数据库
尽管ABC已经具有了良好的可读性和易用性,但最终却也没能流行起来。缘由在于:编程
输入输出的困难对于计算机语言来讲是致命的。你能想像一个打不开车门的跑车么?数据结构
ABC的前车可鉴,给Guido带来启示。
1989年,为了打发圣诞节假期,Guido开始写Python语言的编译器。Python这个名字,来自Guido所挚爱的电视剧Monty Python's Flying Circus。他但愿这个新的叫作Python的语言,能符合他的理想:创造一种C和shell之间,功能全面,易学易用,可拓展的语言。Guido做为一个语言设计爱好者,已经有过设计语言的尝试。这一次,也不过是一次纯粹的hacking行为。
1991 年,第一个 Python 解释器诞生,它是用 C 语言实现的,并可以调用 C 语言的库文件。从一出生,Python已经具备了:类,函数,异常处理,包含表和词典在内的核心数据类型,以及模块为基础的拓展系统。
这里须要牵扯一个“编译器”的概念,其主要做用是便于人编写,阅读,维护的高级计算机语言翻译为计算机能识别,运行的低级机器语言的程序。
编译器翻译语言方式有2种:编译、解释。
①编译型语言:需经过编译器(compiler)将源代码编译成机器码,以后才能执行的语言。
通常需通过编译(compile)、连接(linker)这两个步骤。编译是把源代码编译成机器码,连接是把各个模块的机器码和依赖库串连起来生成可执行文件。
②解释型语言:解释性语言的程序不须要编译,相比编译型语言省了道工序,解释性语言在运行程序的时候才逐行翻译。
Python是一种解释型语言,它的源代码不须要编译,能够直接从源代码运行程序。Python解释器将源代码转换为字节码,而后把编译好的字节码转发到Python虚拟机(Python Virtual Machine,PVM)中执行。
当咱们执行Python代码的时候,在Python解释器用四个过程“拆解”咱们的代码:
简单的说它就是一个从源代码编译而来的中间文件(用于不一样操做系统平台的解释器执行)。好比,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并不会生成:
☞拓展阅读:
(下文详细说明Python的工做机制和Python虚拟机内幕)
Python源码编译的结果就是 PyCodeObject
,每一个做用域会编译出一个对应的代码对象,其中名为co_code的PyStringObject保存着代码对象的字节码。
一个Python源文件就是一个模块。每一个模块顶层的代码对象经过marshal序列化以后就获得了.pyc文件。marshal以little-endian字节序来序列化数据。
那嵌套于顶层做用域里面的那些做用域,例如函数、类的定义,它们对应的代码对象在哪里?它们每个都乖乖的躺在上一层做用域的代码对象的co_const(常量池)域里,因此其实顶层代码对象已经嵌套包含了底下其它做用域的代码对象。
☞拓展阅读:
(下文主要结合实例说明了.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模块的使用)
要不这样吧,若是编程语言里有个地方你弄不明白,而正好又有我的用了这个功能,那就开枪把他打死。这比学习新特性要容易些,而后过不了多久,那些活下来的程序员就会开始用 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开发人员挖掘这门语言及相关程序库的优秀特性,写出简洁、流畅、易读、易维护的代码。特别是深刻探讨了针对数据库处理时生成器的具体应用、特性描述符(ORM的关键),以及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
原文连接:https://linux.cn/article-9816-1.html
若是你曾经编写过 Python,或者只是使用过 Python,你或许常常会看到 Python 源代码文件——它们的名字以 .py
结尾。你可能还看到过其它类型的文件,好比以 .pyc
结尾的文件,或许你可能据说过它们就是 Python 的 “字节码bytecode” 文件。(在 Python 3 上这些可能不容易看到 —— 由于它们与你的 .py
文件不在同一个目录下,它们在一个叫 __pycache__
的子目录中)或者你也据说过,这是节省时间的一种方法,它能够避免每次运行 Python 时去从新解析源代码。
可是,除了 “噢,原来这就是 Python 字节码” 以外,你还知道这些文件能作什么吗?以及 Python 是如何使用它们的?
若是你不知道,那你走运了!今天我将带你了解 Python 的字节码是什么,Python 如何使用它去运行你的代码,以及知道它是如何帮助你的。
Python 常常被介绍为它是一个解释型语言 —— 其中一个缘由是在程序运行时,你的源代码被转换成 CPU 的原生指令 —— 但这样的见解只是部分正确。Python 与大多数解释型语言同样,确实是将源代码编译为一组虚拟机指令,而且 Python 解释器是针对相应的虚拟机实现的。这种中间格式被称为 “字节码”。
所以,这些 .pyc
文件是 Python 悄悄留下的,是为了让它们运行的 “更快”,或者是针对你的源代码的 “优化” 版本;它们是你的程序在 Python 虚拟机上运行的字节码指令。
咱们来看一个示例。这里是用 Python 写的经典程序 “Hello, World!”:
def hello()
print("Hello, World!")
下面是转换后的字节码(转换为人类可读的格式):
2 0 LOAD_GLOBAL 0 (print)
2 LOAD_CONST 1 ('Hello, World!')
4 CALL_FUNCTION 1
若是你输入那个 hello()
函数,而后使用 CPython 解释器去运行它,那么上述列出的内容就是 Python 所运行的。它看起来可能有点奇怪,所以,咱们来深刻了解一下它都作了些什么。
CPython 使用一个基于栈的虚拟机。也就是说,它彻底面向栈数据结构的(你能够 “推入” 一个东西到栈 “顶”,或者,从栈 “顶” 上 “弹出” 一个东西来)。
CPython 使用三种类型的栈:
try
/ except
块、以及 with
块,所有推入到块栈中,当你退出这些控制结构时,块栈被销毁。这将帮助 Python 了解任意给定时刻哪一个块是活动的,好比,一个 continue
或者 break
语句可能影响正确的块。大多数 Python 字节码指令操做的是当前调用栈帧的计算栈,虽然,还有一些指令能够作其它的事情(好比跳转到指定指令,或者操做块栈)。
为了更好地理解,假设咱们有一些调用函数的代码,好比这个:my_function(my_variable, 2)
。Python 将转换为一系列字节码指令:
LOAD_NAME
指令去查找函数对象 my_function
,而后将它推入到计算栈的顶部LOAD_NAME
指令去查找变量 my_variable
,而后将它推入到计算栈的顶部LOAD_CONST
指令去推入一个实整数值 2
到计算栈的顶部CALL_FUNCTION
指令这个 CALL_FUNCTION
指令将有 2 个参数,它表示那个 Python 须要从栈顶弹出两个位置参数;而后函数将在它上面进行调用,而且它也同时被弹出(对于函数涉及的关键字参数,它使用另外一个不一样的指令 —— CALL_FUNCTION_KW
,但使用的操做原则相似,以及第三个指令 —— CALL_FUNCTION_EX
,它适用于函数调用涉及到参数使用 *
或 **
操做符的状况)。一旦 Python 拥有了这些以后,它将在调用栈上分配一个新帧,填充到函数调用的本地变量上,而后,运行那个帧内的 my_function
字节码。运行完成后,这个帧将被调用栈销毁,而在最初的帧内,my_function
的返回值将被推入到计算栈的顶部。
若是你想玩转字节码,那么,Python 标准库中的 dis
模块将对你有很是大的帮助;dis
模块为 Python 字节码提供了一个 “反汇编”,它可让你更容易地获得一我的类可读的版本,以及查找各类字节码指令。dis
模块的文档 可让你遍历它的内容,而且提供一个字节码指令可以作什么和有什么样的参数的完整清单。
例如,获取上面的 hello()
函数的列表,能够在一个 Python 解析器中输入以下内容,而后运行它:
import dis
dis.dis(hello)
函数 dis.dis()
将反汇编一个函数、方法、类、模块、编译过的 Python 代码对象、或者字符串包含的源代码,以及显示出一我的类可读的版本。dis
模块中另外一个方便的功能是 distb()
。你能够给它传递一个 Python 追溯对象,或者在发生预期外状况时调用它,而后它将在发生预期外状况时反汇编调用栈上最顶端的函数,并显示它的字节码,以及插入一个指向到引起意外状况的指令的指针。
它也能够用于查看 Python 为每一个函数构建的编译后的代码对象,由于运行一个函数将会用到这些代码对象的属性。这里有一个查看 hello()
函数的示例:
>>> hello.__code__
<code object hello at 0x104e46930, file "<stdin>", line 1>
>>> hello.__code__.co_consts
(None, 'Hello, World!')
>>> hello.__code__.co_varnames
()
>>> hello.__code__.co_names
('print',)
代码对象在函数中能够以属性 __code__
来访问,而且携带了一些重要的属性:
co_consts
是存在于函数体内的任意实数的元组co_varnames
是函数体内使用的包含任意本地变量名字的元组co_names
是在函数体内引用的任意非本地名字的元组许多字节码指令 —— 尤为是那些推入到栈中的加载值,或者在变量和属性中的存储值 —— 在这些元组中的索引做为它们参数。
所以,如今咱们可以理解 hello()
函数中所列出的字节码:
LOAD_GLOBAL 0
:告诉 Python 经过 co_names
(它是 print
函数)的索引 0 上的名字去查找它指向的全局对象,而后将它推入到计算栈LOAD_CONST 1
:带入 co_consts
在索引 1 上的字面值,并将它推入(索引 0 上的字面值是 None
,它表示在 co_consts
中,由于 Python 函数调用有一个隐式的返回值 None
,若是没有显式的返回表达式,就返回这个隐式的值 )。CALL_FUNCTION 1
:告诉 Python 去调用一个函数;它须要从栈中弹出一个位置参数,而后,新的栈顶将被函数调用。“原始的” 字节码 —— 是非人类可读格式的字节 —— 也能够在代码对象上做为 co_code
属性可用。若是你有兴趣尝试手工反汇编一个函数时,你能够从它们的十进制字节值中,使用列出 dis.opname
的方式去查看字节码指令的名字。
如今,你已经了解的足够多了,你可能会想 “OK,我认为它很酷,可是知道这些有什么实际价值呢?”因为对它很好奇,咱们去了解它,可是除了好奇以外,Python 字节码在几个方面仍是很是有用的。
首先,理解 Python 的运行模型能够帮你更好地理解你的代码。人们都开玩笑说,C 是一种 “可移植汇编器”,你能够很好地猜想出一段 C 代码转换成什么样的机器指令。理解 Python 字节码以后,你在使用 Python 时也具有一样的能力 —— 若是你能预料到你的 Python 源代码将被转换成什么样的字节码,那么你能够知道如何更好地写和优化 Python 源代码。
第二,理解字节码能够帮你更好地回答有关 Python 的问题。好比,我常常看到一些 Python 新手困惑为何某些结构比其它结构运行的更快(好比,为何 {}
比 dict()
快)。知道如何去访问和阅读 Python 字节码将让你很容易回答这样的问题(尝试对比一下: dis.dis("{}")
与 dis.dis("dict()")
就会明白)。
最后,理解字节码和 Python 如何运行它,为 Python 程序员不常用的一种特定的编程方式提供了有用的视角:面向栈的编程。若是你之前历来没有使用过像 FORTH 或 Fator 这样的面向栈的编程语言,它们可能有些古老,可是,若是你不熟悉这种方法,学习有关 Python 字节码的知识,以及理解面向栈的编程模型是如何工做的,将有助你开拓你的编程视野。