wtfpython-cn README

The project's README from github.com/leisurelicht/wtfpython-cn. As one markdown file: README.md.

What the f*ck Python! 🐍

一些有趣且鲜为人知的 Python 特性.

原始版本: | English

翻译版本: | Vietnamese Tiếng Việt | Spanish Español | Korean 한국어 | Russian Русский | German Deutsch | Add translation

其他模式: | Interactive

[![WTFPL 2.0][license-image]][license-url] [![Commit id][commit-image]][commit-url] [![996.icu][996.icu-image]][996.icu-url]

Python, 是一个设计优美的解释型高级语言, 它提供了很多能让程序员感到舒适的功能特性. 但有的时候, Python 的一些输出结果对于初学者来说似乎并不是那么一目了然.

这个有趣的项目意在收集 Python 中那些难以理解和反人类直觉的例子以及鲜为人知的功能特性, 并尝试讨论这些现象背后真正的原理!

虽然下面的有些例子并不一定会让你觉得 WTFs, 但它们依然有可能会告诉你一些你所不知道的 Python 有趣特性. 我觉得这是一种学习编程语言内部原理的好办法, 而且我相信你也会从中获得乐趣!

如果您是一位经验比较丰富的 Python 程序员, 你可以尝试挑战看是否能一次就找到例子的正确答案. 你可能对其中的一些例子已经比较熟悉了, 那这也许能唤起你当年踩这些坑时的甜蜜回忆 :sweat_smile:

PS: 如果你不是第一次读了, 你可以在这里获取变动内容.

那么, 让我们开始吧...

Table of Contents/目录

Structure of the Examples/示例结构

所有示例的结构都如下所示:

> 一个精选的标题 *

标题末尾的星号表示该示例在第一版中不存在,是最近添加的.

# 准备代码.
# 释放魔法...

Output (Python version):

>>> 触发语句
出乎意料的输出结果

(可选): 对意外输出结果的简短描述.

💡 说明:

  • 简要说明发生了什么以及为什么会发生.
    如有必要, 举例说明
    
    Output:
    >>> 触发语句 # 一些让魔法变得容易理解的例子
    # 一些正常的输入
    

注意: 所有的示例都在 Python 3.5.2 版本的交互解释器上测试过, 如果不特别说明应该适用于所有 Python 版本.

Usage/用法

我个人建议, 最好依次阅读下面的示例, 并对每个示例:

PS: 你也可以在命令行阅读 WTFpython. 我们有 pypi 包 和 npm 包(支持代码高亮).(译: 这两个都是英文版的)

安装 npm 包 wtfpython

$ npm install -g wtfpython

或者, 安装 pypi 包 wtfpython

$ pip install wtfpython -U

现在, 在命令行中运行 wtfpython, 你就可以开始浏览了.


👀 Examples/示例

Section: Strain your brain!/大脑运动!

> First things first!/要事优先 *

众所周知,Python 3.8 推出"海象"运算符 (:=) 方便易用,让我们一起看看。

1.

# Python 版本 3.8+

>>> a = "wtf_walrus"
>>> a
'wtf_walrus'

>>> a := "wtf_walrus"
File "<stdin>", line 1
    a := "wtf_walrus"
      ^
SyntaxError: invalid syntax

>>> (a := "wtf_walrus") # 该语句有效
'wtf_walrus'
>>> a
'wtf_walrus'

2 .

# Python 版本 3.8+

>>> a = 6, 9
>>> a
(6, 9)

>>> (a := 6, 9)
(6, 9)
>>> a
6

>>> a, b = 6, 9 # 典型拆包操作
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # 出错啦
  File "<stdin>", line 1
    (a, b = 16, 19)
          ^
SyntaxError: invalid syntax

>>> (a, b := 16, 19) # 这里的结果是一个奇怪的三元组
(6, 16, 19)

>>> a # a值仍然没变?
6

>>> b
16

💡 说明

“海象”运算符简介

海象运算符 (:=) 在Python 3.8中被引入,用来在表达式中为变量赋值。

def some_func():
        # 假设这儿有一些耗时的计算
        # time.sleep(1000)
        return 5

# 引入“海象”运算符前的例子
if some_func():
        print(some_func()) # 糟糕的案例——函数运算了两次

