Python的哲学

PEP20是一条很有意思的命令:

执行之后,你将会看到一首名为「Python之禅」的诗,原文及翻译如下:

The Zen of Python,by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one– and preferably only one –obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea — let’s do more of those!

Python之禅,作者Tim Peters

优美胜于丑陋
明了胜于晦涩
简单胜于复杂
复杂胜于杂乱
扁平胜于嵌套
间隔胜于紧凑
可读性很重要
特例不足以特殊到违背这些原则
不要忽视错误,除非程序需要这样做
面对模棱两可,拒绝猜测
解决问题最直接的方法应该有一种,最好只有一种
可能这种方法一开始不够直接,因为你不是范罗苏姆
做也许好过不做,但不想就做还不如不做
如果方案难以描述明白,那么一定是个糟糕的方案
如果容易描述,那么可能是个好方案
命名空间是一种绝妙的理念,多加利用

 

Python 3.5的新特性

Python 3.5.0在9月13号正式发布,与3.4相比,有哪些让人期待的新特性呢?(Note:本文首发在CSDN,禁止任何形式的转载)

  • PEP441,优化Python对zip压缩包的支持。从2.6版本开始,Python支持将文件或zip形式的压缩包作为脚本执行,对于有必要作为一个文件发布又复杂到要分模块管理的应用来说,这个功能很实用。但是这个功能却鲜有人知,原因之一是当时没有很好地宣传,只在What’s New中提到,没有写到文档中;原因之二是Windows没有为这种形式的文件(.zip)提供相应地Python扩展名(类似.py)。所以这一次的版本中重新发布了这个特性,并且,定义.pyz和.pyzw作为“Python的zip应用”和“Windows下Python Zip应用”的扩展名,并提供了一个简单的管理工具。
  • PEP448,解包操作更加方便。*作为可迭代的解包操作符,**作为字典的解包操作符。list,tuple等数据结构通过解包操作会得到分散的对象,例如,*range(4), 4结果得到(0, 1, 2, 3, 4)。
  • PEP461,%操作符支持对字节和字节数组的格式化。
  • PEP465,新增矩阵乘法运算符’@’。 a @ b表示矩阵a乘矩阵b,也支持a @= b。
  • PEP471,新增os.scandir(),能够快速遍历目录。
  • PEP475,提供系统调用中断时自动重试的支持。
  • PEP479,StopIteration异常改在生成器内部处理。现在如果生成器raise一个StopIteration,将会替换为RuntimeError。因为这个特性不能向前兼容,所以最初是以__future__的形式发布的。
  • PEP484,新增typing模块,类型注释的新标准。支持通过def greeting(name: str) -> str:这种形式声明函数,来注释输入和输出地数据类型。
  • PEP485,新增math.isclose(),来判断近似相等。
  • PEP486,优化Windows平台对virtualenv的支持。在Windows中Python如果安装了virtualenv或者多版本共存的话,需要配置解释器的路径来指定一个解释器执行,但是并不会区分虚拟环境还是系统环境,如果使用pip等命令行,影响的还是系统环境。本PEP使系统能感受到“虚拟环境的存在”,也就说如果没有特别指定解释器,Windows中运行Python命令的时候会影响活跃的virtualenv,如果没有,再调用默认的Python。
  • PEP488,不再使用.pyo文件名。扩展.pyc,如果添加优化参数,字节码文件同目录下生成.pyc将会使优化后的版本,文件中会保存优化级别。
  • PEP489,优化内置模块和扩展模块的加载。
  • PEP492,支持async/await语法的协程。优化了Python的异步,添加了awaitable object 、协程函数、异步迭代和异步上下文管理器等。

其他CPython的优化

  • 当LC_TYPE locale是POSIX locale(C locale)时,sys.stdin 和sys.stdout 用surrogateescape错误处理器,不再使用strict错误处理器。

标准库重要优化

安全优化

  • 标准库禁用SSLv3.
  • HTTP cookie,解析更加严格,以阻止潜在的注入攻击。

Windows优化

  • Windows新安装器,替换MSI.
  • Windows构建以及扩展模块现在使用Microsoft Visual C++ 14.0。

