wtfpython README

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

Shows a wtfpython logo.

What the f*ck Python! 😱

Exploring and understanding Python through surprising snippets.

Translations: Chinese 中文 | Vietnamese Tiếng Việt | Spanish Español | Korean 한국어 | Russian Русский | German Deutsch | Persian فارسی | Add translation

Other modes: Interactive Website | Interactive Notebook

Python, being a beautifully designed high-level and interpreter-based programming language, provides us with many features for the programmer's comfort. But sometimes, the outcomes of a Python snippet may not seem obvious at first sight.

Here's a fun project attempting to explain what exactly is happening under the hood for some counter-intuitive snippets and lesser-known features in Python.

While some of the examples you see below may not be WTFs in the truest sense, but they'll reveal some of the interesting parts of Python that you might be unaware of. I find it a nice way to learn the internals of a programming language, and I believe that you'll find it interesting too!

If you're an experienced Python programmer, you can take it as a challenge to get most of them right in the first attempt You may have already experienced some of them before, and I might be able to revive sweet old memories of yours! :sweat_smile:

PS: If you're a returning reader, you can learn about the new modifications here (the examples marked with asterisk are the ones added in the latest major revision).

So, here we go...

Table of Contents

Structure of the Examples

All the examples are structured like below:

Section: (if necessary)

▶ Some fancy Title

# Set up the code.
# Preparation for the magic...

Output (Python version(s)):

>>> triggering_statement
Some unexpected output

(Optional): One line describing the unexpected output.

💡 Explanation:

  • Brief explanation of what's happening and why is it happening.
# Set up code
# More examples for further clarification (if necessary)

Output (Python version(s)):

>>> trigger # some example that makes it easy to unveil the magic
# some justified output

Note: All the examples are tested on Python 3.5.2 interactive interpreter, and they should work for all the Python versions unless explicitly specified before the output.

Usage

A nice way to get the most out of these examples, in my opinion, is to read them in sequential order, and for every example:


👀 Examples

Section: Strain your brain!

▶ First things first! *

For some reason, the Python 3.8's "Walrus" operator (:=) has become quite popular. Let's check it out,

1.

# Python version 3.8+

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

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

>>> (a := "wtf_walrus") # This works though
'wtf_walrus'
>>> a
'wtf_walrus'

2 .

# Python version 3.8+

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

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

>>> a, b = 6, 9 # Typical unpacking
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # Oops
  File "<stdin>", line 1
    (a, b = 16, 19)
          ^
SyntaxError: invalid syntax

>>> (a, b := 16, 19) # This prints out a weird 3-tuple
(6, 16, 19)

>>> a # a is still unchanged?
6

>>> b
16

💡 Explanation

The Walrus operator (:=) was introduced in Python 3.8, it can be useful in situations where you'd want to assign values to variables within an expression.

def some_func():
        # Assume some expensive computation here
        # time.sleep(1000)
        return 5

# So instead of,
if some_func():
        print(some_func()) # Which is bad practice since computation is happening twice

# or
a = some_func()
if a:
    print(a)

# Now you can concisely write
if a := some_func():
        print(a)

Output (> 3.8):

5
5
5

This saved one line of code, and implicitly prevented invoking some_func twice.


▶ Strings can be tricky sometimes

1. Notice that both the ids are same.

assert id("some_string") == id("some" + "_" + "string")
assert id("some_string") == id("some_string")

2. True because it is invoked in script. Might be False in python shell or ipython

a = "wtf"
b = "wtf"
assert a is b

a = "wtf!"
b = "wtf!"
assert a is b

3. True because it is invoked in script. Might be False in python shell or ipython

a, b = "wtf!", "wtf!"
assert a is b

a = "wtf!"; b = "wtf!"
assert a is b

4. Disclaimer - snippet is not relevant in modern Python versions

Output (< Python3.7 )

>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False

Makes sense, right?

💡 Explanation:

Shows a string interning process.


