Python的起源和认识

Python的创始人是一个荷兰人,Guido van Rossum (吉多·范罗苏姆),89年圣诞节期间,在阿姆斯特丹,guido为了解决在分布式系统Amoeba项目中重复编写C语言工具代码的烦心问题,决心开发一个新的脚本解释程序,把写过的C语言小程序粘贴在一起构成一个新的工具。从89年12月开始编写,91年2月开源发布。91年10月第一版Unicode标准才发布,这给Python埋下了Python2和Python3版本分裂的隐患。

作者其实并不喜欢语言和大蟒蛇关联上,选中大蟒蛇作为编程语言的名字,取自英文20世纪70年代首播的电视喜剧《蒙提.派森的飞行马戏团》(Monty Python’s Flying Circus),那是他喜欢的一个马戏团的名字,也符合编程语言名称来源于名人的名字的习惯,如pascal,ada,eiffel

发明Python的背景

Guido1982年从阿姆斯特丹大学获得数学和计算机硕士双学位,接触过当时的多种流行语言,Pascal,C,Fortran。大学毕业加入CWI(荷兰教育部和其他研究基金资助的研究中心,国家数学和计算机科学研究中心),在CWI中,他参与了Amoeba项目,为两所机构共同参与开发的基于微内核的分布式系统。

在这个项目中,Guido发现用C语言开发系统管理程序所花费的时间太长了,且Amoeba是一个全新的底层操作系统实现,导致了和原有的Bourne shell不兼容,在Bourne shell无法执行Amoeba项目之中的一些程序。这些原因让Guido意识到它需要一个能弥合C和shell间鸿沟,更高抽象级别,更加简单的编程语言去完成工作任务。

为什么选择造轮子发明python,而不是移植其它编程语言?那是因为当初并没有其它编程语言可以完成这些任务。

ABC语言

ABC语言是在CWI主导研发的一种交互式,结构化高级语言,旨在替代basic,pascal等语言,用于教学机原型软件设计。Python的创始人在80年代曾在ABC系统开发中工作了数年。

特点

ABC语言的设计者称其代码量通常为Pascal或C语言的1/4,且具备更强的可读性,其主要特点如下:

  1. 只有五种基本数据类型
  2. 不需要变量声明(动态变量)
  3. 明确支持自上而下的编程
  4. 语句嵌套由缩进来表示
  5. 精度,列表,字符串均无限制

未能流行的原因

ABC是由Guido参加设计的一种教学语言,就Guido本人看来,ABC这种语言非常优美强大,是专门为非专业程序员设计的。但ABC语言并不成功,一方面是Guido认为ABC不开放,故Guido决心在Python中避免这一错误,且还想在其中实现在ABC中闪现过但未曾实现的东西。就Guido本人看来,ABC失败的原因是

  1. 高级语言为时过早
  2. 平台迁移能力弱,难以添加新功能
  3. 仅仅专注于编程初学者,没有把有经验的编程人员纳入其中

其通过python解决了其一些问题,让拓展模块的编写非常容易,且可在多平台进行运行。可以说,Python是从ABC发展起来的,主要受到了Modula-3(另一种相当优美且强大的语言,为小型团队所设计的)的影响,且结合了Unix shell和C的习惯。

Python的特色

小的核心语言+大的标准库

Python并没有把所有功能特性都嵌入到它的核心代码中。Python 的设计是高度可扩展。由于看到ABC语言的失败,Guido van Rossum认为“小的核心语言”+“大的标准库”才是他需要的

优雅-明确-简单的哲学

在语法层面,Python拒绝了强大并复杂的语法(比如Perl的语法),而倾向于一种更简单、更简洁的语法。Python 的哲学拒绝 Perl“语言设计的方法不止一种”,而倾向于”应该有一种——最好只有一种——显而易见的方法”。

像Perl让人诟病的一点,就是它过于灵活,源代码常常难以阅读,甚至被称作write-only语言。当代码量达到成百上千行的规模,想在Perl中进行维护,就需要大量的纪律约束。而在Python中就没有这个烦恼,显得尤为优美。

最好是只有一种方法来做一件事”,也因此它和拥有明显个人风格的其他语言很不一样。如果面临多种选择,Python开发者一般会拒绝花俏的语法,而选择明确没有或者很少有歧义的语法。这些准则被称为“Python格言”:

1
2
3
4
5
6
7
8
9
10
11
优美胜于丑陋(Python 以编写优美的代码为目标)
明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)
简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)
复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)
扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)
间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)
可读性很重要(优美的代码是可读的)
不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)
当存在多种可能,而是尽量找一种,最好是唯一一种明显的解决方案
如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)
命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)