反馈

  • redsymbol说,Python3变得越来越好了,他使用3比2多三年,并深深地爱上了Python3.有时候不得不用2.7,就会感觉从第五代工具回到了二代。 对此有人反驳说,是“从二代回到五代”吧,Python3没有参数列表自解(parameter tuple unpacking by itself),用起来感觉非常不友好。orf对此回应:我用了7年多Python都不知道这玩意是什么,也没在代码中见过,移除难用的feature是一大进步,如果你接受不了的话,我很抱歉。Py3 is awesome.(跟帖中有一大堆关于这个feature的讨论)
  • “你们搞的这个typing模块啊,excited!”ricw提到,这个模块解决了Python最大的软肋,以后Python会变得越来越专业化。
  • ceronman说这是他最喜欢的3.X版本,Typing注释模块,async/await,解包广泛应用,这些功能都很赞,遗憾是PEP0498没有赶上这个版本,期待3.6(原帖又是长长的对这一feature的讨论).
  • 如果所有的东西都兼容Py3,这个世界就完美了 :)。
 

学校不教的计算机课

最近HN上有一篇讨论较火的文章Computer Science Courses that Don’t Exist, But Should,翻译了一下。注意,这篇文章首先发布在csdn,禁止任何形式的转载。

CSCI 2100:忘掉面向对象编程

思考如何创建、使用不属于任何对象的变量。学习“函数(functions)”,它跟方法(methods)很像但是更有用的多。前提是你已经学过一门使用了“抽象基类的课程”。

CSCI 3300:像经典软件学习

讨论并剖析具有划时代意义的软件产品,比如VisiCalc、AppleWorks、Robot Odyssey、Zork、和MacPaint等。学习的重点,在于用户界面,以及由硬件限制激发出的创造力。

CSCI 4020:用低效语言编写高效代码

对性能进行深度分析,用解释型的Python语言来写出能与传统的编译型C++匹敌的代码。而且代码还会更加健壮,写起来也很有意思。

CSCI 2170:用户体验之”命令行工具”

提高用户体验的原则也同样适用于命令行工具的设计,要专注于输出信息的相关性,可读性,最简化。UNIX的“ls”命令就是值得好好学习的一个例子。

PSYC 4410(心理学高年级课程):程序员内心强迫症

能辨别和理解那些无关紧要,但是程序员偏偏会去较真的事情:代码格式、如何分类、类型系统,非要将项目分解成很多个。还包括接触到不熟悉的系统时下意识的批评的细节。


HN精彩评论

  • 我能再加一个吗?“网络不像你想的那么简单:分布式系统生存指南”。作为一个在职业生涯中花了很多精力建设高性能网络,也和这个领域里地人公事了很长时间的一个程序员来说,我对现在从名校毕业的学生在这方面的知识之匮乏感到震惊。很多基本的东西,像是应用的无线传输,系统如何处理数据包,等待对应用的影响等等,对于一些程序员来说简直一窍不通。现在看来,网络连接的实质问题,确实被严重忽略了。
  • 程序员分为两种:一种被训练成能在熟悉的环境中称职地编码;另一种则像是《黑客帝国》中选择了红色药丸的人——他们知道事物的本质。
  • 再加一些:
    • 在白板上写翻转二叉树
    • 用COBOL实现Monads
    • 如何正确地纠正网络上的评论
    • 上一条中的槽点可能有哪些
    • 对Twitter的多项式机器人算法有多糟糕的介绍
    • 用Haskell学习函数式编程
    • 上一门课的实际用途
  • 亲爱的机算计学院:请不要再拿“算法导论”课来愚弄你们的学生了,这门课应该改为“组合算法导论”,因为这才是你们教的东西。你们完全忽视了“数字算法”,这是许多其他计算机学科分支的基础(计算机物理、计算机化学、计算机生物学等),同时这也是机器学习和数据科学毕业生薄弱之处。或者也可以保留“算法导论”的名字,但是教50%的算法和数据结构,50%的数字计算方法和科学计算。
  • “建立端对端软件解决方案”如何?让这门课贯穿所有软件开发流程(想法-设计-原型-公测-发布-迭代)。我最好的朋友马上毕业了,可他对软件开发一无所知,根本不了解现在的市场需求。
  • “Debug”:严肃地讲,这是门完整、系统的课程,而且越早开设越好。这是其他学科/工程不可缺少的技能。
  • 计算机科学和软件工程应该分成两门完全不同的学科。
 

垃圾回收(GC)的三种基本方式