▶ Be careful with chained operations

>>> (False == False) in [False] # makes sense
False
>>> False == (False in [False]) # makes sense
False
>>> False == False in [False] # now what?
True

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

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

💡 Explanation:

As per https://docs.python.org/3/reference/expressions.html#comparisons

Formally, if a, b, c, ..., y, z are expressions and op1, op2, ..., opN are comparison operators, then a op1 b op2 c ... y opN z is equivalent to a op1 b and b op2 c and ... y opN z, except that each expression is evaluated at most once.

While such behavior might seem silly to you in the above examples, it's fantastic with stuff like a == b == c and 0 <= x <= 100.


▶ How not to use is operator

The following is a very famous example present all over the internet.

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

💡 Explanation:

The difference between is and ==

256 is an existing object but 257 isn't

When you start up python the numbers from -5 to 256 will be allocated. These numbers are used a lot, so it makes sense just to have them ready.

Quoting from https://docs.python.org/3/c-api/long.html

The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you just get back a reference to the existing object. So it should be possible to change the value of 1. I suspect the behavior of Python, in this case, is undefined. :-)

>>> 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

Here the interpreter isn't smart enough while executing y = 257 to recognize that we've already created an integer of the value 257, and so it goes on to create another object in the memory.

Similar optimization applies to other immutable objects like empty tuples as well. Since lists are mutable, that's why [] is [] will return False and () is () will return True. This explains our second snippet. Let's move on to the third one,

Both a and b refer to the same object when initialized with same value in the same line.

Output

>>> a, b = 257, 257
>>> id(a)
140640774013296
>>> id(b)
140640774013296
>>> a = 257
>>> b = 257
>>> id(a)
14064077401

wtfpython (satwikkansal/wtfpython)

Searching Database…

37.1K ·GitHub
这是什么?一份免费且非常流行的合集,收录了许多出人意料、违背直觉的 Python 代码片段,每个片段都附有真实运行结果,以及底层发生了什么的解释。
这是需要安装的东西,还是直接阅读的内容?是阅读用的,不用安装——它是一本带注释的代码示例指南。你可以直接在 README 里看,也可以在交互式网站上,或者以 Jupyter notebook 的形式浏览。
我该怎么学才最有效?按顺序阅读示例:先猜每个片段的输出,再核对真实输出,然后看解释。猜对了?继续下一个。猜错了?这正是学到东西的地方。
我需要特定的 Python 版本才能跟着学吗?不需要安装任何东西——但如果你想自己动手试这些片段,任何 Python 3 版本都可以。它们在 3.5.2 上测试过,除非另有说明,所有版本都应该适用;海象运算符那个示例需要 3.8+。
这些片段在我的电脑上运行结果都会一样吗?不一定——有些示例在交互式 shell 里和脚本里表现不同(字符串驻留),还有少数片段被明确标注为在现代 Python 版本中已不适用。
我写 Python 已经很多年了——这东西还值得我看吗?值得——作者明确邀请有经验的 Python 程序员把它当作挑战,试着第一次就猜对所有输出。
这些坑实际长什么样?一个经典例子:a = 256; b = 256 会让 a is b 为 True,但换成 257 就是 False——因为 CPython 缓存了 -5 到 256 的整数,所以 257 会是两个独立的对象。
每个示例是怎么组织的?套路始终一样:先是铺垫代码,然后是真实输出(附适用的 Python 版本),可选的一句点破意外之处的话,最后是用列表逐条解释为什么会这样。
我能用它来准备 Python 面试吗?这是它最热门的用途之一——GitHub 甚至给这个仓库打了 python-interview-questions 的标签,而且很多条目都是经典面试陷阱,比如可变默认参数。
有其他语言的版本吗?有——社区提供了中文、越南语、西班牙语、韩语、俄语、德语和波斯语翻译版本。
Size
1.4 MB
Stars
37.1K
Age
9y
Commits · 90 Days