更甚者,Python 的开发人员努力避免过早优化,并拒绝对 CPython 的非关键部分进行补丁,这些补丁将以成本为代价,提供边际增长速度。

Python与C的不同

Python大量使用了C语言里的设计,但它是一门动态语言。

  • 不像C语言一样需预先声明变量和指定变量类型,运行时也可以改变变量类型,也内置了大量的数据类型
  • 为了程序的模块化,引入了包,模块,类,方法和函数
  • 所有对象都是一等对象,这意味着函数,类,方法,模块和所有其它对象都可以在运行时自由传递,并放入在各种数据结构中去

Python在刚开始时是一个个人独自维护的项目,没任何官方预算和支持,要快速有结果,所以有偷懒的设计哲学,性能也不是他的重要追求目标。对Python的发展也没什么预期,觉得它就跟当时做过的许多失败项目一样,没什么特别突出。

作者对Python的目标

  1. 一门简单直观的语言并与主要竞争者一样强大
  2. 开源,以便任何人都可以为它做贡献
  3. 代码像纯英语那样容易理解
  4. 适用于短期开发的日常任务

变态缩进

Python使用功能缩进区分编程语句来源于ABC编程语言,虽然不是首创者,但ABC编程语言的作者确实发明了使用冒号将导入语句(lead-in clause)与缩进块 (intented block)分开的方式。

代码缩进并不是作者要如此[5]是因为30年前的代码编辑器都不能很好地对代码进行缩进排版。在同事的启发下作者就鼓励程序员自己来对代码进行正确的排版(作者也承认严格要求代码缩进确实有些夸张,改用花括号,也不是不可以)。确保从视觉上对代码的理解与编译器对代码的解析是一致的,这还挺重要。一些代码安全漏洞事故,就是由于代码中语句与程序员实际设想的if-else语法逻辑没有匹配引起的。

青出于蓝

作为ABC语言的一种继承及目标客服其失败,从早期的版本开始,Python就提供了一系列内置功能,被称为“Battery Included(内置电池)”,得益于Python开放易拓展的属性,最终拥有极其强大的标准库,是整个社区的贡献,让它从众多编程语言中容易脱颖而出。

Python最开始的发展其实非常缓慢,也非常小众,因为在计算机资源非常有限的年代,大家都倾向于最大化榨取计算机资源并提升运算效率,而Python显然不是为此而生。

得到大家的青睐,主要是在90年代末期,很多科学家开始进行科学计算时,就像Python作者一样用Python来作为胶水语言,调用原来由fortran或C++编写的代码。对于这些科学家来说,Python是非常顺手的工具

  • 对初学者非常友好。Python 的语法简单直观,几乎就像是用英语写的,学习曲线平缓

  • 在数据科学、机器学习和人工智能领域提供了众多包,这些包或者是被科学家用于数据分析建模,或者被广泛部署在工业界的生产系统,经受了学术圈和工业界的检验。

门槛低能力强

丰富的生态系统

2018年guido在和Python社区斗争多年以后,选择退出独裁模式,2019年建立了委员会来管理python的走向,此后python就发展的越来越好

作者的程序哲学

底层概念与复杂体系

我们不能把计算机当作是一个魔法盒子,而要去了解它是如何运作的。这样虽然不一定能成为最厉害的程序员,但会比那些完全不懂基本概念的程序员更了解软件和计算机的弱点和约束,从而更好地运用各种软件工具,避免愚蠢的错误。

软件实际上是有多层结构组成的,这有点像生物的进化,一方面DNA编码方式已经有10亿年没有发生变化了,就像计算机体系中的比特,字节,指针和内存一样(在软件当中其实最终都是指针,内存和计算,了解这些最底层概念非常重要),水藻化石中发现的远古细胞跟现代的细胞,没有太大的形态变化,不同类的多细胞可组成不同的器官,各种不同的器官最终又组成了人类,人类自己又构成了人类社会。软件也像这样,软件领域里最重要的事情就是通过网络把计算机都联系在了一起,从而能基于简单的细小结构构建出多层次的大型复杂体系,在简单中蕴含着高层级的结构,这里面具有超乎想象的灵活性和可能性

**Guido:**我认为对编程来说真正酷的事情,是将新软件构建于先前软件的基础之上,现在的程序员编写代码可以借鉴前人的代码。

开放与合作

