Skip to content

Commit a553ea0

Browse files
committed
Some meta tags and corrections after running on Python 3.8
1 parent d402186 commit a553ea0

File tree

1 file changed

+45
-29
lines changed

1 file changed

+45
-29
lines changed

README.md

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Python, being a beautifully designed high-level and interpreter-based programmin
99

1010
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.
1111

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

1414
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:
1515

@@ -175,7 +175,7 @@ For some reasons, "Walrus" operator (`:=`) has become a very popular feature in
175175
>>> a
176176
'wtf_walrus'
177177
>>> a := "wtf_walrus"
178-
File "<ipython-input-20-14e95425e0a2>", line 1
178+
File "<stdin>", line 1
179179
a := "wtf_walrus"
180180
^
181181
SyntaxError: invalid syntax
@@ -200,7 +200,7 @@ SyntaxError: invalid syntax
200200
>>> a, b
201201
(6, 9)
202202
>>> (a, b = 16, 19) # Oops
203-
File "<ipython-input-67-f4339673d0d4>", line 1
203+
File "<stdin>", line 1
204204
(a, b = 6, 9)
205205
^
206206
SyntaxError: invalid syntax
@@ -210,6 +210,8 @@ SyntaxError: invalid syntax
210210
211211
>>> a # a is still unchanged?
212212
6
213+
214+
>>> b
213215
16
214216
```
215217
@@ -308,7 +310,7 @@ False
308310
True
309311
310312
>>> 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)
312314
False
313315
```
314316
@@ -343,7 +345,7 @@ Makes sense, right?
343345
* 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)
344346
<img src="images/string-intern/string_intern.png" alt="">
345347
+ 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!"` is not 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`
347349
+ 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.
348350
+ 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).
349351
@@ -401,7 +403,7 @@ some_dict[5] = "Python"
401403
402404
```py
403405
>>> some_dict[5.5]
404-
"Ruby"
406+
"JavaScript"
405407
>>> some_dict[5.0] # "Python" destroyed the existence of "Ruby"?
406408
"Python"
407409
>>> some_dict[5]
@@ -726,10 +728,10 @@ array_2[:] = [1,2,3,4,5]
726728
**Output:**
727729
```py
728730
>>> print(list(gen_1))
729-
[1,2,3,4]
731+
[1, 2, 3, 4]
730732
731733
>>> print(list(gen_2))
732-
[1,2,3,4,5]
734+
[1, 2, 3, 4, 5]
733735
```
734736
735737
3\.
@@ -797,24 +799,18 @@ True
797799
3\.
798800
**Output**
799801
800-
```
802+
```py
801803
>>> a, b = 257, 257
802-
True
803-
804-
>>> a = 257; b = 257
805804
>>> a is b
806805
True
807806
```
808807
809808
**Output (Python 3.7.x specifically)**
810809
811-
```
810+
```py
812811
>>> a, b = 257, 257
812+
>> a is b
813813
False
814-
815-
>>> a = 257; b = 257
816-
>>> a is b
817-
True
818814
```
819815
820816
#### 💡 Explanation:
@@ -1176,6 +1172,10 @@ wtfpython
11761172
>>> # The following statements raise `SyntaxError`
11771173
>>> # print('''wtfpython')
11781174
>>> # print("""wtfpython")
1175+
File "<input>", line 3
1176+
print("""wtfpython")
1177+
^
1178+
SyntaxError: EOF while scanning triple-quoted string literal
11791179
```
11801180
11811181
#### 💡 Explanation:
@@ -1261,15 +1261,17 @@ for item in mixed_list:
12611261
3\.
12621262
12631263
```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!")
12671268
```
12681269
12691270
**Output (< 3.x):**
12701271
1271-
```
1272-
I've lost faith in truth!
1272+
```py
1273+
>>> tell_truth()
1274+
I have lost faith in truth!
12731275
```
12741276
12751277
@@ -1331,7 +1333,7 @@ class C(A):
13311333
>>> A.x, B.x, C.x
13321334
(1, 2, 1)
13331335
>>> 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
13351337
(3, 2, 3)
13361338
>>> a = A()
13371339
>>> a.x, A.x
@@ -1379,7 +1381,7 @@ True
13791381
13801382
---
13811383
1382-
### ▶ Non-reflexive class methods
1384+
### ▶ Non-reflexive class method
13831385
13841386
<!-- Example ID: 3649771a-f733-413c-8060-3f9f167b83fd -->
13851387
@@ -1430,7 +1432,7 @@ def some_func(val):
14301432
return "something"
14311433
```
14321434
1433-
**Output:**
1435+
**Output (<= 3.7.x):**
14341436
14351437
```py
14361438
>>> [x for x in some_iterable]
@@ -1449,6 +1451,7 @@ def some_func(val):
14491451
- This is bug in CPython's handling of `yield` in generators and comprehensions.
14501452
- Source and explanation can be found here: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions
14511453
- Related bug report: http://bugs.python.org/issue10544
1454+
- Python 3.8+ no longer allows `yield` inside list comprehension and will throw a `SyntaxError`.
14521455
14531456
---
14541457
@@ -1509,7 +1512,7 @@ True
15091512
15101513
- `'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.
15111514
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
15131516
15141517
```py
15151518
>>> x = float('nan')
@@ -1524,6 +1527,8 @@ True
15241527
15251528
Since the identities of `x` and `y` are different, the values are considered, which are also different, hence the comparison returns `False` this time.
15261529
1530+
- Interesting read: [Reflexivity, and other pilliars of civilization](https://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/)
1531+
15271532
---
15281533
15291534
### ▶ Mutating the immutable!
@@ -1565,6 +1570,8 @@ But I thought tuples were immutable...
15651570
15661571
### ▶ The disappearing variable from outer scope
15671572
<!-- Example ID: 7f1e71b6-cb3e-44fb-aa47-87ef1b7decc8 --->
1573+
<!-- version-specific: True -->
1574+
15681575
```py
15691576
e = 7
15701577
try:
@@ -1788,7 +1795,7 @@ The Subclass relationships were expected to be transitive, right? (i.e., if `A`
17881795
class SomeClass(str):
17891796
pass
17901797
1791-
some_dict = {'s':42}
1798+
some_dict = {'s': 42}
17921799
```
17931800
17941801
**Output:**
@@ -2014,6 +2021,7 @@ Shouldn't that be 100?
20142021
20152022
### ▶ Modifying a dictionary while iterating over it
20162023
<!-- Example ID: b4e5cdfb-c3a8-4112-bd38-e2356d801c41 --->
2024+
<!-- version-specific: True -->
20172025
```py
20182026
x = {0: None}
20192027
@@ -2044,11 +2052,14 @@ Yes, it runs for exactly **eight** times and stops.
20442052
* 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.
20452053
* How deleted keys are handled and when the resize occurs might be different for different Python implementations.
20462054
* 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.
20472056
20482057
---
20492058
20502059
### ▶ Stubborn `del` operation
20512060
<!-- Example ID: 777ed4fd-3a2d-466f-95e7-c4058e61d78e --->
2061+
<!-- read-only: True -->
2062+
20522063
```py
20532064
class SomeClass:
20542065
def __del__(self):
@@ -2152,6 +2163,7 @@ Can you guess why the output is `[2, 4]`?
21522163
21532164
### ▶ Loop variables leaking out!
21542165
<!-- Example ID: ccec7bf6-7679-4963-907a-1cd8587be9ea --->
2166+
<!-- version-specific: True -->
21552167
1\.
21562168
```py
21572169
for x in range(7):
@@ -2630,14 +2642,14 @@ def some_recursive_func(a):
26302642
if a[0] == 0:
26312643
return
26322644
a[0] -= 1
2633-
some_func(a)
2645+
some_recursive_func(a)
26342646
return a
26352647
26362648
def similar_recursive_func(a):
26372649
if a == 0:
26382650
return a
26392651
a -= 1
2640-
another_func()
2652+
similar_recursive_func(a)
26412653
return a
26422654
```
26432655
@@ -2694,6 +2706,8 @@ def similar_recursive_func(a):
26942706
26952707
### ▶ Wild imports
26962708
<!-- Example ID: 83deb561-bd55-4461-bb5e-77dd7f411e1c --->
2709+
<!-- read-only: True -->
2710+
26972711
```py
26982712
# File: module.py
26992713
@@ -2861,6 +2875,8 @@ Sshh.. It's a super secret.
28612875
28622876
### ▶ `goto`, but why?
28632877
<!-- Example ID: 2aff961e-7fa5-4986-a18a-9e5894bd89fe --->
2878+
<!-- version-specific: True -->
2879+
28642880
```py
28652881
from goto import goto, label
28662882
for i in range(9):

0 commit comments

Comments
 (0)