垃圾(Garbage)就是程序需要回收的对象,如果一个对象不在被直接或间接地引用,那么这个对象就成为了「垃圾」,它占用的内存需要及时地释放,否则就会引起「内存泄露」。有些语言需要程序员来手动释放内存(回收垃圾),有些语言有垃圾回收机制(GC)。本文就来讨论GC实现的三种基本方式。

其实这三种方式也可以大体归为两类:跟踪回收,引用计数。美国IBM的沃森研究中心David F.Bacon等人发布的「垃圾回收统一理论」一文阐述了一个理论:任何垃圾回收的思路,无非以上两种的组合,其中一种的改善和进步,必然伴随着另一种的改善和进步。

跟踪回收

跟踪回收的方式独立于程序,定期运行来检查垃圾,需要较长时间的中断。

标记清除

标记清除的方式需要对程序的对象进行两次扫描,第一次从根(Root)开始扫描,被根引用了的对象标记为不是垃圾,不是垃圾的对象引用的对象同样标记为不是垃圾,以此递归。所有不是垃圾的对象的引用都扫描完了之后。就进行第二次扫描,第一次扫描中没有得到标记的对象就是垃圾了,对此进行回收。

复制收集

复制收集的方式只需要对对象进行一次扫描。准备一个「新的空间」,从根开始,对对象进行扫,如果存在对这个对象的引用,就把它复制到「新空间中」。一次扫描结束之后,所有存在于「新空间」的对象就是所有的非垃圾对象。

这两种方式各有千秋,标记清除的方式节省内存但是两次扫描需要更多的时间,对于垃圾比例较小的情况占优势。复制收集更快速但是需要额外开辟一块用来复制的内存,对垃圾比例较大的情况占优势。特别的,复制收集有「局部性」的优点。

在复制收集的过程中,会按照对象被引用的顺序将对象复制到新空间中。于是,关系较近的对象被放在距离较近的内存空间的可能性会提高,这叫做局部性。局部性高的情况下,内存缓存会更有效地运作,程序的性能会提高。

对于标记清除,有一种标记-压缩算法的衍生算法:

对于压缩阶段,它的工作就是移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。

引用计数

引用计数是指,针对每一个对象,保存一个对该对象的引用计数,该对象的引用增加,则相应的引用计数增加。如果对象的引用计数为零,则回收该对象。

优点:引用计数最大的优点就是容易实现,C++程序员应该都实现过类似的机制。二是成本小,基本上引用计数为0的时候垃圾会被立即回收,而其他方法难以预测对象的生命周期,垃圾存在的时间都会比这个方法高。另,这种垃圾回收方式产生的中断时间最短。

缺点:最著名的缺点就是如果对象中存在循环引用,就无法被回收。例如,下面三个对象互相引用,但是不存在从根(Root)指向的引用,所以已经是垃圾了。但是引用计数不为0.

DC_counter_way

还有一个缺点就是,引用计数不适合在并行中使用,多个线程同时操作引用计数,会引起数值不一样的问题从而导致内存错误。所以引用计数必须采用独占方式,如果引用操作频繁,那么加锁等并发控制机制的开销是相当大的。

Perl和Python采用了这种GC机制。

它们的衍生算法

分代回收

这种回收方式用了程序的一种特性:大部分对象会从产生开始在很短的时间内变成垃圾,而存在的很长时间的对象往往都有较长的生命周期。高频对新生成的对象进行回收,称为「小回收」,低频对所有对象回收,称为「大回收」。每一次「小回收」过后,就把存活下来的对象归为「老生代」,「小回收」的时候,遇到老生代直接跳过。大多数分代回收算法都采用的「复制收集」方法,因为小回收中垃圾的比例较大。

这种方式存在一个问题:如果在某个新生代的对象中,存在「老生代」的对象对它的引用,它就不是垃圾了,那么怎么制止「小回收」对其回收呢?这里用到了一中叫做写屏障的方式。

程序对所有涉及修改对象内容的地方进行保护,被称为「写屏障」(Write Barrier)。写屏障不仅用于分代回收,也用于其他GC算法中。

在此算法的表现是,用一个记录集来记录从新生代到老生代的引用。如果有两个对象A和B,当对A的对象内容进行修改并加入B的引用时,如果①A是「老生代」②B是「新生代」。则将这个引用加入到记录集中。「小回收」的时候,因为记录集中有对B的引用,所以B不再是垃圾。

