|
1 |
| -<p align="center"><img src="/images/logo.png" alt=""></p> |
| 1 | +<p align="center"><img src="images/logo.png" alt=""></p> |
2 | 2 | <h1 align="center">What the f*ck Python! 🐍</h1>
|
3 | 3 | <p align="center">An interesting collection of surprising snippets and lesser-known Python features.</p>
|
4 | 4 |
|
@@ -201,11 +201,10 @@ Makes sense, right?
|
201 | 201 | * All length 0 and length 1 strings are interned.
|
202 | 202 | * Strings are interned at compile time (`'wtf'` will be interned but `''.join(['w', 't', 'f']` will not be interned)
|
203 | 203 | * 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)
|
204 |
| - <img src="/images/string-intern/string_intern.png" alt=""> |
| 204 | + <img src="images/string-intern/string_intern.png" alt=""> |
205 | 205 | + 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 a compiler optimization and specifically applies to the interactive environment.
|
206 | 206 | + Constant folding is a technique for [peephole optimization](https://en.wikipedia.org/wiki/Peephole_optimization) in Python. This means the expression `'a'*20` is replaced by `'aaaaaaaaaaaaaaaaaaaa'` during compilation to reduce 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.
|
207 | 207 |
|
208 |
| -
|
209 | 208 | ---
|
210 | 209 |
|
211 | 210 | ### ▶ Time for some hash brownies!
|
@@ -246,26 +245,63 @@ some_dict[5] = "Python"
|
246 | 245 |
|
247 | 246 | ---
|
248 | 247 |
|
249 |
| -### ▶ Return return everywhere! |
| 248 | +### ▶ Keep trying? * |
250 | 249 |
|
251 | 250 | ```py
|
252 | 251 | def some_func():
|
253 | 252 | try:
|
254 | 253 | return 'from_try'
|
255 | 254 | finally:
|
256 | 255 | return 'from_finally'
|
| 256 | +
|
| 257 | +def another_func(): |
| 258 | + for _ in range(3): |
| 259 | + try: |
| 260 | + continue |
| 261 | + finally: |
| 262 | + print("Finally!") |
| 263 | +
|
| 264 | +def one_more_func(): # A gotcha! |
| 265 | + try: |
| 266 | + for i in range(3): |
| 267 | + try: |
| 268 | + 1 / i |
| 269 | + except ZeroDivisionError: |
| 270 | + # Let's throw it here and handle it outside for loop |
| 271 | + raise ZeroDivisionError("A trivial divide by zero error") |
| 272 | + finally: |
| 273 | + print("Iteration", i) |
| 274 | + break |
| 275 | + except ZeroDivisionError as e: |
| 276 | + print("Zero division error ocurred", e) |
257 | 277 | ```
|
258 | 278 |
|
259 | 279 | **Output:**
|
| 280 | +
|
260 | 281 | ```py
|
261 | 282 | >>> some_func()
|
262 | 283 | 'from_finally'
|
| 284 | +
|
| 285 | +>>> another_func() |
| 286 | +Finally! |
| 287 | +Finally! |
| 288 | +Finally! |
| 289 | +
|
| 290 | +>>> 1 / 0 |
| 291 | +Traceback (most recent call last): |
| 292 | + File "<stdin>", line 1, in <module> |
| 293 | +ZeroDivisionError: division by zero |
| 294 | +
|
| 295 | +>>> one_more_func() |
| 296 | +Iteration 0 |
| 297 | +
|
263 | 298 | ```
|
264 | 299 |
|
265 | 300 | #### 💡 Explanation:
|
266 | 301 |
|
267 | 302 | - When a `return`, `break` or `continue` statement is executed in the `try` suite of a "try…finally" statement, the `finally` clause is also executed ‘on the way out.
|
268 | 303 | - The return value of a function is determined by the last `return` statement executed. Since the `finally` clause always executes, a `return` statement executed in the `finally` clause will always be the last one executed.
|
| 304 | +- The caveat here is, if the finally clause executes a `return` or `break` statement, the temporarily saved exception is discarded. |
269 | 305 |
|
270 | 306 | ---
|
271 | 307 |
|
@@ -529,11 +565,11 @@ We didn't assign 3 "X"s or did we?
|
529 | 565 |
|
530 | 566 | When we initialize `row` variable, this visualization explains what happens in the memory
|
531 | 567 |
|
532 |
| - |
| 568 | + |
533 | 569 |
|
534 | 570 | And when the `board` is initialized by multiplying the `row`, this is what happens inside the memory (each of the elements `board[0]`, `board[1]` and `board[2]` is a reference to the same list referred by `row`)
|
535 | 571 |
|
536 |
| - |
| 572 | + |
537 | 573 |
|
538 | 574 | We can avoid this scenario here by not using `row` variable to generate `board`. (Asked in [this](https://github.com/satwikkansal/wtfpython/issues/68) issue).
|
539 | 575 |
|
@@ -1183,8 +1219,9 @@ a, b = a[b] = {}, 5
|
1183 | 1219 | (target_list "=")+ (expression_list | yield_expression)
|
1184 | 1220 | ```
|
1185 | 1221 | and
|
1186 |
| - > An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right. |
1187 |
| -
|
| 1222 | + |
| 1223 | +> An assignment statement evaluates the expression list (remember that this can be a single expression or a comma-separated list, the latter yielding a tuple) and assigns the single resulting object to each of the target lists, from left to right. |
| 1224 | + |
1188 | 1225 | * The `+` in `(target_list "=")+` means there can be **one or more** target lists. In this case, target lists are `a, b` and `a[b]` (note the expression list is exactly one, which in our case is `{}, 5`).
|
1189 | 1226 |
|
1190 | 1227 | * After the expression list is evaluated, it's value is unpacked to the target lists from **left to right**. So, in our case, first the `{}, 5` tuple is unpacked to `a, b` and we now have `a = {}` and `b = 5`.
|
@@ -1318,6 +1355,7 @@ Shouldn't that be 100?
|
1318 | 1355 |
|
1319 | 1356 | * **Don't mix tabs and spaces!** The character just preceding return is a "tab", and the code is indented by multiple of "4 spaces" elsewhere in the example.
|
1320 | 1357 | * This is how Python handles tabs:
|
| 1358 | + |
1321 | 1359 | > First, tabs are replaced (from left to right) by one to eight spaces such that the total number of characters up to and including the replacement is a multiple of eight <...>
|
1322 | 1360 | * So the "tab" at the last line of `square` function is replaced with eight spaces, and it gets into the loop.
|
1323 | 1361 | * Python 3 is kind enough to throw an error for such cases automatically.
|
@@ -1978,6 +2016,7 @@ There we go.
|
1978 | 2016 | #### 💡 Explanation:
|
1979 | 2017 | - 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).
|
1980 | 2018 | - Quoting from the PEP-401
|
| 2019 | + |
1981 | 2020 | > 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.
|
1982 | 2021 | - 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/).
|
1983 | 2022 |
|
|
0 commit comments