You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+45-29Lines changed: 45 additions & 29 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,7 +9,7 @@ Python, being a beautifully designed high-level and interpreter-based programmin
9
9
10
10
Here is a fun project attempting to explain what exactly is happening under the hood for some counter-intuitive snippets and lesser-known features in Python.
11
11
12
-
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!
12
+
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 beliereve that you'll find it interesting too!
13
13
14
14
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 have already experienced some of them before, and I might be able to revive sweet old memories of yours! :sweat_smile:
15
15
@@ -175,7 +175,7 @@ For some reasons, "Walrus" operator (`:=`) has become a very popular feature in
175
175
>>> a
176
176
'wtf_walrus'
177
177
>>> a :="wtf_walrus"
178
-
File "<ipython-input-20-14e95425e0a2>", line 1
178
+
File "<stdin>", line 1
179
179
a :="wtf_walrus"
180
180
^
181
181
SyntaxError: invalid syntax
@@ -200,7 +200,7 @@ SyntaxError: invalid syntax
200
200
>>> a, b
201
201
(6, 9)
202
202
>>> (a, b = 16, 19) # Oops
203
-
File "<ipython-input-67-f4339673d0d4>", line 1
203
+
File "<stdin>", line 1
204
204
(a, b = 6, 9)
205
205
^
206
206
SyntaxError: invalid syntax
@@ -210,6 +210,8 @@ SyntaxError: invalid syntax
210
210
211
211
>>> a # a is still unchanged?
212
212
6
213
+
214
+
>>> b
213
215
16
214
216
```
215
217
@@ -308,7 +310,7 @@ False
308
310
True
309
311
310
312
>>>a="wtf!"; b="wtf!"
311
-
>>> a is b
313
+
>>> a is b# This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
312
314
False
313
315
```
314
316
@@ -343,7 +345,7 @@ Makes sense, right?
343
345
* Strings that are not composed of ASCII letters, digits or underscores, are not interned. This explains why `'wtf!'` was not interned due to `!`. Cpython implementation of this rule can be found [here](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)
+ When `a`and`b` are set to `"wtf!"`in the same line, the Python interpreter creates a new object, then references the second variable at the same time. If you do it on separate lines, it doesn't "know" that there's already `wtf!`as an object (because `"wtf!"`isnot implicitly interned as per the facts mentioned above). It's an compile time optimization. This optimization doesn't apply to 3.7.x versions of CPython (check this [issue](https://github.com/satwikkansal/wtfpython/issues/100) for more discussion).
346
-
+ The compile unit in interactive environment consists of single statement, whereas it consists of the entire module in case of modules. `a, b = "wtf!", "wtf!"`is single statement, whereas `a = "wtf!"; b = "wtf!"` are 2 statements in single line. This explains why the identities are different in`a = "wtf!"; b = "wtf!"`, and also explain why they are same when invoked in`some_file.py`
348
+
+ The compile unit in interactive environment like ipython consists of single statement, whereas it consists of the entire module in case of modules. `a, b = "wtf!", "wtf!"`is single statement, whereas `a = "wtf!"; b = "wtf!"` are 2 statements in single line. This explains why the identities are different in`a = "wtf!"; b = "wtf!"`, and also explain why they are same when invoked in`some_file.py`
347
349
+ The abrupt change in output of the fourth snippet is due to a [peephole optimization](https://en.wikipedia.org/wiki/Peephole_optimization) technique known as Constant folding. This means the expression `'a'*20`is replaced by `'aaaaaaaaaaaaaaaaaaaa'` during compilation to save a few clock cycles during runtime. Constant folding only occurs for strings having length less than 20. (Why? Imagine the size of `.pyc`file generated as a result of the expression `'a'*10**10`). [Here's](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288) the implementation source for the same.
348
350
+ Note: In Python 3.7, Constant folding was moved out from peephole optimizer to the new AST optimizer with some change in logic as well, so the third snippet doesn't work for Python 3.7. You can read more about the change [here](https://bugs.python.org/issue11549).
349
351
@@ -401,7 +403,7 @@ some_dict[5] = "Python"
401
403
402
404
```py
403
405
>>> some_dict[5.5]
404
-
"Ruby"
406
+
"JavaScript"
405
407
>>> some_dict[5.0] # "Python" destroyed the existence of "Ruby"?
406
408
"Python"
407
409
>>> some_dict[5]
@@ -726,10 +728,10 @@ array_2[:] = [1,2,3,4,5]
726
728
**Output:**
727
729
```py
728
730
>>>print(list(gen_1))
729
-
[1,2,3,4]
731
+
[1,2, 3, 4]
730
732
731
733
>>>print(list(gen_2))
732
-
[1,2,3,4,5]
734
+
[1,2, 3, 4, 5]
733
735
```
734
736
735
737
3\.
@@ -797,24 +799,18 @@ True
797
799
3\.
798
800
**Output**
799
801
800
-
```
802
+
```py
801
803
>>> a, b=257, 257
802
-
True
803
-
804
-
>>>a=257; b=257
805
804
>>> a is b
806
805
True
807
806
```
808
807
809
808
**Output (Python 3.7.x specifically)**
810
809
811
-
```
810
+
```py
812
811
>>> a, b=257, 257
812
+
>> a is b
813
813
False
814
-
815
-
>>>a=257; b=257
816
-
>>> a is b
817
-
True
818
814
```
819
815
820
816
#### 💡 Explanation:
@@ -1176,6 +1172,10 @@ wtfpython
1176
1172
>>># The following statements raise `SyntaxError`
1177
1173
>>># print('''wtfpython')
1178
1174
>>># print("""wtfpython")
1175
+
File "<input>", line 3
1176
+
print("""wtfpython")
1177
+
^
1178
+
SyntaxError: EOF while scanning triple-quoted string literal
1179
1179
```
1180
1180
1181
1181
#### 💡 Explanation:
@@ -1261,15 +1261,17 @@ for item in mixed_list:
1261
1261
3\.
1262
1262
1263
1263
```py
1264
-
True = False
1265
-
if True == False:
1266
-
print("I've lost faith in truth!")
1264
+
def tell_truth():
1265
+
True = False
1266
+
if True == False:
1267
+
print("I have lost faith in truth!")
1267
1268
```
1268
1269
1269
1270
**Output (< 3.x):**
1270
1271
1271
-
```
1272
-
I've lost faith in truth!
1272
+
```py
1273
+
>>> tell_truth()
1274
+
I have lost faith in truth!
1273
1275
```
1274
1276
1275
1277
@@ -1331,7 +1333,7 @@ class C(A):
1331
1333
>>> A.x, B.x, C.x
1332
1334
(1, 2, 1)
1333
1335
>>> A.x = 3
1334
-
>>> A.x, B.x, C.x
1336
+
>>> A.x, B.x, C.x # C.x changed, but B.x didn't
1335
1337
(3, 2, 3)
1336
1338
>>> a = A()
1337
1339
>>> a.x, A.x
@@ -1379,7 +1381,7 @@ True
1379
1381
1380
1382
---
1381
1383
1382
-
### ▶ Non-reflexive class methods
1384
+
### ▶ Non-reflexive class method
1383
1385
1384
1386
<!-- Example ID: 3649771a-f733-413c-8060-3f9f167b83fd -->
1385
1387
@@ -1430,7 +1432,7 @@ def some_func(val):
1430
1432
return "something"
1431
1433
```
1432
1434
1433
-
**Output:**
1435
+
**Output (<= 3.7.x):**
1434
1436
1435
1437
```py
1436
1438
>>> [x for x in some_iterable]
@@ -1449,6 +1451,7 @@ def some_func(val):
1449
1451
- This is bug in CPython's handling of `yield` in generators and comprehensions.
1450
1452
- Source and explanation can be found here: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions
1451
1453
- Related bug report: http://bugs.python.org/issue10544
1454
+
- Python 3.8+ no longer allows `yield` inside list comprehension and will throw a `SyntaxError`.
1452
1455
1453
1456
---
1454
1457
@@ -1509,7 +1512,7 @@ True
1509
1512
1510
1513
- `'inf'` and `'nan'` are special strings (case-insensitive), which when explicitly typecast-ed to `float` type, are used to represent mathematical "infinity" and "not a number" respectively.
1511
1514
1512
-
- Since according to standards ` NaN != NaN`, implementing this breaks the reflexivity assumption of a collection element in Python i.e if `x` is a part of collection like `list`, the implementations like comparison are based on the assumption that `x == x`. Because of this assumption, the identity is compared first (since it's faster) whlie comparing two elements, and the values are compared only when the identities mismatch. Following snippet will make things clearer
1515
+
- Since according to IEEE standards ` NaN != NaN`, obeying this rule breaks the reflexivity assumption of a collection element in Python i.e if `x` is a part of collection like `list`, the implementations like comparison are based on the assumption that `x == x`. Because of this assumption, the identity is compared first (since it's faster) whlie comparing two elements, and the values are compared only when the identities mismatch. Following snippet will make things clearer
1513
1516
1514
1517
```py
1515
1518
>>> x = float('nan')
@@ -1524,6 +1527,8 @@ True
1524
1527
1525
1528
Since the identities of `x` and `y` are different, the values are considered, which are also different, hence the comparison returns `False` this time.
1526
1529
1530
+
- Interesting read: [Reflexivity, and other pilliars of civilization](https://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/)
1531
+
1527
1532
---
1528
1533
1529
1534
### ▶ Mutating the immutable!
@@ -1565,6 +1570,8 @@ But I thought tuples were immutable...
1565
1570
1566
1571
### ▶ The disappearing variable from outer scope
1567
1572
<!-- Example ID: 7f1e71b6-cb3e-44fb-aa47-87ef1b7decc8 --->
1573
+
<!-- version-specific: True -->
1574
+
1568
1575
```py
1569
1576
e = 7
1570
1577
try:
@@ -1788,7 +1795,7 @@ The Subclass relationships were expected to be transitive, right? (i.e., if `A`
1788
1795
class SomeClass(str):
1789
1796
pass
1790
1797
1791
-
some_dict = {'s':42}
1798
+
some_dict = {'s':42}
1792
1799
```
1793
1800
1794
1801
**Output:**
@@ -2014,6 +2021,7 @@ Shouldn't that be 100?
2014
2021
2015
2022
### ▶ Modifying a dictionary while iterating over it
2016
2023
<!-- Example ID: b4e5cdfb-c3a8-4112-bd38-e2356d801c41 --->
2024
+
<!-- version-specific: True -->
2017
2025
```py
2018
2026
x = {0: None}
2019
2027
@@ -2044,11 +2052,14 @@ Yes, it runs for exactly **eight** times and stops.
2044
2052
* It runs eight times because that's the point at which the dictionary resizes to hold more keys (we have eight deletion entries, so a resize is needed). This is actually an implementation detail.
2045
2053
* How deleted keys are handled and when the resize occurs might be different for different Python implementations.
2046
2054
* So for Python versions other than Python 2.7 - Python 3.5 the count might be different from 8 (but whatever the count is, it's going to be the same every time you run it). You can find some discussion around this [here](https://github.com/satwikkansal/wtfpython/issues/53) or in [this](https://stackoverflow.com/questions/44763802/bug-in-python-dict) StackOverflow thread.
2055
+
* Python 3.8 onwards, you'll see `RuntimeError: dictionary keys changed during iteration` exception if you try to do this.
2047
2056
2048
2057
---
2049
2058
2050
2059
### ▶ Stubborn `del` operation
2051
2060
<!-- Example ID: 777ed4fd-3a2d-466f-95e7-c4058e61d78e --->
2061
+
<!-- read-only: True -->
2062
+
2052
2063
```py
2053
2064
class SomeClass:
2054
2065
def __del__(self):
@@ -2152,6 +2163,7 @@ Can you guess why the output is `[2, 4]`?
2152
2163
2153
2164
### ▶ Loop variables leaking out!
2154
2165
<!-- Example ID: ccec7bf6-7679-4963-907a-1cd8587be9ea --->
0 commit comments