增量回收

上面的算法缩短了「GC平均中断时间」,但是在对实时性要求很高的程序中,对「GC最高中断时间」的要求更高。比如,自动驾驶软件,如果某次GC中断了0.1s,那么损失可能是致命的。

增量回收就是将GC分成几部分来执行。设置「GC最多中断10ms」这样的条件限制来使GC的终端时间视作可预测的。

但是,在两段的GC程序之间,引用关系可能发生了变化。所以,这种GC算法也要写屏障,来记录引用关系的变化。虽然这种方式控制了中断最高时间,但是由于中断次数增加,GC总时间是增加的。

并行回收

基本原理是,在程序运行的同时进行GC工作,最大化CPU的性能。但是这种方式也要面对增量回收的问题,所以也要进行写屏障操作。

然而这种方式也并未做到完全不暂停原程序的运行,在某些特定的GC阶段还是要暂停原程序。多核化迅速发展的今天,这种算法也在不断优化。不间断原程序实现并行回收这个领域是相当值得期待的。

 

在编程中体验纯粹的快乐

Learn Python The Hard Way 的最后,有一个「老程序员的建议」,来告诫学完了这个教程的同学们不要拘泥于编程语言,不要陷入「关于语言的迷信和争吵」中,无论是否从事的是计算机行业,都可以爱上编程,从中的到乐趣。这个老程序员的忠言翻译如下:

现在你已经完成了这个教程,并决定继续深入学习。编程可能成为你的事业,也可能成为你的一个爱好。所以你可能需要一些建议,来保证少走弯路,并从这个「新欢」中的到最大的乐趣。

我从事编程已经很长时间了,长到对我来说这已经是一件极其无聊的工作。我在写这本书的时候,已经懂20门编程语言,能在一天到一个周的时间学会一项新语言(取决于它有多么古怪)。即使这样,我还是对此感到无聊,不再有任何兴趣。但这并不是说编程是很无聊的,也不是说你在将来会觉得它无聊,我意思是,我在干到现在这个年纪,觉得没意思了。

经过这么多年,我发现一个真理:用哪一个编程语言并不重要,重要的是用它来做什么。其实这道理我早就懂,不过还是经常忽视,还是会被编程语言分心。但从现在开始我不会再忘了,你最好也记住。

你学哪门语言,用哪门也都不重要,千万不要陷入对语言的宗教崇拜中,那只会让你忘记原来的目的:把编程语言当做一个工具来做有趣的事情。

编程作为一种智力活动,几乎是唯一一种能创造交互艺术的艺术形式了。你可以写软件让别人来玩,甚至可以通过这种形式直接和别人对话。别的艺术都没有这种交互性。电影拍出来之后直接播放给观众,图画不会动,但是代码能做到。

从事编程行业所获得的乐趣其实一般。这份工作确实不错,但是你可以经营一个快餐店来赚一样多的钱,还更轻松快乐。所以你最好在别的领域将编程作为一个秘密武器。

在科技公司工作的人会编程并没有什么特殊的,也不会获得特殊的尊重。但是从事生物、化学、政治、公务员、社会学、物理、历史和数学的人如果会编程就会格外受到尊重,因为他们能做到这些了不起的事情,来发展他们的学科。

当然了,这些建议都没什么意义。如果你真的喜欢编程,你尝试通过任何方式用它来提高你的生活。尽情去探索这个怪异、神奇、充满智慧的,50年来人们都一直孜孜不倦在探索的世界吧!愿快乐永远与你同在。

最后不得不提的一点,学着写软件可能让你变得有些与众不同。不管是好是坏,总之是不同了。可能有人会对你刻薄,比如在你身上用「书呆子」(nerd)这个词;可能你会发现因为你强大的逻辑,别人都不愿意和你争吵;可能你甚至会发现你懂计算机的运作原理,别人因为觉得你怪异、不合群。

对这些情况我只有一条建议:让他们去死吧!(They can go to hell.)这个世界需要古怪的人,需要书呆子,他们清楚或者热衷于弄清楚事情运作的原理。当真的有人如此对你的时候,要记住:这是你自己的路,不是他们的。与众不同不是犯罪,别人这么说你只是因为他们嫉妒,他们永远不敢在自己的白日梦里奢求这些。

You can code. They cannot. That is pretty damn cool.