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 is this?The Chinese translation of 'What the f*ck Python!' (wtfpython) — a curated collection of surprising, counter-intuitive Python snippets, each explained: what the code prints and why it actually happens.
How is the collection organized?It's reading material: dozens of examples grouped into sections like 'Strain your brain!' and 'The Hidden treasures!'. Each one shows a code snippet, its surprising output, and an explanation of the mechanism behind it.
How do I read it — is there anything to install?Nothing — just read the README top to bottom, guessing each output before the explanation. Prefer the terminal? Install the pypi or npm package wtfpython and run the wtfpython command to browse; both serve the English version.
Will the examples behave the same on my Python version?All examples were tested on Python 3.5.2's interactive interpreter and should hold across versions unless noted, but some gotchas are version-sensitive — results shifted in 3.7 and changed again in 3.8.
I've written Python for years — is this still worth my time?Yes — experienced Pythonistas are challenged to guess every output correctly on the first try, and the explanations dig into internals like string interning and the -5 to 256 integer cache. Familiar traps turn into fond memories.
Anything unusual about the license?It's WTFPL 2.0 — famously permissive: you can essentially do whatever you want with the content.
What kind of Python gotchas does it cover?String interning, 'is' vs '==', why 256 is cached but 257 isn't, dict keys where 5 and 5.0 collide, mutable default arguments, loop variables leaking out, walrus-operator quirks, banker's rounding, and even a working goto in Python.
Can I read it in English or other languages?Yes — this repo is the Chinese translation of the English original, and Vietnamese, Spanish, Korean, Russian, and German versions also exist. The pypi/npm CLI packages serve the English text.
Why did the author put this together?Because puzzling outputs and little-known features are a fun way to learn how Python works internally — the project collects the counter-intuitive cases and discusses the real principles behind each one.
Are new examples still being added?Yes — the translation is continuously updated (the repo is marked with a construction sign), and any example added after the first edition is flagged with a star in its title.
Size
0.7 MB
Stars
12.7K
Updated
CommitNov 29
Age
7y
Commits · 90 Days