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 to a regular user at first sight.
Here is a fun project to collect such tricky & counter-intuitive examples and lesser-known features in Python, attempting to discuss what exactly is happening under the hood!
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 think you'll find them interesting as well!
If you're an experienced Python programmer, you can take it as a challenge to get most of them right in first attempt. You may be already familiar with some of these examples, and I might be able to revive sweet old memories of yours being bitten by these gotchas :sweat_smile:
PS: If you're a returning reader, you can learn about the new modifications [here](https://github.com/satwikkansal/wtfpython/releases/).
So, here we go...
# Table of Contents
<!-- TOC -->
- [Table of Contents](#table-of-contents)
- [Structure of the Examples](#structure-of-the-examples)
- [Usage](#usage)
- [👀 Examples](#👀-examples)
- [Section: Strain your brain!](#section-strain-your-brain)
- [> Let's make a giant string!](#-lets-make-a-giant-string)
- [> Explicit typecast of strings](#-explicit-typecast-of-strings)
- [> Minor Ones](#-minor-ones)
- [Contributing](#contributing)
- [Acknowledgements](#acknowledgements)
- [🎓 License](#🎓-license)
- [Help](#help)
- [Want to surprise your geeky pythonist friends?](#want-to-surprise-your-geeky-pythonist-friends)
- [Need a pdf version?](#need-a-pdf-version)
- [Follow Commit](#follow-commit)
<!-- /TOC -->
# Structure of the Examples
All the examples are structured like below:
> ### > Some fancy Title *
> The asterisk at the end of the title indicates the example was not present in the first release and has been recently added.
>
> ```py
> # Setting up the code.
> # Preparation for the magic...
> ```
>
> **Output (Python version):**
> ```py
> >>> triggering_statement
> Probably unexpected output
> ```
> (Optional): One line describing the unexpected output.
>
>
> #### 💡 Explanation:
>
> * Brief explanation of what's happening and why is it happening.
> ```py
> Setting up examples for clarification (if necessary)
> ```
> **Output:**
> ```py
> >>> 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 in the description.
# Usage
A nice way to get the most out of these examples, in my opinion, will be just to read the examples chronologically, and for every example:
- Carefully read the initial code for setting up the example. If you're an experienced Python programmer, most of the times you will successfully anticipate what's going to happen next.
- Read the output snippets and,
+ Check if the outputs are the same as you'd expect.
+ Make sure if you know the exact reason behind the output being the way it is.
- If no, take a deep breath, and read the explanation (and if you still don't understand, shout out! and create an issue [here](https://github.com/satwikkansal/wtfPython)).
- If yes, give a gentle pat on your back, and you may skip to the next example.
PS: You can also read WTFpython at the command line. There's a pypi package and an npm package (supports colored formatting) for the same.
To install the npm package [`wtfpython`](https://www.npmjs.com/package/wtfpython)
```sh
$ npm install -g wtfpython
```
Alternatively, to install the pypi package [`wtfpython`](https://pypi.python.org/pypi/wtfpython)
```sh
$ pip install wtfpython -U
```
Now, just run `wtfpython` at the command line which will open this collection in your selected `$PAGER`.
> "列表推导不再支持句法形式 `[... for var in item1, item2, ...]`. 取而代之的是 `[... for var in (item1, item2, ...)]`. 另外, 注意列表推导具有不同的语义: 它们更接近于 `list()` 构造函数中生成器表达式的语法糖(译: 这一句我也不是很明白), 特别是循环控制变量不再泄漏到周围的作用域中."
goto .breakout # breaking out from a deeply nested loop
label .breakout
print("Freedom!")
```
**Output (Python 2.3):**
```py
I'm trapped, please rescue!
I'm trapped, please rescue!
Freedom!
```
#### 💡 Explanation:
- A working version of `goto` in Python was [announced](https://mail.python.org/pipermail/python-announce-list/2004-April/002982.html) as an April Fool's joke on 1st April 2004.
- Current versions of Python do not have this module.
- Although it works, but please don't use it. Here's the [reason](https://docs.python.org/3/faq/design.html#why-is-there-no-goto) to why `goto` is not present in Python.
---
### > Brace yourself! *
If you are one of the people who doesn't like using whitespace in Python to denote scopes, you can use the C-style {} by importing,
```py
from __future__ import braces
```
**Output:**
```py
File "some_file.py", line 1
from __future__ import braces
SyntaxError: not a chance
```
Braces? No way! If you think that's disappointing, use Java.
#### 💡 Explanation:
+ The `__future__` module is normally used to provide features from future versions of Python. The "future" here is however ironic.
+ This is an easter egg concerned with the community's feelings on this issue.
---
### > Let's meet Friendly Language Uncle For Life *
**Output (Python 3.x)**
```py
>>> from __future__ import barry_as_FLUFL
>>> "Ruby" != "Python" # there's no doubt about it
File "some_file.py", line 1
"Ruby" != "Python"
^
SyntaxError: invalid syntax
>>> "Ruby" <> "Python"
True
```
There we go.
#### 💡 Explanation:
- This is relevant to [PEP-401](https://www.python.org/dev/peps/pep-0401/) released on April 1, 2009 (now you know, what it means).
- Quoting from the PEP-401
> Recognized that the != inequality operator in Python 3.0 was a horrible, finger pain inducing mistake, the FLUFL reinstates the <> diamond operator as the sole spelling.
- There were more things that Uncle Barry had to share in the PEP; you can read them [here](https://www.python.org/dev/peps/pep-0401/).
---
### > Even Python understands that love is complicated *
```py
import this
```
Wait, what's **this**? `this` is love :heart:
**Output:**
```
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!
```
It's the Zen of Python!
```py
>>> love = this
>>> this is love
True
>>> love is True
False
>>> love is False
False
>>> love is not True or False
True
>>> love is not True or False; love is love # Love is complicated
True
```
#### 💡 Explanation:
*`this` module in Python is an easter egg for The Zen Of Python ([PEP 20](https://www.python.org/dev/peps/pep-0020)).
* And if you think that's already interesting enough, check out the implementation of [this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py). Interestingly, the code for the Zen violates itself (and that's probably the only place where this happens).
* Regarding the statement `love is not True or False; love is love`, ironic but it's self-explanatory.
---
### > Yes, it exists!
**The `else` clause for loops.** One typical example might be:
```py
def does_exists_num(l, to_find):
for num in l:
if num == to_find:
print("Exists!")
break
else:
print("Does not exist")
```
**Output:**
```py
>>> some_list = [1, 2, 3, 4, 5]
>>> does_exists_num(some_list, 4)
Exists!
>>> does_exists_num(some_list, -1)
Does not exist
```
**The `else` clause in exception handling.** An example,
```py
try:
pass
except:
print("Exception occurred!!!")
else:
print("Try block executed successfully...")
```
**Output:**
```py
Try block executed successfully...
```
#### 💡 Explanation:
- The `else` clause after a loop is executed only when there's no explicit `break` after all the iterations.
-`else` clause after try block is also called "completion clause" as reaching the `else` clause in a `try` statement means that the try block actually completed successfully.
---
### > Inpinity *
The spelling is intended. Please, don't submit a patch for this.
**Output (Python 3.x):**
```py
>>> infinity = float('infinity')
>>> hash(infinity)
314159
>>> hash(float('-inf'))
-314159
```
#### 💡 Explanation:
- Hash of infinity is 10⁵ x π.
- Interestingly, the hash of `float('-inf')` is "-10⁵ x π" in Python 3, whereas "-10⁵ x e" in Python 2.
---
### > Mangling time! *
```py
class Yo(object):
def __init__(self):
self.__honey = True
self.bitch = True
```
**Output:**
```py
>>> Yo().bitch
True
>>> Yo().__honey
AttributeError: 'Yo' object has no attribute '__honey'
>>> Yo()._Yo__honey
True
```
Why did `Yo()._Yo__honey` work? Only Indian readers would understand.
#### 💡 Explanation:
* [Name Mangling](https://en.wikipedia.org/wiki/Name_mangling) is used to avoid naming collisions between different namespaces.
* In Python, the interpreter modifies (mangles) the class member names starting with `__` (double underscore) and not ending with more than one trailing underscore by adding `_NameOfTheClass` in front.
* So, to access `__honey` attribute, we are required to append `_Yo` to the front which would prevent conflicts with the same name attribute defined in any other class.
+ `+=` is faster than `+` for concatenating more than two strings because the first string (example, `s1` for `s1 += s2 + s3`) is not destroyed while calculating the complete string.
---
### > Let's make a giant string!
```py
def add_string_with_plus(iters):
s = ""
for i in range(iters):
s += "xyz"
assert len(s) == 3*iters
def add_bytes_with_plus(iters):
s = b""
for i in range(iters):
s += b"xyz"
assert len(s) == 3*iters
def add_string_with_format(iters):
fs = "{}"*iters
s = fs.format(*(["xyz"]*iters))
assert len(s) == 3*iters
def add_string_with_join(iters):
l = []
for i in range(iters):
l.append("xyz")
s = "".join(l)
assert len(s) == 3*iters
def convert_list_to_string(l, iters):
s = "".join(l)
assert len(s) == 3*iters
```
**Output:**
```py
>>> timeit(add_string_with_plus(10000))
1000 loops, best of 3: 972 µs per loop
>>> timeit(add_bytes_with_plus(10000))
1000 loops, best of 3: 815 µs per loop
>>> timeit(add_string_with_format(10000))
1000 loops, best of 3: 508 µs per loop
>>> timeit(add_string_with_join(10000))
1000 loops, best of 3: 878 µs per loop
>>> l = ["xyz"]*10000
>>> timeit(convert_list_to_string(l, 10000))
10000 loops, best of 3: 80 µs per loop
```
Let's increase the number of iterations by a factor of 10.
```py
>>> timeit(add_string_with_plus(100000)) # Linear increase in execution time
>>> timeit(add_string_with_format(100000)) # Linear increase
100 loops, best of 3: 5.25 ms per loop
>>> timeit(add_string_with_join(100000)) # Linear increase
100 loops, best of 3: 9.85 ms per loop
>>> l = ["xyz"]*100000
>>> timeit(convert_list_to_string(l, 100000)) # Linear increase
1000 loops, best of 3: 723 µs per loop
```
#### 💡 Explanation
- You can read more about [timeit](https://docs.python.org/3/library/timeit.html) from here. It is generally used to measure the execution time of snippets.
- Don't use `+` for generating long strings — In Python, `str` is immutable, so the left and right strings have to be copied into the new string for every pair of concatenations. If you concatenate four strings of length 10, you'll be copying (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90 characters instead of just 40 characters. Things get quadratically worse as the number and size of the string increases (justified with the execution times of `add_bytes_with_plus` function)
- Therefore, it's advised to use `.format.` or `%` syntax (however, they are slightly slower than `+` for short strings).
- Or better, if already you've contents available in the form of an iterable object, then use `''.join(iterable_object)` which is much faster.
-`add_string_with_plus` didn't show a quadratic increase in execution time unlike `add_bytes_with_plus` because of the `+=` optimizations discussed in the previous example. Had the statement been `s = s + "x" + "y" + "z"` instead of `s += "xyz"`, the increase would have been quadratic.
```py
def add_string_with_plus(iters):
s = ""
for i in range(iters):
s = s + "x" + "y" + "z"
assert len(s) == 3*iters
>>> timeit(add_string_with_plus(10000))
100 loops, best of 3: 9.87 ms per loop
>>> timeit(add_string_with_plus(100000)) # Quadratic increase in execution time
1 loops, best of 3: 1.09 s per loop
```
---
### > Explicit typecast of strings
```py
a = float('inf')
b = float('nan')
c = float('-iNf') #These strings are case-insensitive
d = float('nan')
```
**Output:**
```py
>>> a
inf
>>> b
nan
>>> c
-inf
>>> float('some_other_string')
ValueError: could not convert string to float: some_other_string
>>> a == -c #inf==inf
True
>>> None == None # None==None
True
>>> b == d #but nan!=nan
False
>>> 50/a
0.0
>>> a/a
nan
>>> 23 + b
nan
```
#### 💡 Explanation:
`'inf'` and `'nan'` are special strings (case-insensitive), which when explicitly typecasted to `float` type, are used to represent mathematical "infinity" and "not a number" respectively.
---
### > Minor Ones
*`join()` is a string operation instead of list operation. (sort of counter-intuitive at first usage)
**💡 Explanation:**
If `join()` is a method on a string then it can operate on any iterable (list, tuple, iterators). If it were a method on a list, it'd have to be implemented separately by every type. Also, it doesn't make much sense to put a string-specific method on a generic `list` object API.
* Few weird looking but semantically correct statements:
+ `[] = ()` is a semantically correct statement (unpacking an empty `tuple` into an empty `list`)
+ `'a'[0][0][0][0][0]` is also a semantically correct statement as strings are [sequences](https://docs.python.org/3/glossary.html#term-sequence)(iterables supporting element access using integer indices) in Python.
+ `3 --0-- 5 == 8` and `--5 == 5` are both semantically correct statements and evaluate to `True`.
* Given that `a` is a number, `++a` and `--a` are both valid Python statements but don't behave the same way as compared with similar statements in languages like C, C++ or Java.
```py
>>> a = 5
>>> a
5
>>> ++a
5
>>> --a
5
```
**💡 Explanation:**
+ There is no `++` operator in Python grammar. It is actually two `+` operators.
+ `++a` parses as `+(+a)` which translates to `a`. Similarly, the output of the statement `--a` can be justified.
+ This StackOverflow [thread](https://stackoverflow.com/questions/3654830/why-are-there-no-and-operators-in-python) discusses the rationale behind the absence of increment and decrement operators in Python.
* Python uses 2 bytes for local variable storage in functions. In theory, this means that only 65536 variables can be defined in a function. However, python has a handy solution built in that can be used to store more than 2^16 variable names. The following code demonstrates what happens in the stack when more than 65536 local variables are defined (Warning: This code prints around 2^18 lines of text, so be prepared!):
```py
import dis
exec("""
def f():
""" + """
""".join(["X"+str(x)+"=" + str(x) for x in range(65539)]))
f()
print(dis.dis(f))
```
* Multiple Python threads won't run your *Python code* concurrently (yes you heard it right!). It may seem intuitive to spawn several threads and let them execute your Python code concurrently, but, because of the [Global Interpreter Lock](https://wiki.python.org/moin/GlobalInterpreterLock) in Python, all you're doing is making your threads execute on the same core turn by turn. Python threads are good for IO-bound tasks, but to achieve actual parallelization in Python for CPU-bound tasks, you might want to use the Python [multiprocessing](https://docs.python.org/2/library/multiprocessing.html) module.
* List slicing with out of the bounds indices throws no errors
```py
>>> some_list = [1, 2, 3, 4, 5]
>>> some_list[111:]
[]
```
*`int('١٢٣٤٥٦٧٨٩')` returns `123456789` in Python 3. In Python, Decimal characters include digit characters, and all characters that can be used to form decimal-radix numbers, e.g. U+0660, ARABIC-INDIC DIGIT ZERO. Here's an [interesting story](http://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/) related to this behavior of Python.
*`'abc'.count('') == 4`. Here's an approximate implementation of `count` method, which would make the things more clear
```py
def count(s, sub):
result = 0
for i in range(len(s) + 1 - len(sub)):
result += (s[i:i + len(sub)] == sub)
return result
```
The behavior is due to the matching of empty substring(`''`) with slices of length 0 in the original string.
---
# Contributing
All patches are Welcome! Please see [CONTRIBUTING.md](/CONTRIBUTING.md) for further details.
For discussions, you can either create a new [issue](https://github.com/satwikkansal/wtfpython/issues/new) or ping on the Gitter [channel](https://gitter.im/wtfpython/Lobby)
# Acknowledgements
The idea and design for this collection were initially inspired by Denys Dovhan's awesome project [wtfjs](https://github.com/denysdovhan/wtfjs). The overwhelming support by the community gave it the shape it is in right now.
I've received a few requests for the pdf version of wtfpython. You can add your details [here](https://satwikkansal.xyz/wtfpython-pdf/) to get the pdf as soon as it is finished.