# 或者,加以改进:
a = some_func()
if a:
    print(a)

# 有了“海象”运算符,你可以写的更简洁:
if a := some_func():
        print(a)

输出 (> Python 3.8):

5
5
5

这样既减少了一行代码,又避免了两次调用 some_func 函数。


> Strings can be tricky sometimes/微妙的字符串 *

1.

>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # 注意两个的id值是相同的.
140420665652016

2.

>>> a = "wtf"
>>> b = "wtf"
>>> a is b
True

>>> a = "wtf!"
>>> b = "wtf!"
>>> a is b
False

>>> a, b = "wtf!", "wtf!"
>>> a is b 
True # 3.7 版本返回结果为 False.

3.

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False # 3.7 版本返回结果为 True

很好理解, 对吧?

💡 说明:


> Be careful with chained operations/小心链式操作

>>> (False == False) in [False] # 可以理解
False
>>> False == (False in [False]) # 可以理解
False
>>> False == False in [False] # 为毛?
True

>>> True is False == False
False
>>> False is False is False
True

>>> 1 > 0 < 1
True
>>> (1 > 0) < 1
False
>>> 1 > (0 < 1)
False

💡 说明:

根据 https://docs.python.org/3/reference/expressions.html#comparisons

形式上, 如果 a, b, c, ..., y, z 是表达式, 而 op1, op2, ..., opN 是比较运算符, 那么除了每个表达式最多只出现一次以外 a op1 b op2 c ... y opN z 就等于 a op1 b and b op2 c and ... y opN z.

虽然上面的例子似乎很愚蠢, 但是像 a == b == c 或 0 <= x <= 100 就很棒了.


> How not to use is operator/为什么不使用 is 操作符

下面是一个在互联网上非常有名的例子.

1.

>>> a = 256
>>> b = 256
>>> a is b
True

>>> a = 257
>>> b = 257
>>> a is b
False

2.

>>> a = []
>>> b = []
>>> a is b
False

>>> a = tuple()
>>> b = tuple()
>>> a is b
True

3. Output

>>> a, b = 257, 257
>>> a is b
True

Output (Python 3.7.x specifically)

>>> a, b = 257, 257
>>> a is b
False

💡 说明:

is 和 == 的区别

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

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

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

引用自 https://docs.python.org/3/c-api/long.html

当前的实现为-5到256之间的所有整数保留一个整数对象数组, 当你创建了一个该范围内的整数时, 你只需要返回现有对象的引用. 所以改变1的值是有可能的. 我怀疑这种行为在Python中是未定义行为. :-)

>>> id(256)
10922528
>>> a = 256
>>> b = 256
>>> id(a)
10922528
>>> id(b)
10922528
>>> id(257)
140084850247312
>>> x = 257
>>> y = 257
>>> id(x)
140084850247440
>>> id(y)
140084850247344

这里解释器并没有智能到能在执行 y = 257 时意识到我们已经创建了一个整数 257, 所以它在内存中又新建了另一个对象.

类似的优化也适用于其他不可变对象,例如空元组。由于列表是可变的,这就是为什么 [] is [] 将返回 False 而 () is () 将返回 True。 这解释了我们的第二个代码段。而第三个呢:

当 a 和 b 在同一行中使用相同的值初始化时,会指向同一个对象.

>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
140640774013392
>>> id(b)
140640774013488

> Hash brownies/是时候来点蛋糕了!

1.

some_dict = {}
some_dict[5.5] = "JavaScript"
some_dict[5.0] = "Ruby"
some_dict[5] = "Python"

Output:

>>> some_dict[5.5]
"JavaScript"
>>> some_dict[5.0] # "Python" 消除了 "Ruby" 的存在?
"Python"
>>> some_dict[5] 
"Python"

>>> complex_five = 5 + 0j
>>> type(complex_five)
complex
>>> some_dict[complex_five]
"Python"

为什么到处都是Python?

💡 说明


> Deep down, we're all the same./本质上,我们都一样. *

class WTF:
  pass

Output:

>>> WTF() == WTF() # 两个不同的对象应该不相等
False
>>> WTF() is WTF() # 也不相同
False
>>> hash(WTF()) == hash(WTF()) # 哈希值也应该不同
True
>>> id(WTF()) == id(WTF())
True