Guido自认为自己不是全能型的程序员,所以他只负责制订框架。如果问题太复杂,他会选择绕过去,也就是cut the corner。这些问题最终由社区中的其他人解决。社区中的人才是异常丰富的,就连创建网站,筹集基金这样与开发稍远的事情,也有人乐意于处理。如今的项目开发越来越复杂,越来越庞大,合作以及开放的心态成为项目最终成功的关键

最爱Pascal的优雅

其最早是1974与1975年在阿姆斯特丹开始学习编程。学的第一门语言是 ALGOL 60,还学过一些别的语言,但他最爱的是 Pascal,它是一门非常优雅的语言[5]。在这个过程中,其逐渐了解一门编程语言应有的特性,以及它们在处理具体问题时各自的特点。例如,在 ALGOL 60 里是没有字符串类型的,如果想定义一个标识符就必须用一种魔法一般的方式来处理字符串,这种魔法在不同的输入硬件上的施展方式还不一样——要知道当时是通过穿孔卡片来输入代码的,每一种卡片机都是不同的。而 Pascal 在处理字符串上也很有一套,他认为 Pascal 非常优雅,能帮程序员高效率编程。

目标感与当下主义者

有些人非常有驱动力,目标明确,访谈中Python作者就提到盖茨及一个摇滚乐手,做事非常有计划,有些人则没有雄心勃勃的创业理想或商业目标,如Unix创造者之一也是贝尔实验室花了一番功夫才说服加入,Python之父在年轻时也不知道自己将要干什么,更关注当下,而不是未来主义者。

教堂与集市

微软就像大教堂,开源则像集市?作者在微软工作过一段时间,他认为其实大公司不止有一个风格,内部实际上还有众多不同的部落,彼此有不同的目标及做事方式,有的人不怎么关心软件只关心钱,有的相反,有的不关心使用软件的人,还有人关心使用软件的人及软件如何帮助这些人,你可以找到属于自己的部落。

创造性还无银弹

人的大脑可能才是软件开发效率的天花板,而不是屏幕上能显示多少行代码、或者编译器能跑得多快。新出现的 GitHub Copilot,一用就喜欢上了它,它太棒了,但其也不认为这是提升代码编写效率的银弹——因为它自动生成了 10 行代码,你还得花时间去确认这 10 行代码所做的事情确实就是你想要的。

Python作者想起来以前听过一个笑话,在MS-DOS年代,有个外行项目经理领导一个项目,要交付一百来个用户界面的应用,期限是6个星期,到了第三个星期,他们还没生成一个用户界面,这个项目经理非常焦虑,对他来说团队生产效率就是0!然而程序员却说,他们正在构建一个工具,这个工具会在最后两天自动生成这100多个用户界面。这大概是所有程序员都梦寐以求的事情,事实上他当初创建Python时也是这么想的,他通过Python把原来C语言写的小程序都粘连在一起,很快完成了任务。这说明他非常在意创造性与效率提升

移动设备上Python不太成功

移动操作系统,套接字和信号行为和Unix有所不同,且禁止许多syscall,子进程使用受限。

Guido说:CPython(官方的Python解释器)已有30年的历史了,它最初是为了工作站,台式机和服务器环境诞生的,但那些没法让CPython能在移动设备上运行的人们发现,Python消耗了太多的资源。

他又补充说到:与期待中在移动操作系统运行的情况相比,Python又大又慢,运行Python编写的应用会迅速消耗电池电量和内存。

因为Python应用需要自己的运行环境副本,意味着Python需缩小规模以顺利运行在移动设备,也有人建议过创建一个简化内核版本的Python以解决该问题。

当时BeeWare联合创始人Russell Keith-Magee有一种Python的”存在恐慌“:当iPad成为主流,笔记本电脑不过是细分设备时,Python会发生什么?

但AI世代的降临又让它飞上了天。

Python为什么这么慢?

Python速度真的是相当慢。最简单的整数循环耗时是未优化C程序的近30倍

  • Python编译器还真是很少进行优化,比如明显的对i反复LOAD_FAST和STORE_FAST,可以改为一直在栈上操作。然而在Python里尝试做这种优化,和把基于栈的指令集改成基于寄存器一样,是没有关注到重点。

  • 如果是基于寄存器的指令集,那么最好情况下所有LOAD_FAST, LOAD_CONST, STORE_FAST指令可以去掉,指令数由9条减少到4条,看起来是很大的进步。然而“数指令数估计时间”这种思路并不适用于Python VM。

  • 所有指令都没有类型,或者说都是“抽象”操作。这与JVM形成了鲜明对比:JVM内置了多种基本类型,并且指令都指定了类型,比如iload,iadd。为了效率,Java甚至把这些类型在语言层面暴露出来,哪怕基本类型和对象类型语义不一致给初学者带来了困惑。

