这些 Python 不为人知的「坑」,躲都躲不开

作者:王易诺,人工智能算法工程师,Python/C++ 程序员,推理/科幻小说作者。曾于高中获得全国青少年信息学奥林匹克联赛一等奖。

现已取得复旦大学计算机科学技术学士学位,以及爱丁堡大学计算机科学硕士学位,目前正于东京大学攻读信息科学博士学位。

主要研究方向包括自然语言处理、深度学习中的跨模态任务等。

前 言

首先得声明一下,本文不是在黑 Python。

事实上已经有非常多的程序员朋友在用 Python 做项目,但在使用的过程中,也常常碰到过一些坑,凡用过一段时间的人也该深有体会。哪怕是资深大佬,很多时候也很难避免。

Python 的坑也许还有更多,很想听到各位的见解或经历,欢迎留言,小编将挑选 3 名精彩留言的读者,送上一本图灵 Python 类图书。(统计截止时间:11月9日 12:00)

What the f**k! Python!

GitHub 上有一个项目叫做 “wtfpython”,目的就是记录那些 Python 程序猿总会感到 “What the f**k!” 的瞬间。我把这个项目的地址贴在下面,然后从里面摘录几个最经典的例子出来。

https://github.com/satwikkansal/wtfpython

首先,一些关于字符串的 “基本” 操作

1


2

3


说明:

1. 这些行为是由于 Cpython 在编译优化时,某些情况下会尝试使用已经存在的不可变对象而不是每次都创建一个新对象。(这种行为被称作字符串的驻留[string interning])。

2. 发生驻留之后,许多变量可能指向内存中的相同字符串对象(从而节省内存)。

3. 在上面的代码中,字符串是隐式驻留的。何时发生隐式驻留则取决于具体的实现. 这里有一些方法可以用来猜测字符串是否会被驻留:

  • 所有长度为 0 和长度为 1 的字符串都被驻留。

  • 字符串在编译时被实现(‍‍‍‍‍‍‍‍‍‍‍‍‍‍ 'wtf' 将被驻留,但是 ''.join(['w', 't', 'f']) 将不会被驻留)

  • 字符串中只包含字母,数字或下划线时将会驻留,所以 'wtf!' 由于包含 ! 而未被驻留。

  • 当在同一行将 a 和 b 的值设置为 "wtf!" 的时候,Python 解释器会创建一个新对象,然后同时引用第二个变量(译:仅适用于3.7以下,详细情况请看这里)。如果你在不同的行上进行赋值操作,它就不会“知道”已经有一个 wtf!对象 (因为 "wtf!" 不是按照上面提到的方式被隐式驻留的)。它是一种编译器优化,特别适用于交互式环境。

  • 常量折叠(constant folding) 是 Python 中的一种窥孔优化( peephole optimization)技术。这意味着在编译时表达式 'a'*20 会被替换为 'aaaaaaaaaaaaaaaaaaaa' 以减少运行时的时钟周期,只有长度小于 20 的字符串才会发生常量折叠。

关于函数的返回值——


输出:

说明:

  • 当在 "try...finally" 语句的 try 中执行 return, break 或 continue 后, finally 子句依然会执行。

  • 函数的返回值由最后执行的 return 语句决定。由于 finally 子句一定会执行,所以 finally 子句中的 return 将始终是最后执行的语句。

关于类的本质


输出:

说明:

  • 当调用 id 函数时,Python 创建了一个 WTF 类的对象并传给 id 函数. 然后 id 函数获取其 id 值(也就是内存地址),然后丢弃该对象,该对象就被销毁了。

  • 当我们连续两次进行这个操作时,Python 会将相同的内存地址分配给第二个对象。因为(在 CPython 中)id 函数使用对象的内存地址作为对象的 id 值,所以两个对象的 id 值是相同的。

  • 综上,对象的 id 值仅仅在对象的生命周期内唯一。在对象被销毁之后,或被创建之前,其他对象可以具有相同的 id 值。

  • 那为什么 is 操作的结果为 False 呢? 这是由对象销毁的顺序造成的.

你了解 Python 中的 for 循环语句吗 ——

输出:

说明:

由于循环在 Python 中工作方式,赋值语句 i = 10 并不会影响迭代循环,在每次迭代开始之前,迭代器(这里指 range(4) ) 生成的下一个元素就被解包并赋值给目标列表的变量(这里指 i)了

“is” 究竟是什么 ——

说明:

is 和 == 的区别

  • is 运算符检查两个运算对象是否引用自同一对象(即,它检查两个运算对象是否相同)。

  • == 运算符比较两个运算对象的值是否相等.

  • 因此 is 代表引用相同,== 代表值相等,还有一个例子可以用来说明这一点——

256 是一个已经存在的对象,而 257 不是

当你启动 Python 的时候,数值为 -5 到 256 的对象就已经被分配好了,这些数字因为经常被使用,所以会被提前准备好。

Python 通过这种创建小整数池的方式来避免小整数频繁的申请和销毁内存空间。

is not … is not is (not …) 你在说绕口令吗?

说明:

  • is not 是个单独的二元运算符,与分别使用 is 和 not 不同.

  • 如果操作符两侧的变量指向同一个对象,则 is not 的结果为 False,否则结果为 True。

三个引号——

说明:

Python 提供隐式的字符串连接,例如:

' ' ' 和 " " " 在 Python中也是字符串定界符,Python 解释器在先遇到三个引号的的时候会尝试再寻找三个终止引号作为定界符,如果不存在则会导致 SyntaxError 异常。

假作真时真亦假——

输出:

说明:

  • 最初,Python 并没有 bool 型 (人们用0表示假值,用非零值比如1作为真值). 后来他们添加了 True , False 和 bool 型,但是,为了向后兼容,他们没法把 True 和 False 设置为常量,只是设置成了内置变量.

  • Python 3 由于不再需要向后兼容,终于可以修复这个问题了,所以这个例子无法在 Python 3.x 中执行!

骗过你的眼睛 ——

说明:

一些非西方字符虽然看起来和英语字母相同,但会被解释器识别为不同的字母。

奇怪的加号 ——

1

输出

2

输出:

说明:

  • a += b 并不总是与 a = a + b 表现相同, 类实现 op= 运算符的方式也许 是不同的,列表就是这样做的。

  • 表达式 a = a + [5,6,7,8] 会生成一个新列表,并让 a 引用这个新列表,同时保持 b 不变。

  • 表达式 a += [5,6,7,8] 实际上是使用的是 "extend" 函数,所以 a 和 b 仍然指向已被修改的同一列表。

最后,再来一条超极机密 ——

别问,自己试一试就知道了!

全面深入的对 Python 语言

关键特性剖析到位

[巴西] Luciano Ramalho | 著

安道,吴珂 | 译

本书由奋战在Python开发一线近20年的Luciano Ramalho执笔,Victor Stinner、Alex Martelli等Python大咖担纲技术审稿人,从语言设计层面剖析编程细节,兼顾Python 3和Python 2,告诉你Python中不亲自动手实践就无法理解的语言陷阱成因和解决之道,教你写出风格地道的Python代码。

本书致力于帮助Python开发人员挖掘这门语言及相关程序库的优秀特性,避免重复劳动,同时写出简洁、流畅、易读、易维护,并且具有地道Python风格的代码。本书尤其深入探讨了Python语言的高级用法,涵盖数据结构、Python风格的对象、并行与并发,以及元编程等不同的方面。

热销 750,000 册的

史诗级 Python 经典

《Python编程:从入门到实践》第2版

埃里克·马瑟斯 | 著

袁国忠 | 译

本书是针对所有层次Python读者而作的Python入门书。全书分两部分:第一部分介绍用Python编程所必须了解的基本概念,包括强大的Python库和工具,以及列表、字典、if语句、类、文件与异常、代码测试等内容;

第二部分将理论付诸实践,讲解如何开发三个项目,包括简单的2D游戏、利用数据生成交互式的信息图以及创建和定制简单的Web应用,并帮助读者解决常见编程问题和困惑。

第2版进行了全面修订,简化了Python安装流程,新增了f字符串、get()方法等内容,并且在项目中使用了Plotly库以及新版本的Django和Bootstrap,等等。