💡 说明:


> Disorder within order/有序中潜藏着无序 *

from collections import OrderedDict

dictionary = dict()
dictionary[1] = 'a'; dictionary[2] = 'b';

ordered_dict = OrderedDict()
ordered_dict[1] = 'a'; ordered_dict[2] = 'b';

another_ordered_dict = OrderedDict()
another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';

class DictWithHash(dict):
    """
    实现了 __hash__ 魔法方法的dict类
    """
    __hash__ = lambda self: 0

class OrderedDictWithHash(OrderedDict):
    """
    实现了 __hash__ 魔法方法的OrderedDict类
    """
    __hash__ = lambda self: 0

Output

>>> dictionary == ordered_dict # 如果 a == b
True
>>> dictionary == another_ordered_dict # 且 b == c
True
>>> ordered_dict == another_ordered_dict # 那么为什么 c == a 不成立??
False

# 众所周知,set数据结构储存不重复元素,
# 让我们生成以上字典的 set 数据类型,看看会发生什么……

>>> len({dictionary, ordered_dict, another_ordered_dict})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'dict'

# dict类没有实现 __hash__ ,出错可以理解,接下来使用我们派生的类。

>>> dictionary = DictWithHash()
>>> dictionary[1] = 'a'; dictionary[2] = 'b';
>>> ordered_dict = OrderedDictWithHash()
>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
>>> another_ordered_dict = OrderedDictWithHash()
>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
>>> len({dictionary, ordered_dict, another_ordered_dict})
1
>>> len({ordered_dict, another_ordered_dict, dictionary}) # 交换顺序
2

到底发生了什么?

💡 说明:

wtfpython-cn (leisurelicht/wtfpython-cn)

Searching Database…

这是什么?《What the f*ck Python!》(wtfpython) 的中文翻译版——精选了一系列让人意外、反直觉的 Python 代码片段,每一段都配有讲解:代码会输出什么,以及背后的真实原因。
这些内容是怎么组织的?它是一份阅读材料:几十个例子分成若干章节,比如「Strain your brain!」和「The Hidden treasures!」。每个例子都展示一段代码、出人意料的输出结果,以及其背后机制的讲解。
我怎么阅读它——需要安装什么东西吗?什么都不用装——直接从头到尾读 README,在看到讲解之前先自己猜每段的输出就行。更喜欢终端的话,可以安装 pypi 或 npm 包 wtfpython,然后运行 wtfpython 命令来浏览;这两个包提供的都是英文版内容。
在我的 Python 版本上,这些例子的表现会一样吗?所有例子都在 Python 3.5.2 的交互式解释器里测试过,除非特别注明,应该在各版本上都成立。不过有些坑和版本有关——3.7 里结果变了,3.8 里又变了。
我写 Python 好多年了——这个还值得我花时间看吗?值得——就算是经验丰富的 Python 老手,也很难第一次就猜对每个输出,而且讲解会深入内部机制,比如字符串驻留和 -5 到 256 的整数缓存。那些熟悉的坑,看完会变成美好的回忆。
许可证有什么特别之处吗?用的是 WTFPL 2.0——出了名的宽松:基本上你想拿这些内容做什么都可以。
它覆盖了哪些 Python 的坑?字符串驻留、'is' 和 '==' 的区别、为什么 256 有缓存而 257 没有、字典里 5 和 5.0 是同一个键、可变的默认参数、循环变量泄漏到循环外、海象运算符的怪癖、银行家舍入,甚至还有一个能在 Python 里用的 goto。
我能看英文版或其他语言的版本吗?可以——这个仓库是英文原版的中文翻译,另外还有越南语、西班牙语、韩语、俄语和德语版本。pypi/npm 的命令行包提供的是英文内容。
作者为什么要做这个项目?因为莫名其妙的输出和鲜为人知的特性,是了解 Python 内部工作原理的有趣方式——这个项目收集了那些反直觉的案例,并逐一探讨其背后的真实原理。
现在还在增加新例子吗?是的——翻译版在持续更新(仓库标着施工中的标志),而且第一版之后新增的例子,标题上会带一颗星作为标记。
Size
0.7 MB
Stars
12.7K
Updated
CommitNov 29
Age
7y
Commits · 90 Days