如果把基于栈的虚拟机改为基于寄存器,确定能省掉的是TARGET和FAST_DISPATCH这种纯解释开销和POP_TOP/ROT_THREE这种栈操作指令,但是又要增加寄存器间复制的指令。核心并不是基于寄存器还是基于栈,也不是只要指令少就快。

  • 前面说了,Python字节码都是抽象的,所以INPLACE_ADD并不知道操作数能不能作inplace(不产生新对象)的加法,所以binary_iop1要去判断操作数是否支持。恰好int因为是immutable type,不支持inplace操作(nb_inplace_add=NULL),那么就要用普通的加法(nb_add)。

  • 由于不能inplace,每次n+=1要new一个新的int对象,还要free掉原来的对象。

  • 另外还要处理一大堆情况,比如加法既可以由左操作数定义也可以由右操作数定义;如果两边都定义了,而一个是另一个的子类,还要优先用子类的方法。

  • 和前面一样,最终实际干活的函数是由对象类型动态决定的。在我们的例子里对象是int,所以调用了long_add。由于int支持任意精度整数,long_add实现很长;

  • “实际干活”的指令最后会用DISPATCH而不是FAST_DISPATCH,区别是前者会检查是否需要释放GIL给别的线程。这里需要原子地读一个标志,所以比FAST_DISPATCH慢不少。LOAD/STORE等指令用FAST_DISPATCH的原因是 1) 它们总是围绕着实际干活的指令,没必要这么频繁地检查 2) 实际干活的指令由于会调用用户代码,执行时间可以任意长,所以如果每几条指令才检查一次标志,线程切换延迟可能会很大;而LOAD等指令执行时间比较短,不检查标志也不会给线程切换带来很大延迟。

所以Python为什么慢?一个整数加法,用C就是一条机器指令,Lua大概十几行C代码,而Python需要经过近百行C代码。这些代码都干啥了?

  1. Lua VM的指令集虽然也是抽象的,但对应的数据类型是确定的几种,因此不像Python对所有指令都要dynamic dispatch。
  2. Python的加法语义设计得非常灵活,可以定义在左操作数/右操作数/两边同时定义… 进一步增加了dynamic dispatch的逻辑。
  3. Python的int没有使用机器native整型,而是自己定义的任意精度整型格式。因此即使数据范围可以被native整型支持,整数运算逻辑仍然较复杂。

实际上不只是整数运算,作为面向对象语言最核心的“调用方法”和“访问对象自身属性”两个操作,Python也是非常慢。

性能好的语言

总的来说,Python性能为什么这么差?性能好的语言大体分两类:

  1. 被设计成高性能。如C,Java。这类语言在设计之初就把性能放在优先位置,愿意为之作各种牺牲,比如前面提到的,Java作为面向对象语言而为基本类型搞特殊规则;而后面加新功能时,往往首先问的也是这是否会使现有程序变慢。
  2. 设计时不重视性能,后期被强烈的性能需求和砸钱搞成高性能。如Javascript。

而对Python设计者来说,有太多因素排在性能之前:易学,灵活性(比如加法的各种定义方法),概念简单(比如把int/long合并成一个任意精度整数类型)。即使有人愿意砸钱,设计者也不见得配合你,比如提出各种与性能优化相悖的向后兼容要求…

Python3对Python2的抛弃

为了修复缺陷,更好地适应新环境,让Python更有生命力,Python社区做了一个非常有决断的动作,推出Python 3,不向后兼容。Python 3 开发的重点是清理代码库并删除冗余,清晰地表明只能用一种方式来执行给定的任务。

  • 语法差异:Python2在语法上更倾向于简单明了,但某些设计可能会引起混淆,Python3的语法更加一致和清晰
  • 标准库改进:3对异步IO的支持,更好的日期和时间库等
  • 性能改进:性能上3开展了很多优化,某些功能执行效率更高
  • 字符串和字节处理:Python2字符串类型不区分Unicode和字节字符串,在处理非ASCII字符文本时可能造成混淆,Python3所有字符串都是Unicode,字符串和字节串有明确区分
  • 改进的编程功能:函数注解,类型提示,先进的异步编程等,更易读更易维护

引用