├── .gitattributes ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── LICENSE ├── README.md ├── code-of-conduct.md ├── images ├── logo.png ├── string-intern │ └── string_intern.png ├── tic-tac-toe.png └── tic-tac-toe │ ├── after_board_initialized.png │ └── after_row_initialized.png ├── irrelevant ├── add_categories ├── generate_contributions.py ├── generated.md ├── initial.md └── parse_readme.py ├── mixed_tabs_and_spaces.py ├── package.json ├── wtfpython └── wtfpython-pypi ├── content.md ├── setup.py ├── wtf_python ├── __init__.py └── main.py └── wtfpython /.gitattributes: -------------------------------------------------------------------------------- 1 | * linguist-vendored 2 | *.py linguist-vendored=false 3 | README.md linguist-language=Python 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | wtfpython-pypi/build/ 4 | wtfpython-pypi/dist/ 5 | wtfpython-pypi/wtfpython.egg-info 6 | 7 | # Python-specific byte-compiled files should be ignored 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | irrelevant/ 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | All kinds of patches are welcome. Feel free to even suggest some catchy and funny titles for the existing Examples. The goal is to make this collection as interesting to read as possible. 2 | 3 | If you are interested in translating the project to another language (some people have done that in the past), please feel free to open up an issue or ping on the [gitter channel](https://gitter.im/wtfpython/Lobby) if you need any kind of help. 4 | 5 | If the changes you suggest are significant, filing an issue before submitting the actual patch will be appreciated. If you'd like to work on the issue (highly encouraged), you can mention that you're interested in working on it while creating the issue and get assigned to it. 6 | 7 | If you're adding a new example, please do create an issue to discuss it before submitting a patch. 8 | 9 | You can use the following template for adding a new example: 10 | 11 |
12 | ### ▶ Some fancy Title *
13 | The asterisk at the end of the title indicates the example was not present in the first release and has been recently added.
14 | 
15 | ```py
16 | # Setting up the code.
17 | # Preparation for the magic...
18 | ```
19 | 
20 | **Output (Python version):**
21 | ```py
22 | >>> triggering_statement
23 | Probably unexpected output
24 | ```
25 | (Optional): One line describing the unexpected output.
26 | 
27 | #### 💡 Explanation:
28 | * Brief explanation of what's happening and why is it happening.
29 |   ```py
30 |   Setting up examples for clarification (if necessary)
31 |   ```
32 |   **Outupt:**
33 |   ```py
34 |   >>> trigger # some example that makes it easy to unveil the magic
35 |   # some justified output
36 |   ```
37 | ```
38 | 
39 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | I'm really grateful to all the contributors. Following are the wonderful people (in no specific order) who have contributed their examples to wtfpython. 2 | 3 | | Contributor | Github | Issues | 4 | |-------------|--------|--------| 5 | | Lucas-C | [Lucas-C](https://github.com/Lucas-C) | [#36](https:/github.com/satwikkansal/wtfpython/issues/36) | 6 | | MittalAshok | [MittalAshok](https://github.com/MittalAshok) | [#23](https:/github.com/satwikkansal/wtfpython/issues/23) | 7 | | asottile | [asottile](https://github.com/asottile) | [#40](https:/github.com/satwikkansal/wtfpython/issues/40) | 8 | | MostAwesomeDude | [MostAwesomeDude](https://github.com/MostAwesomeDude) | [#1](https:/github.com/satwikkansal/wtfpython/issues/1) | 9 | | tukkek | [tukkek](https://github.com/tukkek) | [#11](https:/github.com/satwikkansal/wtfpython/issues/11), [#26](https:/github.com/satwikkansal/wtfpython/issues/26) | 10 | | PiaFraus | [PiaFraus](https://github.com/PiaFraus) | [#9](https:/github.com/satwikkansal/wtfpython/issues/9) | 11 | | chris-rands | [chris-rands](https://github.com/chris-rands) | [#32](https:/github.com/satwikkansal/wtfpython/issues/32) | 12 | | sohaibfarooqi | [sohaibfarooqi](https://github.com/sohaibfarooqi) | [#63](https:/github.com/satwikkansal/wtfpython/issues/63) | 13 | 14 | Thank you all for taking out time, and helping to make this project awesome! :smile: 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2018 Satwik Kansal 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinkatyal13/wtfpython/2db8002197fe50101bf209dd7c09b73e61cb9930/images/logo.png -------------------------------------------------------------------------------- /images/string-intern/string_intern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinkatyal13/wtfpython/2db8002197fe50101bf209dd7c09b73e61cb9930/images/string-intern/string_intern.png -------------------------------------------------------------------------------- /images/tic-tac-toe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinkatyal13/wtfpython/2db8002197fe50101bf209dd7c09b73e61cb9930/images/tic-tac-toe.png -------------------------------------------------------------------------------- /images/tic-tac-toe/after_board_initialized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinkatyal13/wtfpython/2db8002197fe50101bf209dd7c09b73e61cb9930/images/tic-tac-toe/after_board_initialized.png -------------------------------------------------------------------------------- /images/tic-tac-toe/after_row_initialized.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinkatyal13/wtfpython/2db8002197fe50101bf209dd7c09b73e61cb9930/images/tic-tac-toe/after_row_initialized.png -------------------------------------------------------------------------------- /irrelevant/add_categories: -------------------------------------------------------------------------------- 1 | Skipping lines? 2 | a 3 | 4 | Well, something is fishy... 5 | a 6 | 7 | Time for some hash brownies! 8 | f 9 | 10 | Evaluation time discrepancy 11 | f 12 | 13 | Modifying a dictionary while iterating over it 14 | c 15 | 16 | Deleting a list item while iterating over it 17 | c 18 | 19 | Backslashes at the end of string 20 | f 21 | 22 | Brace yourself! 23 | t* 24 | 25 | "this" is love :heart: 26 | t* 27 | 28 | Okay Python, Can you make me fly? 29 | t* 30 | 31 | `goto`, but why? 32 | t* 33 | 34 | Let's meet Friendly Language Uncle For Life 35 | t* 36 | 37 | Inpinity 38 | t* 39 | 40 | Strings can be tricky sometimes 41 | f* 42 | 43 | `+=` is faster 44 | m 45 | 46 | Let's make a giant string! 47 | m 48 | 49 | Yes, it exists! 50 | t 51 | 52 | `is` is not what it is! 53 | f 54 | 55 | `is not ...` is not `is (not ...)` 56 | f 57 | 58 | The function inside loop sticks to the same output 59 | f 60 | 61 | Loop variables leaking out of local scope! 62 | c 63 | 64 | A tic-tac-toe where X wins in the first attempt! 65 | f 66 | 67 | Beware of default mutable arguments! 68 | c 69 | 70 | Same operands, different story! 71 | c 72 | 73 | Mutating the immutable! 74 | f 75 | 76 | Using a variable not defined in scope 77 | c 78 | 79 | The disappearing variable from outer scope 80 | f 81 | 82 | Return return everywhere! 83 | f 84 | 85 | When True is actually False 86 | f 87 | 88 | Be careful with chained operations 89 | c 90 | 91 | Name resolution ignoring class scope 92 | c 93 | 94 | From filled to None in one instruction... 95 | f 96 | 97 | Explicit typecast of strings 98 | m 99 | 100 | Class attributes and instance attributes 101 | f 102 | 103 | Catching the Exceptions! 104 | f 105 | 106 | Midnight time doesn't exist? 107 | f 108 | 109 | What's wrong with booleans? 110 | f 111 | 112 | Needle in a Haystack 113 | c 114 | 115 | Teleportation 116 | a* 117 | 118 | yielding None 119 | f 120 | 121 | The surprising comma 122 | f 123 | 124 | For what? 125 | f 126 | 127 | not knot! 128 | f 129 | 130 | Subclass relationships 131 | f* 132 | 133 | Mangling time! 134 | t* 135 | 136 | Deep down, we're all the same. 137 | f* 138 | 139 | Half triple-quoted strings 140 | f 141 | 142 | Implicity key type conversion 143 | f* 144 | 145 | Stubborn `del` operator 146 | c* 147 | 148 | Let's see if you can guess this? 149 | f 150 | 151 | Minor Ones 152 | m -------------------------------------------------------------------------------- /irrelevant/generate_contributions.py: -------------------------------------------------------------------------------- 1 | """ 2 | Parses the README.md and generated the table 3 | `CONTRIBUTORS.md`. 4 | """ 5 | 6 | import pprint 7 | import re 8 | import requests 9 | 10 | regex = ("[sS]uggested by @(\S+) in \[this\]\(https:\/\/github\.com\/satwikkansal" 11 | "\/wtf[pP]ython\/issues\/(\d+)\) issue") 12 | 13 | 14 | fname = "README.md" 15 | contribs = {} 16 | 17 | table_header = """ 18 | | Contributor | Github | Issues | 19 | |-------------|--------|--------| 20 | """ 21 | 22 | table_row = '| {} | [{}](https://github.com/{}) | {} |' 23 | issue_format = '[#{}](https:/github.com/satwikkansal/wtfpython/issues/{})' 24 | rows_so_far = [] 25 | 26 | github_rest_api = "https://api.github.com/users/{}" 27 | 28 | 29 | with open(fname, 'r') as f: 30 | file_content = f.read() 31 | matches = re.findall(regex, file_content) 32 | for match in matches: 33 | if contribs.get(match[0]) and match[1] not in contribs[match[0]]: 34 | contribs[match[0]].append(match[1]) 35 | else: 36 | contribs[match[0]] = [match[1]] 37 | 38 | for handle, issues in contribs.items(): 39 | issue_string = ', '.join([issue_format.format(i, i) for i in issues]) 40 | resp = requests.get(github_rest_api.format(handle)) 41 | name = handle 42 | if resp.status_code is 200: 43 | pprint.pprint(resp.json()['name']) 44 | else: 45 | print(handle, resp.content) 46 | rows_so_far.append(table_row.format(name, 47 | handle, 48 | handle, 49 | issue_string)) 50 | 51 | print(table_header + "\n".join(rows_so_far)) -------------------------------------------------------------------------------- /irrelevant/generated.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinkatyal13/wtfpython/2db8002197fe50101bf209dd7c09b73e61cb9930/irrelevant/generated.md -------------------------------------------------------------------------------- /irrelevant/initial.md: -------------------------------------------------------------------------------- 1 |

What the f*ck Python? 🐍

2 |

An interesting collection of subtle and tricky Python Snippets.

3 | 4 | [![WTFPL 2.0][license-image]][license-url] 5 | 6 | 7 | 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. 8 | 9 | Here is a fun project attempting to collect such classic & tricky examples of unexpected behaviors and lesser known features in Python, and discuss what exactly is happening under the hood! 10 | 11 | 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! 12 | 13 | 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: 14 | 15 | So, here we go... 16 | 17 | # Table of Contents 18 | 19 | 20 | 21 | - [Structure of the Examples](#structure-of-the-examples) 22 | - [Usage](#usage) 23 | - [👀 Examples](#-examples) 24 | - [Skipping lines?](#skipping-lines) 25 | - [💡 Explanation](#-explanation) 26 | - [Well, something is fishy...](#well-something-is-fishy) 27 | - [💡 Explanation](#-explanation-1) 28 | - [Time for some hash brownies!](#time-for-some-hash-brownies) 29 | - [💡 Explanation](#-explanation-2) 30 | - [Evaluation time discrepancy](#evaluation-time-discrepancy) 31 | - [💡 Explanation](#-explanation-3) 32 | - [Modifying a dictionary while iterating over it](#modifying-a-dictionary-while-iterating-over-it) 33 | - [💡 Explanation:](#-explanation) 34 | - [Deleting a list item while iterating over it](#deleting-a-list-item-while-iterating-over-it) 35 | - [💡 Explanation:](#-explanation-1) 36 | - [Backslashes at the end of string](#backslashes-at-the-end-of-string) 37 | - [💡 Explanation](#-explanation-4) 38 | - [Strings can be tricky sometimes](#strings-can-be-tricky-sometimes) 39 | - [💡 Explanation:](#-explanation-2) 40 | - [`+=` is faster](#-is-faster) 41 | - [💡 Explanation:](#-explanation-3) 42 | - [Let's make a giant string!](#lets-make-a-giant-string) 43 | - [💡 Explanation](#-explanation-5) 44 | - [Yes, it exists!](#yes-it-exists) 45 | - [💡 Explanation:](#-explanation-4) 46 | - [`is` is not what it is!](#is-is-not-what-it-is) 47 | - [💡 Explanation:](#-explanation-5) 48 | - [`is not ...` is not `is (not ...)`](#is-not--is-not-is-not-) 49 | - [💡 Explanation](#-explanation-6) 50 | - [The function inside loop sticks to the same output](#the-function-inside-loop-sticks-to-the-same-output) 51 | - [💡 Explanation](#-explanation-7) 52 | - [Loop variables leaking out of local scope!](#loop-variables-leaking-out-of-local-scope) 53 | - [💡 Explanation:](#-explanation-6) 54 | - [A tic-tac-toe where X wins in the first attempt!](#a-tic-tac-toe-where-x-wins-in-the-first-attempt) 55 | - [💡 Explanation:](#-explanation-7) 56 | - [Beware of default mutable arguments!](#beware-of-default-mutable-arguments) 57 | - [💡 Explanation:](#-explanation-8) 58 | - [Same operands, different story!](#same-operands-different-story) 59 | - [💡 Explanation:](#-explanation-9) 60 | - [Mutating the immutable!](#mutating-the-immutable) 61 | - [💡 Explanation:](#-explanation-10) 62 | - [Using a variable not defined in scope](#using-a-variable-not-defined-in-scope) 63 | - [💡 Explanation:](#-explanation-11) 64 | - [The disappearing variable from outer scope](#the-disappearing-variable-from-outer-scope) 65 | - [💡 Explanation:](#-explanation-12) 66 | - [Return return everywhere!](#return-return-everywhere) 67 | - [💡 Explanation:](#-explanation-13) 68 | - [When True is actually False](#when-true-is-actually-false) 69 | - [💡 Explanation:](#-explanation-14) 70 | - [Be careful with chained operations](#be-careful-with-chained-operations) 71 | - [💡 Explanation:](#-explanation-15) 72 | - [Name resolution ignoring class scope](#name-resolution-ignoring-class-scope) 73 | - [💡 Explanation](#-explanation-8) 74 | - [From filled to None in one instruction...](#from-filled-to-none-in-one-instruction) 75 | - [💡 Explanation](#-explanation-9) 76 | - [Explicit typecast of strings](#explicit-typecast-of-strings) 77 | - [💡 Explanation:](#-explanation-16) 78 | - [Class attributes and instance attributes](#class-attributes-and-instance-attributes) 79 | - [💡 Explanation:](#-explanation-17) 80 | - [Catching the Exceptions!](#catching-the-exceptions) 81 | - [💡 Explanation](#-explanation-10) 82 | - [Midnight time doesn't exist?](#midnight-time-doesnt-exist) 83 | - [💡 Explanation:](#-explanation-18) 84 | - [What's wrong with booleans?](#whats-wrong-with-booleans) 85 | - [💡 Explanation:](#-explanation-19) 86 | - [Needle in a Haystack](#needle-in-a-haystack) 87 | - [💡 Explanation:](#-explanation-20) 88 | - [Teleportation](#teleportation) 89 | - [💡 Explanation:](#-explanation-21) 90 | - [yielding None](#yielding-none) 91 | - [💡 Explanation:](#-explanation-22) 92 | - [The surprising comma](#the-surprising-comma) 93 | - [💡 Explanation:](#-explanation-23) 94 | - [For what?](#for-what) 95 | - [💡 Explanation:](#-explanation-24) 96 | - [not knot!](#not-knot) 97 | - [💡 Explanation:](#-explanation-25) 98 | - [Subclass relationships](#subclass-relationships) 99 | - [💡 Explanation:](#-explanation-26) 100 | - [Mangling time!](#mangling-time) 101 | - [💡 Explanation:](#-explanation-27) 102 | - [Deep down, we're all the same.](#deep-down-were-all-the-same) 103 | - [💡 Explanation:](#-explanation-28) 104 | - [Half triple-quoted strings](#half-triple-quoted-strings) 105 | - [💡 Explanation:](#-explanation-29) 106 | - [Implicity key type conversion](#implicity-key-type-conversion) 107 | - [💡 Explanation:](#-explanation-30) 108 | - [Stubborn `del` operator](#stubborn-del-operator) 109 | - [💡 Explanation:](#-explanation-31) 110 | - [Let's see if you can guess this?](#lets-see-if-you-can-guess-this) 111 | - [💡 Explanation:](#-explanation-32) 112 | - [Minor Ones](#minor-ones) 113 | - [TODO: Hell of an example!](#todo-hell-of-an-example) 114 | - [Contributing](#contributing) 115 | - [Acknowledgements](#acknowledgements) 116 | - [Some nice Links!](#some-nice-links) 117 | - [🎓 License](#-license) 118 | - [Help](#help) 119 | 120 | 121 | 122 | # Structure of the Examples 123 | 124 | All the examples are structured like below: 125 | 126 | > ### ▶ Some fancy Title * 127 | > The asterisk at the end of the title indicates the example was not present in the first release and has been recently added. 128 | > 129 | > ```py 130 | > # Setting up the code. 131 | > # Preparation for the magic... 132 | > ``` 133 | > 134 | > **Output (Python version):** 135 | > ```py 136 | > >>> triggering_statement 137 | > Probably unexpected output 138 | > ``` 139 | > (Optional): One line describing the unexpected output. 140 | > 141 | > 142 | > #### 💡 Explanation: 143 | > 144 | > * Brief explanation of what's happening and why is it happening. 145 | > ```py 146 | > Setting up examples for clarification (if necessary) 147 | > ``` 148 | > **Output:** 149 | > ```py 150 | > >>> trigger # some example that makes it easy to unveil the magic 151 | > # some justified output 152 | > ``` 153 | 154 | **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. 155 | 156 | # Usage 157 | 158 | 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: 159 | - 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. 160 | - Read the output snippets and, 161 | + Check if the outputs are the same as you'd expect. 162 | + Make sure if you know the exact reason behind the output being the way it is. 163 | - 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)). 164 | - If yes, give a gentle pat on your back, and you may skip to the next example. 165 | 166 | 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. 167 | 168 | To install the npm package [`wtfpython`](https://www.npmjs.com/package/wtfpython) 169 | ```sh 170 | $ npm install -g wtfpython 171 | ``` 172 | 173 | Alternatively, to install the pypi package [`wtfpython`](https://pypi.python.org/pypi/wtfpython) 174 | ```sh 175 | $ pip install wtfpython -U 176 | ``` 177 | 178 | Now, just run `wtfpython` at the command line which will open this collection in your selected `$PAGER`. 179 | 180 | 181 | # 👀 Examples 182 | 183 | ### Skipping lines? 184 | 185 | **Output:** 186 | ```py 187 | >>> value = 11 188 | >>> valuе = 32 189 | >>> value 190 | 11 191 | ``` 192 | 193 | Wut? 194 | 195 | **Note:** The easiest way to reproduce this is to simply copy the statements from the above snippet and paste them into your file/shell. 196 | 197 | #### 💡 Explanation 198 | 199 | Some non-Western characters look identical to letters in the English alphabet but are considered distinct by the interpreter. 200 | 201 | ```py 202 | >>> ord('е') # cyrillic 'e' (Ye) 203 | 1077 204 | >>> ord('e') # latin 'e', as used in English and typed using standard keyboard 205 | 101 206 | >>> 'е' == 'e' 207 | False 208 | 209 | >>> value = 42 # latin e 210 | >>> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here 211 | >>> value 212 | 42 213 | ``` 214 | 215 | The built-in `ord()` function returns a character's Unicode [code point](https://en.wikipedia.org/wiki/Code_point), and different code positions of Cyrillic 'e' and Latin 'e' justify the behavior of the above example. 216 | 217 | --- 218 | 219 | ### Well, something is fishy... 220 | 221 | ```py 222 | def square(x): 223 | """ 224 | A simple function to calculate the square of a number by addition. 225 | """ 226 | sum_so_far = 0 227 | for counter in range(x): 228 | sum_so_far = sum_so_far + x 229 | return sum_so_far 230 | ``` 231 | 232 | **Output (Python 2.x):** 233 | 234 | ```py 235 | >>> square(10) 236 | 10 237 | ``` 238 | 239 | Shouldn't that be 100? 240 | 241 | **Note:** If you're not able to reproduce this, try running the file [mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py) via the shell. 242 | 243 | #### 💡 Explanation 244 | 245 | * **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. 246 | * This is how Python handles tabs: 247 | > 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 <...> 248 | * So the "tab" at the last line of `square` function is replaced with eight spaces, and it gets into the loop. 249 | * Python 3 is kind enough to throw an error for such cases automatically. 250 | 251 | **Output (Python 3.x):** 252 | ```py 253 | TabError: inconsistent use of tabs and spaces in indentation 254 | ``` 255 | 256 | --- 257 | 258 | ### Time for some hash brownies! 259 | 260 | 1\. 261 | ```py 262 | some_dict = {} 263 | some_dict[5.5] = "Ruby" 264 | some_dict[5.0] = "JavaScript" 265 | some_dict[5] = "Python" 266 | ``` 267 | 268 | **Output:** 269 | ```py 270 | >>> some_dict[5.5] 271 | "Ruby" 272 | >>> some_dict[5.0] 273 | "Python" 274 | >>> some_dict[5] 275 | "Python" 276 | ``` 277 | 278 | "Python" destroyed the existence of "JavaScript"? 279 | 280 | 281 | #### 💡 Explanation 282 | 283 | * Python dictionaries check for equality and compare the hash value to determine if two keys are the same. 284 | * Immutable objects with same value always have the same hash in Python. 285 | ```py 286 | >>> 5 == 5.0 287 | True 288 | >>> hash(5) == hash(5.0) 289 | True 290 | ``` 291 | **Note:** Objects with different values may also have same hash (known as hash collision). 292 | * When the statement `some_dict[5] = "Python"` is executed, the existing value "JavaScript" is overwritten with "Python" because Python recongnizes `5` and `5.0` as the same keys of the dictionary `some_dict`. 293 | * This StackOverflow [answer](https://stackoverflow.com/a/32211042/4354153) explains beautifully the rationale behind it. 294 | 295 | --- 296 | 297 | ### Evaluation time discrepancy 298 | 299 | ```py 300 | array = [1, 8, 15] 301 | g = (x for x in array if array.count(x) > 0) 302 | array = [2, 8, 22] 303 | ``` 304 | 305 | **Output:** 306 | ```py 307 | >>> print(list(g)) 308 | [8] 309 | ``` 310 | 311 | #### 💡 Explanation 312 | 313 | - In a [generator](https://wiki.python.org/moin/Generators) expression, the `in` clause is evaluated at declaration time, but the conditional clause is evaluated at runtime. 314 | - So before runtime, `array` is re-assigned to the list `[2, 8, 22]`, and since out of `1`, `8` and `15`, only the count of `8` is greater than `0`, the generator only yields `8`. 315 | 316 | --- 317 | 318 | ### Modifying a dictionary while iterating over it 319 | 320 | ```py 321 | x = {0: None} 322 | 323 | for i in x: 324 | del x[i] 325 | x[i+1] = None 326 | print(i) 327 | ``` 328 | 329 | **Output (Python 2.7- Python 3.5):** 330 | 331 | ``` 332 | 0 333 | 1 334 | 2 335 | 3 336 | 4 337 | 5 338 | 6 339 | 7 340 | ``` 341 | 342 | Yes, it runs for exactly **eight** times and stops. 343 | 344 | #### 💡 Explanation: 345 | 346 | * Iteration over a dictionary that you edit at the same time is not supported. 347 | * 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. 348 | * How deleted keys are handled and when the resize occurs might be different for different Python implementations. 349 | * For more information, you may refer to this StackOverflow [thread](https://stackoverflow.com/questions/44763802/bug-in-python-dict) explaining a similar example in detail. 350 | 351 | --- 352 | 353 | ### Deleting a list item while iterating over it 354 | 355 | ```py 356 | list_1 = [1, 2, 3, 4] 357 | list_2 = [1, 2, 3, 4] 358 | list_3 = [1, 2, 3, 4] 359 | list_4 = [1, 2, 3, 4] 360 | 361 | for idx, item in enumerate(list_1): 362 | del item 363 | 364 | for idx, item in enumerate(list_2): 365 | list_2.remove(item) 366 | 367 | for idx, item in enumerate(list_3[:]): 368 | list_3.remove(item) 369 | 370 | for idx, item in enumerate(list_4): 371 | list_4.pop(idx) 372 | ``` 373 | 374 | **Output:** 375 | ```py 376 | >>> list_1 377 | [1, 2, 3, 4] 378 | >>> list_2 379 | [2, 4] 380 | >>> list_3 381 | [] 382 | >>> list_4 383 | [2, 4] 384 | ``` 385 | 386 | Can you guess why the output is `[2, 4]`? 387 | 388 | #### 💡 Explanation: 389 | 390 | * It's never a good idea to change the object you're iterating over. The correct way to do so is to iterate over a copy of the object instead, and `list_3[:]` does just that. 391 | 392 | ```py 393 | >>> some_list = [1, 2, 3, 4] 394 | >>> id(some_list) 395 | 139798789457608 396 | >>> id(some_list[:]) # Notice that python creates new object for sliced list. 397 | 139798779601192 398 | ``` 399 | 400 | 401 | **Difference between `del`, `remove`, and `pop`:** 402 | * `del var_name` just removes the binding of the `var_name` from the local or global namespace (That's why the `list_1` is unaffected). 403 | * `remove` removes the first matching value, not a specific index, raises `ValueError` if the value is not found. 404 | * `pop` removes the element at a specific index and returns it, raises `IndexError` if an invalid index is specified. 405 | 406 | **Why the output is `[2, 4]`?** 407 | - The list iteration is done index by index, and when we remove `1` from `list_2` or `list_4`, the contents of the lists are now `[2, 3, 4]`. The remaining elements are shifted down, i.e., `2` is at index 0, and `3` is at index 1. Since the next iteration is going to look at index 1 (which is the `3`), the `2` gets skipped entirely. A similar thing will happen with every alternate element in the list sequence. 408 | 409 | * Refer to this StackOverflow [thread](https://stackoverflow.com/questions/45946228/what-happens-when-you-try-to-delete-a-list-element-while-iterating-over-it) explaining the example 410 | * See also this nice StackOverflow [thread](https://stackoverflow.com/questions/45877614/how-to-change-all-the-dictionary-keys-in-a-for-loop-with-d-items) for a similar example related to dictionaries in Python. 411 | 412 | --- 413 | 414 | ### Backslashes at the end of string 415 | 416 | **Output:** 417 | ``` 418 | >>> print("\\ C:\\") 419 | \ C:\ 420 | >>> print(r"\ C:") 421 | \ C: 422 | >>> print(r"\ C:\") 423 | 424 | File "", line 1 425 | print(r"\ C:\") 426 | ^ 427 | SyntaxError: EOL while scanning string literal 428 | ``` 429 | 430 | #### 💡 Explanation 431 | 432 | - In a raw string literal, as indicated by the prefix `r`, the backslash doesn't have the special meaning. 433 | ```py 434 | >>> print(repr(r"wt\"f")) 435 | 'wt\\"f' 436 | ``` 437 | - What the interpreter actually does, though, is simply change the behavior of backslashes, so they pass themselves and the following character through. That's why backslashes don't work at the end of a raw string. 438 | 439 | --- 440 | 441 | ### Brace yourself! 442 | 443 | 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, 444 | 445 | ```py 446 | from __future__ import braces 447 | ``` 448 | 449 | **Output:** 450 | ```py 451 | File "some_file.py", line 1 452 | from __future__ import braces 453 | SyntaxError: not a chance 454 | ``` 455 | 456 | Braces? No way! If you think that's disappointing, use Java. 457 | 458 | #### 💡 Explanation: 459 | + The `__future__` module is normally used to provide features from future versions of Python. The "future" here is however ironic. 460 | + This is an easter egg concerned with the community's feelings on this issue. 461 | 462 | --- 463 | 464 | ### "this" is love :heart: 465 | 466 | ```py 467 | import this 468 | ``` 469 | 470 | Wait, what's **this**? 471 | 472 | **Output:** 473 | ``` 474 | The Zen of Python, by Tim Peters 475 | 476 | Beautiful is better than ugly. 477 | Explicit is better than implicit. 478 | Simple is better than complex. 479 | Complex is better than complicated. 480 | Flat is better than nested. 481 | Sparse is better than dense. 482 | Readability counts. 483 | Special cases aren't special enough to break the rules. 484 | Although practicality beats purity. 485 | Errors should never pass silently. 486 | Unless explicitly silenced. 487 | In the face of ambiguity, refuse the temptation to guess. 488 | There should be one-- and preferably only one --obvious way to do it. 489 | Although that way may not be obvious at first unless you're Dutch. 490 | Now is better than never. 491 | Although never is often better than *right* now. 492 | If the implementation is hard to explain, it's a bad idea. 493 | If the implementation is easy to explain, it may be a good idea. 494 | Namespaces are one honking great idea -- let's do more of those! 495 | ``` 496 | 497 | It's the Zen of Python! 498 | 499 | ```py 500 | >>> love = this 501 | >>> this is love 502 | True 503 | >>> love is True 504 | False 505 | >>> love is False 506 | False 507 | >>> love is not True or False 508 | True 509 | >>> love is not True or False; love is love # Love is complicated 510 | True 511 | ``` 512 | 513 | #### 💡 Explanation: 514 | 515 | * `this` module in Python is an easter egg for The Zen Of Python ([PEP 20](https://www.python.org/dev/peps/pep-0020)). 516 | * 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). 517 | * Regarding the statement `love is not True or False; love is love`, ironic but it's self-explanatory. 518 | 519 | --- 520 | 521 | ### Okay Python, Can you make me fly? 522 | 523 | Well, here you go 524 | 525 | ```py 526 | import antigravity 527 | ``` 528 | 529 | **Output:** 530 | Sshh.. It's a super secret. 531 | 532 | #### 💡 Explanation: 533 | + `antigravity` module is an easter egg. 534 | + `import antigravity` opens up a web browser pointing to the [classic XKCD comic](http://xkcd.com/353/) about Python. 535 | + Well, there's more to it. There's **another easter egg inside the easter egg**. If look at the [code](https://github.com/python/cpython/blob/master/Lib/antigravity.py#L7-L17), there's a function defined that purports to implement the [XKCD's geohashing algorithm](https://xkcd.com/426/). 536 | 537 | --- 538 | 539 | ### `goto`, but why? 540 | 541 | ```py 542 | from goto import goto, label 543 | for i in range(9): 544 | for j in range(9): 545 | for k in range(9): 546 | print("I'm trapped, please rescue!") 547 | if k == 2: 548 | goto .breakout # breaking out from a deeply nested loop 549 | label .breakout 550 | print("Freedom!") 551 | ``` 552 | 553 | **Output (Python 2.3):** 554 | ```py 555 | I'm trapped, please rescue! 556 | I'm trapped, please rescue! 557 | Freedom! 558 | ``` 559 | 560 | #### 💡 Explanation: 561 | - 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. 562 | - Current versions of Python do not have this module. 563 | - 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. 564 | 565 | --- 566 | 567 | ### Let's meet Friendly Language Uncle For Life 568 | 569 | **Output (Python 3.x)** 570 | ```py 571 | >>> from __future__ import barry_as_FLUFL 572 | >>> "Ruby" != "Python" # there's no doubt about it 573 | File "some_file.py", line 1 574 | "Ruby" != "Python" 575 | ^ 576 | SyntaxError: invalid syntax 577 | 578 | >>> "Ruby" <> "Python" 579 | True 580 | ``` 581 | 582 | There we go. 583 | 584 | #### 💡 Explanation: 585 | - 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). 586 | - Quoting from the PEP-401 587 | 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. 588 | - 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/). 589 | 590 | --- 591 | 592 | ### Inpinity 593 | 594 | The spelling is intended. Please, don't submit a patch for this. 595 | 596 | **Output (Python 3.x):** 597 | ```py 598 | >>> infinity = float('infinity') 599 | >>> hash(infinity) 600 | 314159 601 | >>> hash(float('-inf')) 602 | -314159 603 | ``` 604 | 605 | #### 💡 Explanation: 606 | - Hash of infinity is 10⁵ x π. 607 | - Interestingly, the hash of `float('-inf')` is "-10⁵ x π" in Python 3, whereas "-10⁵ x e" in Python 2. 608 | 609 | --- 610 | 611 | ### Strings can be tricky sometimes 612 | 613 | 1\. 614 | ```py 615 | >>> a = "some_string" 616 | >>> id(a) 617 | 140420665652016 618 | >>> id("some" + "_" + "string") # Notice that both the ids are same. 619 | 140420665652016 620 | ``` 621 | 622 | 2\. 623 | ```py 624 | >>> a = "wtf" 625 | >>> b = "wtf" 626 | >>> a is b 627 | True 628 | 629 | >>> a = "wtf!" 630 | >>> b = "wtf!" 631 | >>> a is b 632 | False 633 | 634 | >>> a, b = "wtf!", "wtf!" 635 | >>> a is b 636 | True 637 | ``` 638 | 639 | 3\. 640 | ```py 641 | >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' 642 | True 643 | >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' 644 | False 645 | ``` 646 | 647 | Makes sense, right? 648 | 649 | #### 💡 Explanation: 650 | + Such behavior is due to CPython optimization (called string interning) that tries to use existing immutable objects in some cases rather than creating a new object every time. 651 | + After being interned, many variables may point to the same string object in memory (thereby saving memory). 652 | + In the snippets above, strings are implicitly interned. The decision of when to implicitly intern a string is implementation dependent. There are some facts that can be used to guess if a string will be interned or not: 653 | * All length 0 and length 1 strings are interned. 654 | * Strings are interned at compile time (`'wtf'` will be interned but `''.join(['w', 't', 'f']` will not be interned) 655 | * Strings that are not composed of ASCII letters, digits or underscores, are not interned. This explains why `'wtf!'` was not interned due to `!`. 656 | + 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. 657 | 658 | --- 659 | 660 | ### `+=` is faster 661 | 662 | ```py 663 | # using "+", three strings: 664 | >>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100) 665 | 0.25748300552368164 666 | # using "+=", three strings: 667 | >>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100) 668 | 0.012188911437988281 669 | ``` 670 | 671 | #### 💡 Explanation: 672 | + `+=` 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. 673 | 674 | --- 675 | 676 | ### Let's make a giant string! 677 | 678 | ```py 679 | def add_string_with_plus(iters): 680 | s = "" 681 | for i in range(iters): 682 | s += "xyz" 683 | assert len(s) == 3*iters 684 | 685 | def add_bytes_with_plus(iters): 686 | s = b"" 687 | for i in range(iters): 688 | s += b"xyz" 689 | assert len(s) == 3*iters 690 | 691 | def add_string_with_format(iters): 692 | fs = "{}"*iters 693 | s = fs.format(*(["xyz"]*iters)) 694 | assert len(s) == 3*iters 695 | 696 | def add_string_with_join(iters): 697 | l = [] 698 | for i in range(iters): 699 | l.append("xyz") 700 | s = "".join(l) 701 | assert len(s) == 3*iters 702 | 703 | def convert_list_to_string(l, iters): 704 | s = "".join(l) 705 | assert len(s) == 3*iters 706 | ``` 707 | 708 | **Output:** 709 | ```py 710 | >>> timeit(add_string_with_plus(10000)) 711 | 1000 loops, best of 3: 972 µs per loop 712 | >>> timeit(add_bytes_with_plus(10000)) 713 | 1000 loops, best of 3: 815 µs per loop 714 | >>> timeit(add_string_with_format(10000)) 715 | 1000 loops, best of 3: 508 µs per loop 716 | >>> timeit(add_string_with_join(10000)) 717 | 1000 loops, best of 3: 878 µs per loop 718 | >>> l = ["xyz"]*10000 719 | >>> timeit(convert_list_to_string(l, 10000)) 720 | 10000 loops, best of 3: 80 µs per loop 721 | ``` 722 | 723 | Let's increase the number of iterations by a factor of 10. 724 | 725 | ```py 726 | >>> timeit(add_string_with_plus(100000)) # Linear increase in execution time 727 | 100 loops, best of 3: 9.75 ms per loop 728 | >>> timeit(add_bytes_with_plus(100000)) # Quadratic increase 729 | 1000 loops, best of 3: 974 ms per loop 730 | >>> timeit(add_string_with_format(100000)) # Linear increase 731 | 100 loops, best of 3: 5.25 ms per loop 732 | >>> timeit(add_string_with_join(100000)) # Linear increase 733 | 100 loops, best of 3: 9.85 ms per loop 734 | >>> l = ["xyz"]*100000 735 | >>> timeit(convert_list_to_string(l, 100000)) # Linear increase 736 | 1000 loops, best of 3: 723 µs per loop 737 | ``` 738 | 739 | #### 💡 Explanation 740 | - 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. 741 | - 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) 742 | - Therefore, it's advised to use `.format.` or `%` syntax (however, they are slightly slower than `+` for short strings). 743 | - Or better, if already you've contents available in the form of an iterable object, then use `''.join(iterable_object)` which is much faster. 744 | - `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. 745 | ```py 746 | def add_string_with_plus(iters): 747 | s = "" 748 | for i in range(iters): 749 | s = s + "x" + "y" + "z" 750 | assert len(s) == 3*iters 751 | 752 | >>> timeit(add_string_with_plus(10000)) 753 | 100 loops, best of 3: 9.87 ms per loop 754 | >>> timeit(add_string_with_plus(100000)) # Quadratic increase in execution time 755 | 1 loops, best of 3: 1.09 s per loop 756 | ``` 757 | 758 | --- 759 | 760 | ### Yes, it exists! 761 | 762 | **The `else` clause for loops.** One typical example might be: 763 | 764 | ```py 765 | def does_exists_num(l, to_find): 766 | for num in l: 767 | if num == to_find: 768 | print("Exists!") 769 | break 770 | else: 771 | print("Does not exist") 772 | ``` 773 | 774 | **Output:** 775 | ```py 776 | >>> some_list = [1, 2, 3, 4, 5] 777 | >>> does_exists_num(some_list, 4) 778 | Exists! 779 | >>> does_exists_num(some_list, -1) 780 | Does not exist 781 | ``` 782 | 783 | **The `else` clause in exception handling.** An example, 784 | 785 | ```py 786 | try: 787 | pass 788 | except: 789 | print("Exception occurred!!!") 790 | else: 791 | print("Try block executed successfully...") 792 | ``` 793 | 794 | **Output:** 795 | ```py 796 | Try block executed successfully... 797 | ``` 798 | 799 | #### 💡 Explanation: 800 | - The `else` clause after a loop is executed only when there's no explicit `break` after all the iterations. 801 | - `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. 802 | 803 | --- 804 | 805 | ### `is` is not what it is! 806 | 807 | The following is a very famous example present all over the internet. 808 | 809 | ```py 810 | >>> a = 256 811 | >>> b = 256 812 | >>> a is b 813 | True 814 | 815 | >>> a = 257 816 | >>> b = 257 817 | >>> a is b 818 | False 819 | 820 | >>> a = 257; b = 257 821 | >>> a is b 822 | True 823 | ``` 824 | 825 | 826 | #### 💡 Explanation: 827 | 828 | **The difference between `is` and `==`** 829 | 830 | * `is` operator checks if both the operands refer to the same object (i.e., it checks if the identity of the operands matches or not). 831 | * `==` operator compares the values of both the operands and checks if they are the same. 832 | * So `is` is for reference equality and `==` is for value equality. An example to clear things up, 833 | ```py 834 | >>> [] == [] 835 | True 836 | >>> [] is [] # These are two empty lists at two different memory locations. 837 | False 838 | ``` 839 | 840 | **`256` is an existing object but `257` isn't** 841 | 842 | 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. 843 | 844 | Quoting from https://docs.python.org/3/c-api/long.html 845 | > 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. :-) 846 | 847 | ```py 848 | >>> id(256) 849 | 10922528 850 | >>> a = 256 851 | >>> b = 256 852 | >>> id(a) 853 | 10922528 854 | >>> id(b) 855 | 10922528 856 | >>> id(257) 857 | 140084850247312 858 | >>> x = 257 859 | >>> y = 257 860 | >>> id(x) 861 | 140084850247440 862 | >>> id(y) 863 | 140084850247344 864 | ``` 865 | 866 | 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. 867 | 868 | 869 | **Both `a` and `b` refer to the same object when initialized with same value in the same line.** 870 | 871 | ```py 872 | >>> a, b = 257, 257 873 | >>> id(a) 874 | 140640774013296 875 | >>> id(b) 876 | 140640774013296 877 | >>> a = 257 878 | >>> b = 257 879 | >>> id(a) 880 | 140640774013392 881 | >>> id(b) 882 | 140640774013488 883 | ``` 884 | 885 | 886 | * When a and b are set to `257` 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 `257` as an object. 887 | * It's a compiler optimization and specifically applies to the interactive environment. When you enter two lines in a live interpreter, they're compiled separately, therefore optimized separately. If you were to try this example in a `.py` file, you would not see the same behavior, because the file is compiled all at once. 888 | 889 | --- 890 | 891 | ### `is not ...` is not `is (not ...)` 892 | 893 | ```py 894 | >>> 'something' is not None 895 | True 896 | >>> 'something' is (not None) 897 | False 898 | ``` 899 | 900 | #### 💡 Explanation 901 | 902 | - `is not` is a single binary operator, and has behavior different than using `is` and `not` separated. 903 | - `is not` evaluates to `False` if the variables on either side of the operator point to the same object and `True` otherwise. 904 | 905 | --- 906 | 907 | ### The function inside loop sticks to the same output 908 | 909 | ```py 910 | funcs = [] 911 | results = [] 912 | for x in range(7): 913 | def some_func(): 914 | return x 915 | funcs.append(some_func) 916 | results.append(some_func()) 917 | 918 | funcs_results = [func() for func in funcs] 919 | ``` 920 | 921 | **Output:** 922 | ```py 923 | >>> results 924 | [0, 1, 2, 3, 4, 5, 6] 925 | >>> funcs_results 926 | [6, 6, 6, 6, 6, 6, 6] 927 | ``` 928 | Even when the values of `x` were different in every iteration prior to appending `some_func` to `funcs`, all the functions return 6. 929 | 930 | //OR 931 | 932 | ```py 933 | >>> powers_of_x = [lambda x: x**i for i in range(10)] 934 | >>> [f(2) for f in powers_of_x] 935 | [512, 512, 512, 512, 512, 512, 512, 512, 512, 512] 936 | ``` 937 | 938 | #### 💡 Explanation 939 | 940 | - When defining a function inside a loop that uses the loop variable in its body, the loop function's closure is bound to the variable, not its value. So all of the functions use the latest value assigned to the variable for computation. 941 | 942 | - To get the desired behavior you can pass in the loop variable as a named variable to the function. **Why this works?** Because this will define the variable again within the function's scope. 943 | 944 | ```py 945 | funcs = [] 946 | for x in range(7): 947 | def some_func(x=x): 948 | return x 949 | funcs.append(some_func) 950 | ``` 951 | 952 | **Output:** 953 | ```py 954 | >>> funcs_results = [func() for func in funcs] 955 | >>> funcs_results 956 | [0, 1, 2, 3, 4, 5, 6] 957 | ``` 958 | 959 | 960 | --- 961 | 962 | ### Loop variables leaking out of local scope! 963 | 964 | 1\. 965 | ```py 966 | for x in range(7): 967 | if x == 6: 968 | print(x, ': for x inside loop') 969 | print(x, ': x in global') 970 | ``` 971 | 972 | **Output:** 973 | ```py 974 | 6 : for x inside loop 975 | 6 : x in global 976 | ``` 977 | 978 | But `x` was never defined outside the scope of for loop... 979 | 980 | 2\. 981 | ```py 982 | # This time let's initialize x first 983 | x = -1 984 | for x in range(7): 985 | if x == 6: 986 | print(x, ': for x inside loop') 987 | print(x, ': x in global') 988 | ``` 989 | 990 | **Output:** 991 | ```py 992 | 6 : for x inside loop 993 | 6 : x in global 994 | ``` 995 | 996 | 3\. 997 | ``` 998 | x = 1 999 | print([x for x in range(5)]) 1000 | print(x, ': x in global') 1001 | ``` 1002 | 1003 | **Output (on Python 2.x):** 1004 | ``` 1005 | [0, 1, 2, 3, 4] 1006 | (4, ': x in global') 1007 | ``` 1008 | 1009 | **Output (on Python 3.x):** 1010 | ``` 1011 | [0, 1, 2, 3, 4] 1012 | 1 : x in global 1013 | ``` 1014 | 1015 | #### 💡 Explanation: 1016 | 1017 | - In Python, for-loops use the scope they exist in and leave their defined loop-variable behind. This also applies if we explicitly defined the for-loop variable in the global namespace before. In this case, it will rebind the existing variable. 1018 | 1019 | - The differences in the output of Python 2.x and Python 3.x interpreters for list comprehension example can be explained by following change documented in [What’s New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html) documentation: 1020 | 1021 | > "List comprehensions no longer support the syntactic form `[... for var in item1, item2, ...]`. Use `[... for var in (item1, item2, ...)]` instead. Also, note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a `list()` constructor, and in particular the loop control variables are no longer leaked into the surrounding scope." 1022 | 1023 | 1024 | --- 1025 | 1026 | ### A tic-tac-toe where X wins in the first attempt! 1027 | 1028 | ```py 1029 | # Let's initialize a row 1030 | row = [""]*3 #row i['', '', ''] 1031 | # Let's make a board 1032 | board = [row]*3 1033 | ``` 1034 | 1035 | **Output:** 1036 | ```py 1037 | >>> board 1038 | [['', '', ''], ['', '', ''], ['', '', '']] 1039 | >>> board[0] 1040 | ['', '', ''] 1041 | >>> board[0][0] 1042 | '' 1043 | >>> board[0][0] = "X" 1044 | >>> board 1045 | [['X', '', ''], ['X', '', ''], ['X', '', '']] 1046 | ``` 1047 | 1048 | We didn't assign 3 "X"s or did we? 1049 | 1050 | #### 💡 Explanation: 1051 | 1052 | When we initialize `row` variable, this visualization explains what happens in the memory 1053 | 1054 | ![image](/images/tic-tac-toe/after_row_initialized.png) 1055 | 1056 | 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`) 1057 | 1058 | ![image](/images/tic-tac-toe/after_board_initialized.png) 1059 | 1060 | --- 1061 | 1062 | ### Beware of default mutable arguments! 1063 | 1064 | ```py 1065 | def some_func(default_arg=[]): 1066 | default_arg.append("some_string") 1067 | return default_arg 1068 | ``` 1069 | 1070 | **Output:** 1071 | ```py 1072 | >>> some_func() 1073 | ['some_string'] 1074 | >>> some_func() 1075 | ['some_string', 'some_string'] 1076 | >>> some_func([]) 1077 | ['some_string'] 1078 | >>> some_func() 1079 | ['some_string', 'some_string', 'some_string'] 1080 | ``` 1081 | 1082 | #### 💡 Explanation: 1083 | 1084 | - The default mutable arguments of functions in Python aren't really initialized every time you call the function. Instead, the recently assigned value to them is used as the default value. When we explicitly passed `[]` to `some_func` as the argument, the default value of the `default_arg` variable was not used, so the function returned as expected. 1085 | 1086 | ```py 1087 | def some_func(default_arg=[]): 1088 | default_arg.append("some_string") 1089 | return default_arg 1090 | ``` 1091 | 1092 | **Output:** 1093 | ```py 1094 | >>> some_func.__defaults__ #This will show the default argument values for the function 1095 | ([],) 1096 | >>> some_func() 1097 | >>> some_func.__defaults__ 1098 | (['some_string'],) 1099 | >>> some_func() 1100 | >>> some_func.__defaults__ 1101 | (['some_string', 'some_string'],) 1102 | >>> some_func([]) 1103 | >>> some_func.__defaults__ 1104 | (['some_string', 'some_string'],) 1105 | ``` 1106 | 1107 | - A common practice to avoid bugs due to mutable arguments is to assign `None` as the default value and later check if any value is passed to the function corresponding to that argument. Example: 1108 | 1109 | ```py 1110 | def some_func(default_arg=None): 1111 | if not default_arg: 1112 | default_arg = [] 1113 | default_arg.append("some_string") 1114 | return default_arg 1115 | ``` 1116 | 1117 | --- 1118 | 1119 | ### Same operands, different story! 1120 | 1121 | 1\. 1122 | ```py 1123 | a = [1, 2, 3, 4] 1124 | b = a 1125 | a = a + [5, 6, 7, 8] 1126 | ``` 1127 | 1128 | **Output:** 1129 | ```py 1130 | >>> a 1131 | [1, 2, 3, 4, 5, 6, 7, 8] 1132 | >>> b 1133 | [1, 2, 3, 4] 1134 | ``` 1135 | 1136 | 2\. 1137 | ```py 1138 | a = [1, 2, 3, 4] 1139 | b = a 1140 | a += [5, 6, 7, 8] 1141 | ``` 1142 | 1143 | **Output:** 1144 | ```py 1145 | >>> a 1146 | [1, 2, 3, 4, 5, 6, 7, 8] 1147 | >>> b 1148 | [1, 2, 3, 4, 5, 6, 7, 8] 1149 | ``` 1150 | 1151 | #### 💡 Explanation: 1152 | 1153 | * `a += b` doesn't always behave the same way as `a = a + b`. Classes *may* implement the *`op=`* operators differently, and lists do this. 1154 | 1155 | * The expression `a = a + [5,6,7,8]` generates a new list and sets `a`'s reference to that new list, leaving `b` unchanged. 1156 | 1157 | * The expression `a + =[5,6,7,8]` is actually mapped to an "extend" function that operates on the list such that `a` and `b` still point to the same list that has been modified in-place. 1158 | 1159 | --- 1160 | 1161 | ### Mutating the immutable! 1162 | 1163 | ```py 1164 | some_tuple = ("A", "tuple", "with", "values") 1165 | another_tuple = ([1, 2], [3, 4], [5, 6]) 1166 | ``` 1167 | 1168 | **Output:** 1169 | ```py 1170 | >>> some_tuple[2] = "change this" 1171 | TypeError: 'tuple' object does not support item assignment 1172 | >>> another_tuple[2].append(1000) #This throws no error 1173 | >>> another_tuple 1174 | ([1, 2], [3, 4], [5, 6, 1000]) 1175 | >>> another_tuple[2] += [99, 999] 1176 | TypeError: 'tuple' object does not support item assignment 1177 | >>> another_tuple 1178 | ([1, 2], [3, 4], [5, 6, 1000, 99, 999]) 1179 | ``` 1180 | 1181 | But I thought tuples were immutable... 1182 | 1183 | #### 💡 Explanation: 1184 | 1185 | * Quoting from https://docs.python.org/2/reference/datamodel.html 1186 | 1187 | > Immutable sequences 1188 | An object of an immutable sequence type cannot change once it is created. (If the object contains references to other objects, these other objects may be mutable and may be modified; however, the collection of objects directly referenced by an immutable object cannot change.) 1189 | 1190 | * `+=` operator changes the list in-place. The item assignment doesn't work, but when the exception occurs, the item has already been changed in place. 1191 | 1192 | --- 1193 | 1194 | ### Using a variable not defined in scope 1195 | 1196 | ```py 1197 | a = 1 1198 | def some_func(): 1199 | return a 1200 | 1201 | def another_func(): 1202 | a += 1 1203 | return a 1204 | ``` 1205 | 1206 | **Output:** 1207 | ```py 1208 | >>> some_func() 1209 | 1 1210 | >>> another_func() 1211 | UnboundLocalError: local variable 'a' referenced before assignment 1212 | ``` 1213 | 1214 | #### 💡 Explanation: 1215 | * When you make an assignment to a variable in scope, it becomes local to that scope. So `a` becomes local to the scope of `another_func`, but it has not been initialized previously in the same scope which throws an error. 1216 | * Read [this](http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html) short but an awesome guide to learn more about how namespaces and scope resolution works in Python. 1217 | * To modify the outer scope variable `a` in `another_func`, use `global` keyword. 1218 | ```py 1219 | def another_func() 1220 | global a 1221 | a += 1 1222 | return a 1223 | ``` 1224 | 1225 | **Output:** 1226 | ```py 1227 | >>> another_func() 1228 | 2 1229 | ``` 1230 | 1231 | --- 1232 | 1233 | ### The disappearing variable from outer scope 1234 | 1235 | ```py 1236 | e = 7 1237 | try: 1238 | raise Exception() 1239 | except Exception as e: 1240 | pass 1241 | ``` 1242 | 1243 | **Output (Python 2.x):** 1244 | ```py 1245 | >>> print(e) 1246 | # prints nothing 1247 | ``` 1248 | 1249 | **Output (Python 3.x):** 1250 | ```py 1251 | >>> print(e) 1252 | NameError: name 'e' is not defined 1253 | ``` 1254 | 1255 | #### 💡 Explanation: 1256 | 1257 | * Source: https://docs.python.org/3/reference/compound_stmts.html#except 1258 | 1259 | When an exception has been assigned using `as` target, it is cleared at the end of the except clause. This is as if 1260 | 1261 | ```py 1262 | except E as N: 1263 | foo 1264 | ``` 1265 | 1266 | was translated into 1267 | 1268 | ```py 1269 | except E as N: 1270 | try: 1271 | foo 1272 | finally: 1273 | del N 1274 | ``` 1275 | 1276 | This means the exception must be assigned to a different name to be able to refer to it after the except clause. Exceptions are cleared because, with the traceback attached to them, they form a reference cycle with the stack frame, keeping all locals in that frame alive until the next garbage collection occurs. 1277 | 1278 | * The clauses are not scoped in Python. Everything in the example is present in the same scope, and the variable `e` got removed due to the execution of the `except` clause. The same is not the case with functions which have their separate inner-scopes. The example below illustrates this: 1279 | 1280 | ```py 1281 | def f(x): 1282 | del(x) 1283 | print(x) 1284 | 1285 | x = 5 1286 | y = [5, 4, 3] 1287 | ``` 1288 | 1289 | **Output:** 1290 | ```py 1291 | >>>f(x) 1292 | UnboundLocalError: local variable 'x' referenced before assignment 1293 | >>>f(y) 1294 | UnboundLocalError: local variable 'x' referenced before assignment 1295 | >>> x 1296 | 5 1297 | >>> y 1298 | [5, 4, 3] 1299 | ``` 1300 | 1301 | * In Python 2.x the variable name `e` gets assigned to `Exception()` instance, so when you try to print, it prints nothing. 1302 | 1303 | **Output (Python 2.x):** 1304 | ```py 1305 | >>> e 1306 | Exception() 1307 | >>> print e 1308 | # Nothing is printed! 1309 | ``` 1310 | 1311 | 1312 | --- 1313 | 1314 | ### Return return everywhere! 1315 | 1316 | ```py 1317 | def some_func(): 1318 | try: 1319 | return 'from_try' 1320 | finally: 1321 | return 'from_finally' 1322 | ``` 1323 | 1324 | **Output:** 1325 | ```py 1326 | >>> some_func() 1327 | 'from_finally' 1328 | ``` 1329 | 1330 | #### 💡 Explanation: 1331 | 1332 | - 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. 1333 | - 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. 1334 | 1335 | --- 1336 | 1337 | ### When True is actually False 1338 | 1339 | ```py 1340 | True = False 1341 | if True == False: 1342 | print("I've lost faith in truth!") 1343 | ``` 1344 | 1345 | **Output:** 1346 | ``` 1347 | I've lost faith in truth! 1348 | ``` 1349 | 1350 | #### 💡 Explanation: 1351 | 1352 | - Initially, Python used to have no `bool` type (people used 0 for false and non-zero value like 1 for true). Then they added `True`, `False`, and a `bool` type, but, for backward compatibility, they couldn't make `True` and `False` constants- they just were built-in variables. 1353 | - Python 3 was backward-incompatible, so it was now finally possible to fix that, and so this example won't work with Python 3.x! 1354 | 1355 | --- 1356 | 1357 | ### Be careful with chained operations 1358 | 1359 | ```py 1360 | >>> (False == False) in [False] # makes sense 1361 | False 1362 | >>> False == (False in [False]) # makes sense 1363 | False 1364 | >>> False == False in [False] # now what? 1365 | True 1366 | 1367 | 1368 | >>> True is False == False 1369 | False 1370 | >>> False is False is False 1371 | True 1372 | 1373 | 1374 | >>> 1 > 0 < 1 1375 | True 1376 | >>> (1 > 0) < 1 1377 | False 1378 | >>> 1 > (0 < 1) 1379 | False 1380 | ``` 1381 | 1382 | #### 💡 Explanation: 1383 | 1384 | As per https://docs.python.org/2/reference/expressions.html#not-in 1385 | 1386 | > 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. 1387 | 1388 | 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`. 1389 | 1390 | * `False is False is False` is equivalent to `(False is False) and (False is False)` 1391 | * `True is False == False` is equivalent to `True is False and False == False` and since the first part of the statement (`True is False`) evaluates to `False`, the overall expression evaluates to `False`. 1392 | * `1 > 0 < 1` is equivalent to `1 > 0 and 0 < 1` which evaluates to `True`. 1393 | * The expression `(1 > 0) < 1` is equivalent to `True < 1` and 1394 | ```py 1395 | >>> int(True) 1396 | 1 1397 | >>> True + 1 #not relevant for this example, but just for fun 1398 | 2 1399 | ``` 1400 | So, `1 < 1` evaluates to `False` 1401 | 1402 | 1403 | --- 1404 | 1405 | ### Name resolution ignoring class scope 1406 | 1407 | 1\. 1408 | ```py 1409 | x = 5 1410 | class SomeClass: 1411 | x = 17 1412 | y = (x for i in range(10)) 1413 | ``` 1414 | 1415 | **Output:** 1416 | ```py 1417 | >>> list(SomeClass.y)[0] 1418 | 5 1419 | ``` 1420 | 1421 | 2\. 1422 | ```py 1423 | x = 5 1424 | class SomeClass: 1425 | x = 17 1426 | y = [x for i in range(10)] 1427 | ``` 1428 | 1429 | **Output (Python 2.x):** 1430 | ```py 1431 | >>> SomeClass.y[0] 1432 | 17 1433 | ``` 1434 | 1435 | **Output (Python 3.x):** 1436 | ```py 1437 | >>> SomeClass.y[0] 1438 | 5 1439 | ``` 1440 | 1441 | #### 💡 Explanation 1442 | - Scopes nested inside class definition ignore names bound at the class level. 1443 | - A generator expression has its own scope. 1444 | - Starting from Python 3.X, list comprehensions also have their own scope. 1445 | 1446 | --- 1447 | 1448 | ### From filled to None in one instruction... 1449 | 1450 | ```py 1451 | some_list = [1, 2, 3] 1452 | some_dict = { 1453 | "key_1": 1, 1454 | "key_2": 2, 1455 | "key_3": 3 1456 | } 1457 | 1458 | some_list = some_list.append(4) 1459 | some_dict = some_dict.update({"key_4": 4}) 1460 | ``` 1461 | 1462 | **Output:** 1463 | ```py 1464 | >>> print(some_list) 1465 | None 1466 | >>> print(some_dict) 1467 | None 1468 | ``` 1469 | 1470 | #### 💡 Explanation 1471 | 1472 | Most methods that modify the items of sequence/mapping objects like `list.append`, `dict.update`, `list.sort`, etc. modify the objects in-place and return `None`. The rationale behind this is to improve performance by avoiding making a copy of the object if the operation can be done in-place (Referred from [here](http://docs.python.org/2/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list)) 1473 | 1474 | --- 1475 | 1476 | ### Explicit typecast of strings 1477 | 1478 | ```py 1479 | a = float('inf') 1480 | b = float('nan') 1481 | c = float('-iNf') #These strings are case-insensitive 1482 | d = float('nan') 1483 | ``` 1484 | 1485 | **Output:** 1486 | ```py 1487 | >>> a 1488 | inf 1489 | >>> b 1490 | nan 1491 | >>> c 1492 | -inf 1493 | >>> float('some_other_string') 1494 | ValueError: could not convert string to float: some_other_string 1495 | >>> a == -c #inf==inf 1496 | True 1497 | >>> None == None # None==None 1498 | True 1499 | >>> b == d #but nan!=nan 1500 | False 1501 | >>> 50/a 1502 | 0.0 1503 | >>> a/a 1504 | nan 1505 | >>> 23 + b 1506 | nan 1507 | ``` 1508 | 1509 | #### 💡 Explanation: 1510 | 1511 | `'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. 1512 | 1513 | --- 1514 | 1515 | ### Class attributes and instance attributes 1516 | 1517 | 1\. 1518 | ```py 1519 | class A: 1520 | x = 1 1521 | 1522 | class B(A): 1523 | pass 1524 | 1525 | class C(A): 1526 | pass 1527 | ``` 1528 | 1529 | **Ouptut:** 1530 | ```py 1531 | >>> A.x, B.x, C.x 1532 | (1, 1, 1) 1533 | >>> B.x = 2 1534 | >>> A.x, B.x, C.x 1535 | (1, 2, 1) 1536 | >>> A.x = 3 1537 | >>> A.x, B.x, C.x 1538 | (3, 2, 3) 1539 | >>> a = A() 1540 | >>> a.x, A.x 1541 | (3, 3) 1542 | >>> a.x += 1 1543 | >>> a.x, A.x 1544 | (4, 3) 1545 | ``` 1546 | 1547 | 2\. 1548 | ```py 1549 | class SomeClass: 1550 | some_var = 15 1551 | some_list = [5] 1552 | another_list = [5] 1553 | def __init__(self, x): 1554 | self.some_var = x + 1 1555 | self.some_list = self.some_list + [x] 1556 | self.another_list += [x] 1557 | ``` 1558 | 1559 | **Output:** 1560 | 1561 | ```py 1562 | >>> some_obj = SomeClass(420) 1563 | >>> some_obj.some_list 1564 | [5, 420] 1565 | >>> some_obj.another_list 1566 | [5, 420] 1567 | >>> another_obj = SomeClass(111) 1568 | >>> another_obj.some_list 1569 | [5, 111] 1570 | >>> another_obj.another_list 1571 | [5, 420, 111] 1572 | >>> another_obj.another_list is SomeClass.another_list 1573 | True 1574 | >>> another_obj.another_list is some_obj.another_list 1575 | True 1576 | ``` 1577 | 1578 | 1579 | #### 💡 Explanation: 1580 | 1581 | * Class variables and variables in class instances are internally handled as dictionaries of a class object. If a variable name is not found in the dictionary of the current class, the parent classes are searched for it. 1582 | * The `+=` operator modifies the mutable object in-place without creating a new object. So changing the attribute of one instance affects the other instances and the class attribute as well. 1583 | 1584 | --- 1585 | 1586 | ### Catching the Exceptions! 1587 | 1588 | ```py 1589 | some_list = [1, 2, 3] 1590 | try: 1591 | # This should raise an ``IndexError`` 1592 | print(some_list[4]) 1593 | except IndexError, ValueError: 1594 | print("Caught!") 1595 | 1596 | try: 1597 | # This should raise a ``ValueError`` 1598 | some_list.remove(4) 1599 | except IndexError, ValueError: 1600 | print("Caught again!") 1601 | ``` 1602 | 1603 | **Output (Python 2.x):** 1604 | ```py 1605 | Caught! 1606 | 1607 | ValueError: list.remove(x): x not in list 1608 | ``` 1609 | 1610 | **Output (Python 3.x):** 1611 | ```py 1612 | File "", line 3 1613 | except IndexError, ValueError: 1614 | ^ 1615 | SyntaxError: invalid syntax 1616 | ``` 1617 | 1618 | #### 💡 Explanation 1619 | 1620 | * To add multiple Exceptions to the except clause, you need to pass them as parenthesized tuple as the first argument. The second argument is an optional name, which when supplied will bind the Exception instance that has been raised. Example, 1621 | ```py 1622 | some_list = [1, 2, 3] 1623 | try: 1624 | # This should raise a ``ValueError`` 1625 | some_list.remove(4) 1626 | except (IndexError, ValueError), e: 1627 | print("Caught again!") 1628 | print(e) 1629 | ``` 1630 | **Output (Python 2.x):** 1631 | ``` 1632 | Caught again! 1633 | list.remove(x): x not in list 1634 | ``` 1635 | **Output (Python 3.x):** 1636 | ```py 1637 | File "", line 4 1638 | except (IndexError, ValueError), e: 1639 | ^ 1640 | IndentationError: unindent does not match any outer indentation level 1641 | ``` 1642 | 1643 | * Separating the exception from the variable with a comma is deprecated and does not work in Python 3; the correct way is to use `as`. Example, 1644 | ```py 1645 | some_list = [1, 2, 3] 1646 | try: 1647 | some_list.remove(4) 1648 | 1649 | except (IndexError, ValueError) as e: 1650 | print("Caught again!") 1651 | print(e) 1652 | ``` 1653 | **Output:** 1654 | ``` 1655 | Caught again! 1656 | list.remove(x): x not in list 1657 | ``` 1658 | 1659 | --- 1660 | 1661 | ### Midnight time doesn't exist? 1662 | 1663 | ```py 1664 | from datetime import datetime 1665 | 1666 | midnight = datetime(2018, 1, 1, 0, 0) 1667 | midnight_time = midnight.time() 1668 | 1669 | noon = datetime(2018, 1, 1, 12, 0) 1670 | noon_time = noon.time() 1671 | 1672 | if midnight_time: 1673 | print("Time at midnight is", midnight_time) 1674 | 1675 | if noon_time: 1676 | print("Time at noon is", noon_time) 1677 | ``` 1678 | 1679 | **Output:** 1680 | ```sh 1681 | ('Time at noon is', datetime.time(12, 0)) 1682 | ``` 1683 | The midnight time is not printed. 1684 | 1685 | #### 💡 Explanation: 1686 | 1687 | Before Python 3.5, the boolean value for `datetime.time` object was considered to be `False` if it represented midnight in UTC. It is error-prone when using the `if obj:` syntax to check if the `obj` is null or some equivalent of "empty." 1688 | 1689 | --- 1690 | 1691 | ### What's wrong with booleans? 1692 | 1693 | 1\. 1694 | ```py 1695 | # A simple example to count the number of boolean and 1696 | # integers in an iterable of mixed data types. 1697 | mixed_list = [False, 1.0, "some_string", 3, True, [], False] 1698 | integers_found_so_far = 0 1699 | booleans_found_so_far = 0 1700 | 1701 | for item in mixed_list: 1702 | if isinstance(item, int): 1703 | integers_found_so_far += 1 1704 | elif isinstance(item, bool): 1705 | booleans_found_so_far += 1 1706 | ``` 1707 | 1708 | **Output:** 1709 | ```py 1710 | >>> booleans_found_so_far 1711 | 0 1712 | >>> integers_found_so_far 1713 | 4 1714 | ``` 1715 | 1716 | 2\. 1717 | ```py 1718 | another_dict = {} 1719 | another_dict[True] = "JavaScript" 1720 | another_dict[1] = "Ruby" 1721 | another_dict[1.0] = "Python" 1722 | ``` 1723 | 1724 | **Output:** 1725 | ```py 1726 | >>> another_dict[True] 1727 | "Python" 1728 | ``` 1729 | 1730 | 3\. 1731 | ```py 1732 | >>> some_bool = True 1733 | >>> "wtf"*some_bool 1734 | 'wtf' 1735 | >>> "wtf"*some_bool 1736 | '' 1737 | ``` 1738 | 1739 | #### 💡 Explanation: 1740 | 1741 | * Booleans are a subclass of `int` 1742 | ```py 1743 | >>> isinstance(True, int) 1744 | True 1745 | >>> isinstance(False, int) 1746 | True 1747 | ``` 1748 | 1749 | * The integer value of `True` is `1` and that of `False` is `0`. 1750 | ```py 1751 | >>> True == 1 == 1.0 and False == 0 == 0.0 1752 | True 1753 | ``` 1754 | 1755 | * See this StackOverflow [answer](https://stackoverflow.com/a/8169049/4354153) for the rationale behind it. 1756 | 1757 | --- 1758 | 1759 | ### Needle in a Haystack 1760 | 1761 | 1\. 1762 | ```py 1763 | x, y = (0, 1) if True else None, None 1764 | ``` 1765 | 1766 | **Output:** 1767 | ``` 1768 | >>> x, y # expected (0, 1) 1769 | ((0, 1), None) 1770 | ``` 1771 | 1772 | Almost every Python programmer would have faced a similar situation. 1773 | 2\. 1774 | ```py 1775 | t = ('one', 'two') 1776 | for i in t: 1777 | print(i) 1778 | 1779 | t = ('one') 1780 | for i in t: 1781 | print(i) 1782 | 1783 | t = () 1784 | print(t) 1785 | ``` 1786 | 1787 | **Output:** 1788 | ```py 1789 | one 1790 | two 1791 | o 1792 | n 1793 | e 1794 | tuple() 1795 | ``` 1796 | 1797 | 1798 | #### 💡 Explanation: 1799 | * For 1, the correct statement for expected behavior is `x, y = (0, 1) if True else (None, None)`. 1800 | * For 2, the correct statement for expected behavior is `t = ('one',)` or `t = 'one',` (missing comma) otherwise the interpreter considers `t` to be a `str` and iterates over it character by character. 1801 | * `()` is a special token and denotes empty `tuple`. 1802 | 1803 | --- 1804 | 1805 | ### Teleportation 1806 | 1807 | ```py 1808 | import numpy as np 1809 | 1810 | def energy_send(x): 1811 | # Initializing a numpy array 1812 | np.array([float(x)]) 1813 | 1814 | def energy_receive(): 1815 | # Return an empty numpy array 1816 | return np.empty((), dtype=np.float).tolist() 1817 | ``` 1818 | 1819 | **Output:** 1820 | ```py 1821 | >>> energy_send(123.456) 1822 | >>> energy_receive() 1823 | 123.456 1824 | ``` 1825 | 1826 | Is it worth a Nobel Prize? 1827 | 1828 | #### 💡 Explanation: 1829 | 1830 | * Notice that the numpy array created in the `energy_send` function is not returned, so that memory space is free to reallocate. 1831 | * `numpy.empty()` returns the next free memory slot without reinitializing it. This memory spot just happens to be the same one that was just freed (usually, but not always). 1832 | 1833 | --- 1834 | 1835 | ### yielding None 1836 | 1837 | ```py 1838 | some_iterable = ('a', 'b') 1839 | 1840 | def some_func(val): 1841 | return "something" 1842 | ``` 1843 | 1844 | 1845 | **Output:** 1846 | ```py 1847 | >>> [x for x in some_iterable] 1848 | ['a', 'b'] 1849 | >>> [(yield x) for x in some_iterable] 1850 | at 0x7f70b0a4ad58> 1851 | >>> list([(yield x) for x in some_iterable]) 1852 | ['a', 'b'] 1853 | >>> list((yield x) for x in some_iterable) 1854 | ['a', None, 'b', None] 1855 | >>> list(some_func((yield x)) for x in some_iterable) 1856 | ['a', 'something', 'b', 'something'] 1857 | ``` 1858 | 1859 | #### 💡 Explanation: 1860 | - Source and explanation can be found here: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions 1861 | - Related bug report: http://bugs.python.org/issue10544 1862 | 1863 | --- 1864 | 1865 | ### The surprising comma 1866 | 1867 | **Output:** 1868 | ```py 1869 | >>> def f(x, y,): 1870 | ... print(x, y) 1871 | ... 1872 | >>> def g(x=4, y=5,): 1873 | ... print(x, y) 1874 | ... 1875 | >>> def h(x, **kwargs,): 1876 | File "", line 1 1877 | def h(x, **kwargs,): 1878 | ^ 1879 | SyntaxError: invalid syntax 1880 | >>> def h(*args,): 1881 | File "", line 1 1882 | def h(*args,): 1883 | ^ 1884 | SyntaxError: invalid syntax 1885 | ``` 1886 | 1887 | #### 💡 Explanation: 1888 | 1889 | - Trailing comma is not always legal in formal parameters list of a Python function. 1890 | - In Python, the argument list is defined partially with leading commas and partially with trailing commas. This conflict causes situations where a comma is trapped in the middle, and no rule accepts it. 1891 | - **Note:** The trailing comma problem is [fixed in Python 3.6](https://bugs.python.org/issue9232). The remarks in [this](https://bugs.python.org/issue9232#msg248399) post discuss in brief different usages of trailing commas in Python. 1892 | 1893 | --- 1894 | 1895 | ### For what? 1896 | 1897 | ```py 1898 | some_string = "wtf" 1899 | some_dict = {} 1900 | for i, some_dict[i] in enumerate(some_string): 1901 | pass 1902 | ``` 1903 | 1904 | **Output:** 1905 | ```py 1906 | >>> some_dict # An indexed dict is created. 1907 | {0: 'w', 1: 't', 2: 'f'} 1908 | ``` 1909 | 1910 | #### 💡 Explanation: 1911 | 1912 | * A `for` statement is defined in the [Python grammar](https://docs.python.org/3/reference/grammar.html) as: 1913 | ``` 1914 | for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] 1915 | ``` 1916 | Where `exprlist` is the assignment target. This means that the equivalent of `{exprlist} = {next_value}` is **executed for each item** in the iterable. 1917 | An interesting example that illustrates this: 1918 | ```py 1919 | for i in range(4): 1920 | print(i) 1921 | i = 10 1922 | ``` 1923 | 1924 | **Output:** 1925 | ``` 1926 | 0 1927 | 1 1928 | 2 1929 | 3 1930 | ``` 1931 | 1932 | Did you expect the loop to run just once? 1933 | 1934 | **💡 Explanation:** 1935 | 1936 | - The assignment statement `i = 10` never affects the iterations of the loop because of the way for loops work in Python. Before the beginning of every iteration, the next item provided by the iterator (`range(4)` this case) is unpacked and assigned the target list variables (`i` in this case). 1937 | 1938 | * The `enumerate(some_string)` function yields a new value `i` (A counter going up) and a character from the `some_string` in each iteration. It then sets the (just assigned) `i` key of the dictionary `some_dict` to that character. The unrolling of the loop can be simplified as: 1939 | ```py 1940 | >>> i, some_dict[i] = (0, 'w') 1941 | >>> i, some_dict[i] = (1, 't') 1942 | >>> i, some_dict[i] = (2, 'f') 1943 | >>> some_dict 1944 | ``` 1945 | 1946 | --- 1947 | 1948 | ### not knot! 1949 | 1950 | ```py 1951 | x = True 1952 | y = False 1953 | ``` 1954 | 1955 | **Output:** 1956 | ```py 1957 | >>> not x == y 1958 | True 1959 | >>> x == not y 1960 | File "", line 1 1961 | x == not y 1962 | ^ 1963 | SyntaxError: invalid syntax 1964 | ``` 1965 | 1966 | #### 💡 Explanation: 1967 | 1968 | * Operator precedence affects how an expression is evaluated, and `==` operator has higher precedence than `not` operator in Python. 1969 | * So `not x == y` is equivalent to `not (x == y)` which is equivalent to `not (True == False)` finally evaluating to `True`. 1970 | * But `x == not y` raises a `SyntaxError` because it can be thought of being equivalent to `(x == not) y` and not `x == (not y)` which you might have expected at first sight. 1971 | * The parser expected the `not` token to be a part of the `not in` operator (because both `==` and `not in` operators have the same precedence), but after not being able to find an `in` token following the `not` token, it raises a `SyntaxError`. 1972 | 1973 | --- 1974 | 1975 | ### Subclass relationships 1976 | 1977 | **Output:** 1978 | ```py 1979 | >>> from collections import Hashable 1980 | >>> issubclass(list, object) 1981 | True 1982 | >>> issubclass(object, Hashable) 1983 | True 1984 | >>> issubclass(list, Hashable) 1985 | False 1986 | ``` 1987 | 1988 | The Subclass relationships were expected to be transitive, right? (i.e., if `A` is a subclass of `B`, and `B` is a subclass of `C`, the `A` _should_ a subclass of `C`) 1989 | 1990 | #### 💡 Explanation: 1991 | 1992 | * Subclass relationships are not necessarily transitive in Python. Anyone is allowed to define their own, arbitrary `__subclasscheck__` in a metaclass. 1993 | * When `issubclass(cls, Hashable)` is called, it simply looks for non-Falsey "`__hash__`" method in `cls` or anything it inherits from. 1994 | * Since `object` is hashable, but `list` is non-hashable, it breaks the transitivity relation. 1995 | * More detailed explanation can be found [here](https://www.naftaliharris.com/blog/python-subclass-intransitivity/). 1996 | 1997 | --- 1998 | 1999 | ### Mangling time! 2000 | 2001 | 2002 | ```py 2003 | class Yo(object): 2004 | def __init__(self): 2005 | self.__honey = True 2006 | self.bitch = True 2007 | ``` 2008 | 2009 | **Output:** 2010 | ```py 2011 | >>> Yo().bitch 2012 | True 2013 | >>> Yo().__honey 2014 | AttributeError: 'Yo' object has no attribute '__honey' 2015 | >>> Yo()._Yo__honey 2016 | True 2017 | ``` 2018 | 2019 | Why did `Yo()._Yo__honey` worked? Only Indian readers would understand. 2020 | 2021 | #### 💡 Explanation: 2022 | 2023 | * [Name Mangling](https://en.wikipedia.org/wiki/Name_mangling) is used to avoid naming collisions between different namespaces. 2024 | * 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. 2025 | * 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. 2026 | 2027 | --- 2028 | 2029 | ### Deep down, we're all the same. 2030 | 2031 | ```py 2032 | class WTF: 2033 | pass 2034 | ``` 2035 | 2036 | **Output:** 2037 | ```py 2038 | >>> WTF() == WTF() # two different instances can't be equal 2039 | False 2040 | >>> WTF() is WTF() # identities are also different 2041 | False 2042 | >>> hash(WTF()) == hash(WTF()) # hashes _should_ be different as well 2043 | True 2044 | >>> id(WTF()) == id(WTF()) 2045 | True 2046 | ``` 2047 | 2048 | 2049 | #### 💡 Explanation: 2050 | 2051 | * When `id` was called, Python created a `WTF` class object and passed it to the `id` function. The `id` function takes its `id` (its memory location), and throws away the object. The object is destroyed. 2052 | * When we do this twice in succession, Python allocates the same memory location to this second object as well. Since (in CPython) `id` uses the memory location as the object id, the id of the two objects is the same. 2053 | * So, object's id is unique only for the lifetime of the object. After the object is destroyed, or before it is created, something else can have the same id. 2054 | * But why did the `is` operator evaluated to `False`? Let's see with this snippet. 2055 | ```py 2056 | class WTF(object): 2057 | def __init__(self): print("I ") 2058 | def __del__(self): print("D ") 2059 | ``` 2060 | 2061 | **Output:** 2062 | ```py 2063 | >>> WTF() is WTF() 2064 | I I D D 2065 | >>> id(WTF()) == id(WTF()) 2066 | I D I D 2067 | ``` 2068 | As you may observe, the order in which the objects are destroyed is what made all the difference here. 2069 | 2070 | --- 2071 | 2072 | ### Half triple-quoted strings 2073 | 2074 | **Output:** 2075 | ```py 2076 | >>> print('wtfpython''') 2077 | wtfpython 2078 | >>> print("wtfpython""") 2079 | wtfpython 2080 | >>> # The following statements raise `SyntaxError` 2081 | >>> # print('''wtfpython') 2082 | >>> # print("""wtfpython") 2083 | ``` 2084 | 2085 | #### 💡 Explanation: 2086 | + Python supports implicit [string literal concatenation](https://docs.python.org/2/reference/lexical_analysis.html#string-literal-concatenation), Example, 2087 | ``` 2088 | >>> print("wtf" "python") 2089 | wtfpython 2090 | >>> print("wtf" "") # or "wtf""" 2091 | wtf 2092 | ``` 2093 | + `'''` and `"""` are also string delimiters in Python which causes a SyntaxError because the Python interpreter was expecting a terminating triple quote as delimiter while scanning the currently encountered triple quoted string literal. 2094 | 2095 | --- 2096 | 2097 | ### Implicity key type conversion 2098 | 2099 | ```py 2100 | class SomeClass(str): 2101 | pass 2102 | 2103 | some_dict = {'s':42} 2104 | ``` 2105 | 2106 | **Output:** 2107 | ```py 2108 | >>> type(list(some_dict.keys())[0]) 2109 | str 2110 | >>> s = SomeClass('s') 2111 | >>> some_dict[s] = 40 2112 | >>> some_dict # expected: Two different keys-value pairs 2113 | {'s': 40} 2114 | >>> type(list(some_dict.keys())[0]) 2115 | str 2116 | ``` 2117 | 2118 | #### 💡 Explanation: 2119 | 2120 | * Both the object `s` and the string `"s"` hash to the same value because `SomeClass` inherits the `__hash__` method of `str` class. 2121 | * `SomeClass("s") == "s"` evaluates to `True` because `SomeClass` also inherits `__eq__` method from `str` class. 2122 | * Since both the objects hash to the same value and are equal, they are represented by the same key in the dictionary. 2123 | * For the desired behavior, we can redefine the `__eq__` method in `SomeClass` 2124 | ```py 2125 | class SomeClass(str): 2126 | def __eq__(self, other): 2127 | return ( 2128 | type(self) is SomeClass 2129 | and type(other) is SomeClass 2130 | and super().__eq__(other) 2131 | ) 2132 | 2133 | # When we define a custom __eq__, Python stops automatically inheriting the 2134 | # __hash__ method, so we need to define it as well 2135 | __hash__ = str.__hash__ 2136 | 2137 | some_dict = {'s':42} 2138 | ``` 2139 | 2140 | **Output:** 2141 | ```py 2142 | >>> s = SomeClass('s') 2143 | >>> some_dict[s] = 40 2144 | >>> some_dict 2145 | {'s': 40} 2146 | >>> keys = list(some_dict.keys()) 2147 | >>> type(keys[0]), type(keys[1]) 2148 | (__main__.SomeClass, str) 2149 | ``` 2150 | 2151 | --- 2152 | 2153 | ### Stubborn `del` operator 2154 | 2155 | ```py 2156 | class SomeClass: 2157 | def __del__(self): 2158 | print("Deleted!") 2159 | ``` 2160 | 2161 | **Output:** 2162 | 1\. 2163 | ```py 2164 | >>> x = SomeClass() 2165 | >>> y = x 2166 | >>> del x # this should print "Deleted!" 2167 | >>> del y 2168 | Deleted! 2169 | ``` 2170 | 2171 | Phew, deleted at last. You might have guessed what saved from `__del__` being called in our first attempt to delete `x`. Let's add more twist to the example. 2172 | 2173 | 2\. 2174 | ```py 2175 | >>> x = SomeClass() 2176 | >>> y = x 2177 | >>> del x 2178 | >>> y # check if y exists 2179 | <__main__.SomeClass instance at 0x7f98a1a67fc8> 2180 | >>> del y # Like previously, this should print "Deleted!" 2181 | >>> globals() # oh, it didn't. Let's check all our global variables and confirm 2182 | Deleted! 2183 | {'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None} 2184 | ``` 2185 | 2186 | Okay, now it's deleted :confused: 2187 | 2188 | #### 💡 Explanation: 2189 | + `del x` doesn’t directly call `x.__del__()`. 2190 | + Whenever `del x` is encountered, Python decrements the reference count for `x` by one, and `x.__del__()` when x’s reference count reaches zero. 2191 | + In the second output snippet, `y.__del__()` was not called because the previous statement (`>>> y`) in the interactive interpreter created another reference to the same object, thus preventing the reference count to reach zero when `del y` was encountered. 2192 | + Calling `globals` caused the existing reference to be destroyed and hence we can see "Deleted!" being printed (finally!). 2193 | 2194 | --- 2195 | 2196 | ### Let's see if you can guess this? 2197 | 2198 | ```py 2199 | a, b = a[b] = {}, 5 2200 | ``` 2201 | 2202 | **Output:** 2203 | ```py 2204 | >>> a 2205 | {5: ({...}, 5)} 2206 | ``` 2207 | 2208 | #### 💡 Explanation: 2209 | 2210 | * According to [Python language reference](https://docs.python.org/2/reference/simple_stmts.html#assignment-statements), assignment statements have the form 2211 | ``` 2212 | (target_list "=")+ (expression_list | yield_expression) 2213 | ``` 2214 | and 2215 | > 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. 2216 | 2217 | * 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`). 2218 | 2219 | * 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`. 2220 | 2221 | * `a` is now assigned to `{}` which is a mutable object. 2222 | 2223 | * The second target list is `a[b]` (you may expect this to throw an error because both `a` and `b` have not been defined in the statements before. But remember, we just assigned `a` to `{}` and `b` to `5`). 2224 | 2225 | * Now, we are setting the key `5` in the dictionary to the tuple `({}, 5)` creating a circular reference (the `{...}` in the output refers to the same object that `a` is already referencing). Another simpler example of circular reference could be 2226 | ```py 2227 | >>> some_list = some_list[0] = [0] 2228 | >>> some_list 2229 | [[...]] 2230 | >>> some_list[0] 2231 | [[...]] 2232 | >>> some_list is some_list[0] 2233 | True 2234 | >>> some_list[0][0][0][0][0][0] == some_list 2235 | True 2236 | ``` 2237 | Similar is the case in our example (`a[b][0]` is the same object as `a`) 2238 | 2239 | * So to sum it up, you can break the example down to 2240 | ```py 2241 | a, b = {}, 5 2242 | a[b] = a, b 2243 | ``` 2244 | And the circular reference can be justified by the fact that `a[b][0]` is the same object as `a` 2245 | ```py 2246 | >>> a[b][0] is a 2247 | True 2248 | ``` 2249 | 2250 | --- 2251 | 2252 | ### Minor Ones 2253 | 2254 | * `join()` is a string operation instead of list operation. (sort of counter-intuitive at first usage) 2255 | 2256 | **💡 Explanation:** 2257 | 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. 2258 | 2259 | * Few weird looking but semantically correct statements: 2260 | + `[] = ()` is a semantically correct statement (unpacking an empty `tuple` into an empty `list`) 2261 | + `'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. 2262 | + `3 --0-- 5 == 8` and `--5 == 5` are both semantically correct statements and evaluate to `True`. 2263 | 2264 | * 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. 2265 | ```py 2266 | >>> a = 5 2267 | >>> a 2268 | 5 2269 | >>> ++a 2270 | 5 2271 | >>> --a 2272 | 5 2273 | ``` 2274 | 2275 | **💡 Explanation:** 2276 | + There is no `++` operator in Python grammar. It is actually two `+` operators. 2277 | + `++a` parses as `+(+a)` which translates to `a`. Similarly, the output of the statement `--a` can be justified. 2278 | + 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. 2279 | 2280 | * 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!): 2281 | ```py 2282 | import dis 2283 | exec(""" 2284 | def f():* """ + """ 2285 | """.join(["X"+str(x)+"=" + str(x) for x in range(65539)])) 2286 | 2287 | f() 2288 | 2289 | print(dis.dis(f)) 2290 | ``` 2291 | 2292 | * 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. 2293 | 2294 | * List slicing with out of the bounds indices throws no errors 2295 | ```py 2296 | >>> some_list = [1, 2, 3, 4, 5] 2297 | >>> some_list[111:] 2298 | [] 2299 | ``` 2300 | 2301 | * `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. 2302 | 2303 | * `'abc'.count('') == 4`. Here's an approximate implementation of `count` method, which would make the things more clear 2304 | ```py 2305 | def count(s, sub): 2306 | result = 0 2307 | for i in range(len(s) + 1 - len(sub)): 2308 | result += (s[i:i + len(sub)] == sub) 2309 | return result 2310 | ``` 2311 | The behavior is due to the matching of empty substring(`''`) with slices of length 0 in the original string. 2312 | 2313 | --- 2314 | 2315 | 2316 | # TODO: Hell of an example! 2317 | 2318 | Trying to come up with an example that combines multiple examples discussed above, making it difficult for the reader to guess the output correctly :sweat_smile:. 2319 | 2320 | # Contributing 2321 | 2322 | All patches are Welcome! Please see [CONTRIBUTING.md](/CONTRIBUTING.md) for further details. 2323 | 2324 | 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) 2325 | 2326 | # Acknowledgements 2327 | 2328 | 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. 2329 | 2330 | #### Some nice Links! 2331 | * https://www.youtube.com/watch?v=sH4XF6pKKmk 2332 | * https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python 2333 | * https://sopython.com/wiki/Common_Gotchas_In_Python 2334 | * https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines 2335 | * https://stackoverflow.com/questions/1011431/common-pitfalls-in-python 2336 | * https://www.python.org/doc/humor/ 2337 | 2338 | 2339 | # 🎓 License 2340 | 2341 | [![CC 4.0][license-image]][license-url] 2342 | 2343 | © [Satwik Kansal](https://satwikkansal.xyz) 2344 | 2345 | [license-url]: http://www.wtfpl.net 2346 | [license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square 2347 | 2348 | # Help 2349 | 2350 | Thanks a ton for reading this project, I hope you enjoyed it and found it informative! 2351 | 2352 | I'm looking for full-time opportunities. I'd highly appreciate if you could do me a small favor by letting me know about open positions around you. You can find more about me [here](https://satwikkansal.xyz). 2353 | 2354 | 2355 | ## Want to share wtfPython with friends? 2356 | 2357 | You can use these quick links for Twitter and Linkedin. 2358 | 2359 | [Twitter](https://twitter.com/intent/tweet?url=https://github.com/satwikkansal/wtfpython&hastags=python,wtfpython) | 2360 | [Linkedin](https://www.linkedin.com/shareArticle?url=https://github.com/satwikkansal&title=What%20the%20f*ck%20Python!&summary=An%20interesting%20collection%20of%20subtle%20and%20tricky%20Python%20snippets.) 2361 | 2362 | ## Need a pdf version? 2363 | 2364 | 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. 2365 | -------------------------------------------------------------------------------- /irrelevant/parse_readme.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | This inefficient module would parse the README.md in the initial version of 5 | WTFPython, and enable me to categorize and reorder a hell lot of examples with 6 | the help of the file `add_categories` (part of which is automatically 7 | generated). 8 | 9 | After the refactor, this module would not work now with necessary updates in 10 | the code. 11 | """ 12 | 13 | try: 14 | raw_input # Python 2 15 | except NameError: 16 | raw_input = input # Python 3 17 | 18 | 19 | fname = "README.md" 20 | snippets = [] 21 | 22 | with open(fname, 'r') as f: 23 | lines = iter(f.readlines()) 24 | line = lines.next() 25 | 26 | try: 27 | while True: 28 | # check if it's a H3 29 | if line.startswith("### "): 30 | title = line.replace("### ", "") 31 | description = [] 32 | next_line = lines.next() 33 | 34 | # store lines till an H4 (explanation) is encountered 35 | while not next_line.startswith("#### "): 36 | description.append(next_line) 37 | next_line = lines.next() 38 | 39 | explanation = [] 40 | # store lines again until --- or another H3 is encountered 41 | while not (next_line.startswith("---") or 42 | next_line.startswith("### ")): 43 | explanation.append(next_line) 44 | next_line = lines.next() 45 | 46 | # Store the results finally 47 | snippets.append({ 48 | "title": title, 49 | "description": '\n'.join(description), 50 | "explanation": '\n'.join(explanation) 51 | }) 52 | 53 | line = next_line 54 | 55 | else: 56 | line = lines.next() 57 | 58 | except StopIteration: 59 | snippets.append({ 60 | "title": title, 61 | "description": '\n'.join(description), 62 | "explanation": '\n'.join(explanation) 63 | }) 64 | 65 | ''' 66 | # Create a file 67 | file_content = "\n\n".join([snip["title"] for snip in snippets]) 68 | 69 | with open("add_categories", "w") as f: 70 | f.write(file_content) 71 | ''' 72 | 73 | snips_by_title = {} 74 | 75 | with open("add_categories", "r") as f: 76 | content = iter(f.readlines()) 77 | try: 78 | while True: 79 | title = content.next() 80 | cat = content.next().strip() 81 | is_new = True if cat[-1]=="*" else False 82 | cat = cat.replace('*','') 83 | snips_by_title[title] = { 84 | "category": cat, 85 | "is_new": is_new 86 | } 87 | content.next() 88 | except StopIteration: 89 | pass 90 | 91 | for idx, snip in enumerate(snippets): 92 | snippets[idx]["category"] = snips_by_title[snip["title"]]["category"] 93 | snippets[idx]["is_new"] = snips_by_title[snip["title"]]["is_new"] 94 | 95 | 96 | snips_by_cat = {} 97 | for snip in snippets: 98 | cat = snip["category"] 99 | if not snips_by_cat.get(cat): 100 | snips_by_cat[cat] = [] 101 | snips_by_cat[cat].append(snip) 102 | 103 | snippet_template = """ 104 | 105 | ### ▶ {title}{is_new} 106 | 107 | {description} 108 | 109 | {explanation} 110 | 111 | --- 112 | """ 113 | 114 | category_template = """ 115 | --- 116 | 117 | ## {category} 118 | 119 | {content} 120 | """ 121 | 122 | result = "" 123 | 124 | category_names = { 125 | "a": "Appearances are Deceptive!", 126 | "t": "The Hiddent treasures", 127 | "f": "Strain your Brain", 128 | "c": "Be careful of these", 129 | "m": "Miscallaneous" 130 | } 131 | 132 | categories_in_order = ["a", "t", "f", "c", "m"] 133 | 134 | for category in categories_in_order: 135 | snips = snips_by_cat[category] 136 | for i, snip in enumerate(snips): 137 | print(i, ":", snip["title"]) 138 | content = "" 139 | for _ in snips: 140 | snip = snips[int(raw_input())] 141 | is_new = " *" if snip["is_new"] else "" 142 | content += snippet_template.format(title=snip["title"].strip(), 143 | is_new=is_new, 144 | description=snip["description"].strip().replace("\n\n", "\n"), 145 | explanation=snip["explanation"].strip().replace("\n\n", "\n")) 146 | result += category_template.format(category=category_names[category], content=content.replace("\n\n\n", "\n\n")) 147 | 148 | with open("generated.md", "w") as f: 149 | f.write(result.replace("\n\n\n", "\n\n")) 150 | 151 | print("Done!") 152 | -------------------------------------------------------------------------------- /mixed_tabs_and_spaces.py: -------------------------------------------------------------------------------- 1 | def square(x): 2 | sum_so_far = 0 3 | for _ in range(x): 4 | sum_so_far += x 5 | return sum_so_far # noqa: E999 # pylint: disable=mixed-indentation Python 3 will raise a TabError here 6 | 7 | print(square(10)) 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wtfpython", 3 | "version": "2.1.0", 4 | "description": "A collection of surprising Python snippets and lesser known features.", 5 | "bin": "wtfpython", 6 | "scripts": { 7 | "postpublish": "git push origin master", 8 | "toc": "doctoc --github --title '# Table of Contents' --maxlevel 3 README.md" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/satwikkansal/wtfPython.git" 13 | }, 14 | "keywords": [ 15 | "python", 16 | "specification", 17 | "notes", 18 | "wtf", 19 | "learning", 20 | "guide", 21 | "handbook" 22 | ], 23 | "author": "Satwik Kansal (https://satwikkansal.xyz)", 24 | "license": "WTFPL 2.0", 25 | "bugs": { 26 | "url": "https://github.com/satwikkansal/wtfPython/issues" 27 | }, 28 | "homepage": "https://github.com/satwikkansal/wtfPython#readme", 29 | "devDependencies": { 30 | "doctoc": "^1.3.0" 31 | }, 32 | "dependencies": { 33 | "boxen": "^1.1.0", 34 | "chalk": "^1.1.1", 35 | "default-pager": "^1.1.0", 36 | "meow": "^3.7.0", 37 | "msee": "^0.3.3", 38 | "through2": "^2.0.2", 39 | "update-notifier": "^2.0.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /wtfpython: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const obj = require('through2').obj; 5 | const pager = require('default-pager'); 6 | const msee = require('msee'); 7 | const join = require('path').join; 8 | const boxen = require('boxen'); 9 | const chalk = require('chalk'); 10 | const updateNotifier = require('update-notifier'); 11 | const pkg = require('./package.json'); 12 | const meow = require('meow'); 13 | 14 | const cli = meow([ 15 | 'Usage', 16 | ' bash-handbook', 17 | '', 18 | 'Options', 19 | ' --lang, -l Translation language', 20 | '', 21 | 'Examples', 22 | ' bash-handbook', 23 | ' bash-handbook --lang pt-br' 24 | ], { 25 | string: [ 26 | 'lang' 27 | ], 28 | alias: { 29 | l: 'lang', 30 | h: 'help' 31 | }, 32 | default: { 33 | lang: '' 34 | } 35 | }); 36 | 37 | const boxenOpts = { 38 | borderColor: 'yellow', 39 | margin: { 40 | bottom: 1 41 | }, 42 | padding: { 43 | right: 1, 44 | left: 1 45 | } 46 | }; 47 | 48 | const mseeOpts = { 49 | paragraphEnd: '\n\n' 50 | }; 51 | 52 | const notifier = updateNotifier({ pkg }); 53 | 54 | process.env.PAGER = process.env.PAGER || 'less'; 55 | process.env.LESS = process.env.LESS || 'FRX'; 56 | 57 | const lang = cli.flags.lang 58 | .toLowerCase() 59 | .split('-') 60 | .map((l, i) => i === 0 ? l : l.toUpperCase()) 61 | .join('-'); 62 | 63 | const translation = join(__dirname, !lang ? './README.md' : `./README-${lang}.md`); 64 | 65 | fs.stat(translation, function (err, stats) { 66 | if (err) { 67 | console.log('The %s translation does not exist', chalk.bold(lang)); 68 | return; 69 | } 70 | 71 | fs.createReadStream(translation) 72 | .pipe(obj(function (chunk, enc, cb) { 73 | const message = []; 74 | 75 | if (notifier.update) { 76 | message.push(`Update available: {green.bold ${notifier.update.latest}} {dim current: ${notifier.update.current}}`); 77 | message.push(`Run {blue npm install -g ${pkg.name}} to update.`); 78 | this.push(boxen(message.join('\n'), boxenOpts)); 79 | } 80 | 81 | this.push(msee.parse(chunk.toString(), mseeOpts)); 82 | cb(); 83 | })) 84 | .pipe(pager()); 85 | }); 86 | -------------------------------------------------------------------------------- /wtfpython-pypi/content.md: -------------------------------------------------------------------------------- 1 |

2 |

What the f*ck Python! 🐍

3 |

An interesting collection of surprising snippets and lesser-known Python features.

4 | 5 | [![WTFPL 2.0][license-image]][license-url] 6 | 7 | 8 | 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. 9 | 10 | 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! 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 think you'll find them interesting as well! 13 | 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 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: 15 | 16 | If you're a returning reader, you can learn about the new modifications [here](https://github.com/satwikkansal/wtfpython/releases/). 17 | 18 | So, here we go... 19 | 20 | # Table of Contents 21 | 22 | 23 | 24 | 25 | 26 | - [Structure of the Examples](#structure-of-the-examples) 27 | - [Usage](#usage) 28 | - [👀 Examples](#-examples) 29 | - [Section: Strain your brain!](#section-strain-your-brain) 30 | - [▶ Strings can be tricky sometimes *](#-strings-can-be-tricky-sometimes-) 31 | - [▶ Time for some hash brownies!](#-time-for-some-hash-brownies) 32 | - [▶ Return return everywhere!](#-return-return-everywhere) 33 | - [▶ Deep down, we're all the same. *](#-deep-down-were-all-the-same-) 34 | - [▶ For what?](#-for-what) 35 | - [▶ Evaluation time discrepancy](#-evaluation-time-discrepancy) 36 | - [▶ `is` is not what it is!](#-is-is-not-what-it-is) 37 | - [▶ A tic-tac-toe where X wins in the first attempt!](#-a-tic-tac-toe-where-x-wins-in-the-first-attempt) 38 | - [▶ The sticky output function](#-the-sticky-output-function) 39 | - [▶ `is not ...` is not `is (not ...)`](#-is-not--is-not-is-not-) 40 | - [▶ The surprising comma](#-the-surprising-comma) 41 | - [▶ Backslashes at the end of string](#-backslashes-at-the-end-of-string) 42 | - [▶ not knot!](#-not-knot) 43 | - [▶ Half triple-quoted strings](#-half-triple-quoted-strings) 44 | - [▶ Midnight time doesn't exist?](#-midnight-time-doesnt-exist) 45 | - [▶ What's wrong with booleans?](#-whats-wrong-with-booleans) 46 | - [▶ Class attributes and instance attributes](#-class-attributes-and-instance-attributes) 47 | - [▶ yielding None](#-yielding-none) 48 | - [▶ Mutating the immutable!](#-mutating-the-immutable) 49 | - [▶ The disappearing variable from outer scope](#-the-disappearing-variable-from-outer-scope) 50 | - [▶ When True is actually False](#-when-true-is-actually-false) 51 | - [▶ From filled to None in one instruction...](#-from-filled-to-none-in-one-instruction) 52 | - [▶ Subclass relationships *](#-subclass-relationships-) 53 | - [▶ The mysterious key type conversion *](#-the-mysterious-key-type-conversion-) 54 | - [▶ Let's see if you can guess this?](#-lets-see-if-you-can-guess-this) 55 | - [Section: Appearances are deceptive!](#section-appearances-are-deceptive) 56 | - [▶ Skipping lines?](#-skipping-lines) 57 | - [▶ Teleportation *](#-teleportation-) 58 | - [▶ Well, something is fishy...](#-well-something-is-fishy) 59 | - [Section: Watch out for the landmines!](#section-watch-out-for-the-landmines) 60 | - [▶ Modifying a dictionary while iterating over it](#-modifying-a-dictionary-while-iterating-over-it) 61 | - [▶ Stubborn `del` operator *](#-stubborn-del-operator-) 62 | - [▶ Deleting a list item while iterating](#-deleting-a-list-item-while-iterating) 63 | - [▶ Loop variables leaking out!](#-loop-variables-leaking-out) 64 | - [▶ Beware of default mutable arguments!](#-beware-of-default-mutable-arguments) 65 | - [▶ Catching the Exceptions](#-catching-the-exceptions) 66 | - [▶ Same operands, different story!](#-same-operands-different-story) 67 | - [▶ The out of scope variable](#-the-out-of-scope-variable) 68 | - [▶ Be careful with chained operations](#-be-careful-with-chained-operations) 69 | - [▶ Name resolution ignoring class scope](#-name-resolution-ignoring-class-scope) 70 | - [▶ Needle in a Haystack](#-needle-in-a-haystack) 71 | - [Section: The Hidden treasures!](#section-the-hidden-treasures) 72 | - [▶ Okay Python, Can you make me fly? *](#-okay-python-can-you-make-me-fly-) 73 | - [▶ `goto`, but why? *](#-goto-but-why-) 74 | - [▶ Brace yourself! *](#-brace-yourself-) 75 | - [▶ Let's meet Friendly Language Uncle For Life *](#-lets-meet-friendly-language-uncle-for-life-) 76 | - [▶ Even Python understands that love is complicated *](#-even-python-understands-that-love-is-complicated-) 77 | - [▶ Yes, it exists!](#-yes-it-exists) 78 | - [▶ Inpinity *](#-inpinity-) 79 | - [▶ Mangling time! *](#-mangling-time-) 80 | - [Section: Miscallaneous](#section-miscallaneous) 81 | - [▶ `+=` is faster](#--is-faster) 82 | - [▶ Let's make a giant string!](#-lets-make-a-giant-string) 83 | - [▶ Explicit typecast of strings](#-explicit-typecast-of-strings) 84 | - [▶ Minor Ones](#-minor-ones) 85 | - [Contributing](#contributing) 86 | - [Acknowledgements](#acknowledgements) 87 | - [🎓 License](#-license) 88 | - [Help](#help) 89 | - [Want to share wtfpython with friends?](#want-to-share-wtfpython-with-friends) 90 | - [Need a pdf version?](#need-a-pdf-version) 91 | 92 | 93 | 94 | # Structure of the Examples 95 | 96 | All the examples are structured like below: 97 | 98 | > ### ▶ Some fancy Title * 99 | > The asterisk at the end of the title indicates the example was not present in the first release and has been recently added. 100 | > 101 | > ```py 102 | > # Setting up the code. 103 | > # Preparation for the magic... 104 | > ``` 105 | > 106 | > **Output (Python version):** 107 | > ```py 108 | > >>> triggering_statement 109 | > Probably unexpected output 110 | > ``` 111 | > (Optional): One line describing the unexpected output. 112 | > 113 | > 114 | > #### 💡 Explanation: 115 | > 116 | > * Brief explanation of what's happening and why is it happening. 117 | > ```py 118 | > Setting up examples for clarification (if necessary) 119 | > ``` 120 | > **Output:** 121 | > ```py 122 | > >>> trigger # some example that makes it easy to unveil the magic 123 | > # some justified output 124 | > ``` 125 | 126 | **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. 127 | 128 | # Usage 129 | 130 | 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: 131 | - 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. 132 | - Read the output snippets and, 133 | + Check if the outputs are the same as you'd expect. 134 | + Make sure if you know the exact reason behind the output being the way it is. 135 | - 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)). 136 | - If yes, give a gentle pat on your back, and you may skip to the next example. 137 | 138 | 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. 139 | 140 | To install the npm package [`wtfpython`](https://www.npmjs.com/package/wtfpython) 141 | ```sh 142 | $ npm install -g wtfpython 143 | ``` 144 | 145 | Alternatively, to install the pypi package [`wtfpython`](https://pypi.python.org/pypi/wtfpython) 146 | ```sh 147 | $ pip install wtfpython -U 148 | ``` 149 | 150 | Now, just run `wtfpython` at the command line which will open this collection in your selected `$PAGER`. 151 | 152 | --- 153 | 154 | # 👀 Examples 155 | 156 | 157 | ## Section: Strain your brain! 158 | 159 | ### ▶ Strings can be tricky sometimes * 160 | 161 | 1\. 162 | ```py 163 | >>> a = "some_string" 164 | >>> id(a) 165 | 140420665652016 166 | >>> id("some" + "_" + "string") # Notice that both the ids are same. 167 | 140420665652016 168 | ``` 169 | 170 | 2\. 171 | ```py 172 | >>> a = "wtf" 173 | >>> b = "wtf" 174 | >>> a is b 175 | True 176 | 177 | >>> a = "wtf!" 178 | >>> b = "wtf!" 179 | >>> a is b 180 | False 181 | 182 | >>> a, b = "wtf!", "wtf!" 183 | >>> a is b 184 | True 185 | ``` 186 | 187 | 3\. 188 | ```py 189 | >>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa' 190 | True 191 | >>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa' 192 | False 193 | ``` 194 | 195 | Makes sense, right? 196 | 197 | #### 💡 Explanation: 198 | + Such behavior is due to CPython optimization (called string interning) that tries to use existing immutable objects in some cases rather than creating a new object every time. 199 | + After being interned, many variables may point to the same string object in memory (thereby saving memory). 200 | + In the snippets above, strings are implicitly interned. The decision of when to implicitly intern a string is implementation dependent. There are some facts that can be used to guess if a string will be interned or not: 201 | * All length 0 and length 1 strings are interned. 202 | * Strings are interned at compile time (`'wtf'` will be interned but `''.join(['w', 't', 'f']` will not be interned) 203 | * Strings that are not composed of ASCII letters, digits or underscores, are not interned. This explains why `'wtf!'` was not interned due to `!`. 204 | 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 | + 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. But since the python bytecode generated after compilation is stored in `.pyc` files, the strings greater than length of 20 are discarded for peephole optimization (Why? Imagine the size of `.pyc` file generated as a result of the expression `'a'*10**10`) 207 | 208 | 209 | --- 210 | 211 | ### ▶ Time for some hash brownies! 212 | 213 | 1\. 214 | ```py 215 | some_dict = {} 216 | some_dict[5.5] = "Ruby" 217 | some_dict[5.0] = "JavaScript" 218 | some_dict[5] = "Python" 219 | ``` 220 | 221 | **Output:** 222 | ```py 223 | >>> some_dict[5.5] 224 | "Ruby" 225 | >>> some_dict[5.0] 226 | "Python" 227 | >>> some_dict[5] 228 | "Python" 229 | ``` 230 | 231 | "Python" destroyed the existence of "JavaScript"? 232 | 233 | #### 💡 Explanation 234 | 235 | * Python dictionaries check for equality and compare the hash value to determine if two keys are the same. 236 | * Immutable objects with same value always have the same hash in Python. 237 | ```py 238 | >>> 5 == 5.0 239 | True 240 | >>> hash(5) == hash(5.0) 241 | True 242 | ``` 243 | **Note:** Objects with different values may also have same hash (known as hash collision). 244 | * When the statement `some_dict[5] = "Python"` is executed, the existing value "JavaScript" is overwritten with "Python" because Python recongnizes `5` and `5.0` as the same keys of the dictionary `some_dict`. 245 | * This StackOverflow [answer](https://stackoverflow.com/a/32211042/4354153) explains beautifully the rationale behind it. 246 | 247 | --- 248 | 249 | ### ▶ Return return everywhere! 250 | 251 | ```py 252 | def some_func(): 253 | try: 254 | return 'from_try' 255 | finally: 256 | return 'from_finally' 257 | ``` 258 | 259 | **Output:** 260 | ```py 261 | >>> some_func() 262 | 'from_finally' 263 | ``` 264 | 265 | #### 💡 Explanation: 266 | 267 | - 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 | - 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. 269 | 270 | --- 271 | 272 | ### ▶ Deep down, we're all the same. * 273 | 274 | ```py 275 | class WTF: 276 | pass 277 | ``` 278 | 279 | **Output:** 280 | ```py 281 | >>> WTF() == WTF() # two different instances can't be equal 282 | False 283 | >>> WTF() is WTF() # identities are also different 284 | False 285 | >>> hash(WTF()) == hash(WTF()) # hashes _should_ be different as well 286 | True 287 | >>> id(WTF()) == id(WTF()) 288 | True 289 | ``` 290 | 291 | #### 💡 Explanation: 292 | 293 | * When `id` was called, Python created a `WTF` class object and passed it to the `id` function. The `id` function takes its `id` (its memory location), and throws away the object. The object is destroyed. 294 | * When we do this twice in succession, Python allocates the same memory location to this second object as well. Since (in CPython) `id` uses the memory location as the object id, the id of the two objects is the same. 295 | * So, object's id is unique only for the lifetime of the object. After the object is destroyed, or before it is created, something else can have the same id. 296 | * But why did the `is` operator evaluated to `False`? Let's see with this snippet. 297 | ```py 298 | class WTF(object): 299 | def __init__(self): print("I ") 300 | def __del__(self): print("D ") 301 | ``` 302 | 303 | **Output:** 304 | ```py 305 | >>> WTF() is WTF() 306 | I I D D 307 | >>> id(WTF()) == id(WTF()) 308 | I D I D 309 | ``` 310 | As you may observe, the order in which the objects are destroyed is what made all the difference here. 311 | 312 | --- 313 | 314 | ### ▶ For what? 315 | 316 | ```py 317 | some_string = "wtf" 318 | some_dict = {} 319 | for i, some_dict[i] in enumerate(some_string): 320 | pass 321 | ``` 322 | 323 | **Output:** 324 | ```py 325 | >>> some_dict # An indexed dict is created. 326 | {0: 'w', 1: 't', 2: 'f'} 327 | ``` 328 | 329 | #### 💡 Explanation: 330 | 331 | * A `for` statement is defined in the [Python grammar](https://docs.python.org/3/reference/grammar.html) as: 332 | ``` 333 | for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] 334 | ``` 335 | Where `exprlist` is the assignment target. This means that the equivalent of `{exprlist} = {next_value}` is **executed for each item** in the iterable. 336 | An interesting example that illustrates this: 337 | ```py 338 | for i in range(4): 339 | print(i) 340 | i = 10 341 | ``` 342 | 343 | **Output:** 344 | ``` 345 | 0 346 | 1 347 | 2 348 | 3 349 | ``` 350 | 351 | Did you expect the loop to run just once? 352 | 353 | **💡 Explanation:** 354 | 355 | - The assignment statement `i = 10` never affects the iterations of the loop because of the way for loops work in Python. Before the beginning of every iteration, the next item provided by the iterator (`range(4)` this case) is unpacked and assigned the target list variables (`i` in this case). 356 | 357 | * The `enumerate(some_string)` function yields a new value `i` (A counter going up) and a character from the `some_string` in each iteration. It then sets the (just assigned) `i` key of the dictionary `some_dict` to that character. The unrolling of the loop can be simplified as: 358 | ```py 359 | >>> i, some_dict[i] = (0, 'w') 360 | >>> i, some_dict[i] = (1, 't') 361 | >>> i, some_dict[i] = (2, 'f') 362 | >>> some_dict 363 | ``` 364 | 365 | --- 366 | 367 | ### ▶ Evaluation time discrepancy 368 | 369 | 1\. 370 | ```py 371 | array = [1, 8, 15] 372 | g = (x for x in array if array.count(x) > 0) 373 | array = [2, 8, 22] 374 | ``` 375 | 376 | **Output:** 377 | ```py 378 | >>> print(list(g)) 379 | [8] 380 | ``` 381 | 382 | 2\. 383 | 384 | ```py 385 | array_1 = [1,2,3,4] 386 | g1 = (x for x in array_1) 387 | array_1 = [1,2,3,4,5] 388 | 389 | array_2 = [1,2,3,4] 390 | g2 = (x for x in array_2) 391 | array_2[:] = [1,2,3,4,5] 392 | ``` 393 | 394 | **Output:** 395 | ```py 396 | >>> print(list(g1)) 397 | [1,2,3,4] 398 | 399 | >>> print(list(g2)) 400 | [1,2,3,4,5] 401 | ``` 402 | 403 | #### 💡 Explanation 404 | 405 | - In a [generator](https://wiki.python.org/moin/Generators) expression, the `in` clause is evaluated at declaration time, but the conditional clause is evaluated at runtime. 406 | - So before runtime, `array` is re-assigned to the list `[2, 8, 22]`, and since out of `1`, `8` and `15`, only the count of `8` is greater than `0`, the generator only yields `8`. 407 | - The differences in the output of `g1` and `g2` in the second part is due the way variables `array_1` and `array_2` are re-assigned values. 408 | - In the first case, `array_1` is binded to the new object `[1,2,3,4,5]` and since the `in` clause is evaluated at the declaration time it still refers to the old object `[1,2,3,4]` (which is not destroyed). 409 | - In the second case, the slice assignment to `array_2` updates the same old object `[1,2,3,4]` to `[1,2,3,4,5]`. Hence both the `g2` and `array_2` still have reference to the same object (which has now been updated to `[1,2,3,4,5]`). 410 | 411 | --- 412 | 413 | ### ▶ `is` is not what it is! 414 | 415 | The following is a very famous example present all over the internet. 416 | 417 | ```py 418 | >>> a = 256 419 | >>> b = 256 420 | >>> a is b 421 | True 422 | 423 | >>> a = 257 424 | >>> b = 257 425 | >>> a is b 426 | False 427 | 428 | >>> a = 257; b = 257 429 | >>> a is b 430 | True 431 | ``` 432 | 433 | #### 💡 Explanation: 434 | 435 | **The difference between `is` and `==`** 436 | 437 | * `is` operator checks if both the operands refer to the same object (i.e., it checks if the identity of the operands matches or not). 438 | * `==` operator compares the values of both the operands and checks if they are the same. 439 | * So `is` is for reference equality and `==` is for value equality. An example to clear things up, 440 | ```py 441 | >>> [] == [] 442 | True 443 | >>> [] is [] # These are two empty lists at two different memory locations. 444 | False 445 | ``` 446 | 447 | **`256` is an existing object but `257` isn't** 448 | 449 | 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. 450 | 451 | Quoting from https://docs.python.org/3/c-api/long.html 452 | > 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. :-) 453 | 454 | ```py 455 | >>> id(256) 456 | 10922528 457 | >>> a = 256 458 | >>> b = 256 459 | >>> id(a) 460 | 10922528 461 | >>> id(b) 462 | 10922528 463 | >>> id(257) 464 | 140084850247312 465 | >>> x = 257 466 | >>> y = 257 467 | >>> id(x) 468 | 140084850247440 469 | >>> id(y) 470 | 140084850247344 471 | ``` 472 | 473 | 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. 474 | 475 | **Both `a` and `b` refer to the same object when initialized with same value in the same line.** 476 | 477 | ```py 478 | >>> a, b = 257, 257 479 | >>> id(a) 480 | 140640774013296 481 | >>> id(b) 482 | 140640774013296 483 | >>> a = 257 484 | >>> b = 257 485 | >>> id(a) 486 | 140640774013392 487 | >>> id(b) 488 | 140640774013488 489 | ``` 490 | 491 | * When a and b are set to `257` 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 `257` as an object. 492 | * It's a compiler optimization and specifically applies to the interactive environment. When you enter two lines in a live interpreter, they're compiled separately, therefore optimized separately. If you were to try this example in a `.py` file, you would not see the same behavior, because the file is compiled all at once. 493 | 494 | --- 495 | 496 | ### ▶ A tic-tac-toe where X wins in the first attempt! 497 | 498 | ```py 499 | # Let's initialize a row 500 | row = [""]*3 #row i['', '', ''] 501 | # Let's make a board 502 | board = [row]*3 503 | ``` 504 | 505 | **Output:** 506 | ```py 507 | >>> board 508 | [['', '', ''], ['', '', ''], ['', '', '']] 509 | >>> board[0] 510 | ['', '', ''] 511 | >>> board[0][0] 512 | '' 513 | >>> board[0][0] = "X" 514 | >>> board 515 | [['X', '', ''], ['X', '', ''], ['X', '', '']] 516 | ``` 517 | 518 | We didn't assign 3 "X"s or did we? 519 | 520 | #### 💡 Explanation: 521 | 522 | When we initialize `row` variable, this visualization explains what happens in the memory 523 | 524 | ![image](/images/tic-tac-toe/after_row_initialized.png) 525 | 526 | 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`) 527 | 528 | ![image](/images/tic-tac-toe/after_board_initialized.png) 529 | 530 | 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). 531 | 532 | ```py 533 | >>> board = [(['']*3)*3] # board = = [['']*3 for _ in range(3)] 534 | >>> board[0][0] = "X" 535 | >>> board 536 | [['X', '', ''], ['', '', ''], ['', '', '']] 537 | ``` 538 | 539 | --- 540 | 541 | ### ▶ The sticky output function 542 | 543 | ```py 544 | funcs = [] 545 | results = [] 546 | for x in range(7): 547 | def some_func(): 548 | return x 549 | funcs.append(some_func) 550 | results.append(some_func()) 551 | 552 | funcs_results = [func() for func in funcs] 553 | ``` 554 | 555 | **Output:** 556 | ```py 557 | >>> results 558 | [0, 1, 2, 3, 4, 5, 6] 559 | >>> funcs_results 560 | [6, 6, 6, 6, 6, 6, 6] 561 | ``` 562 | Even when the values of `x` were different in every iteration prior to appending `some_func` to `funcs`, all the functions return 6. 563 | 564 | //OR 565 | 566 | ```py 567 | >>> powers_of_x = [lambda x: x**i for i in range(10)] 568 | >>> [f(2) for f in powers_of_x] 569 | [512, 512, 512, 512, 512, 512, 512, 512, 512, 512] 570 | ``` 571 | 572 | #### 💡 Explanation 573 | 574 | - When defining a function inside a loop that uses the loop variable in its body, the loop function's closure is bound to the variable, not its value. So all of the functions use the latest value assigned to the variable for computation. 575 | 576 | - To get the desired behavior you can pass in the loop variable as a named variable to the function. **Why this works?** Because this will define the variable again within the function's scope. 577 | 578 | ```py 579 | funcs = [] 580 | for x in range(7): 581 | def some_func(x=x): 582 | return x 583 | funcs.append(some_func) 584 | ``` 585 | 586 | **Output:** 587 | ```py 588 | >>> funcs_results = [func() for func in funcs] 589 | >>> funcs_results 590 | [0, 1, 2, 3, 4, 5, 6] 591 | ``` 592 | 593 | --- 594 | 595 | ### ▶ `is not ...` is not `is (not ...)` 596 | 597 | ```py 598 | >>> 'something' is not None 599 | True 600 | >>> 'something' is (not None) 601 | False 602 | ``` 603 | 604 | #### 💡 Explanation 605 | 606 | - `is not` is a single binary operator, and has behavior different than using `is` and `not` separated. 607 | - `is not` evaluates to `False` if the variables on either side of the operator point to the same object and `True` otherwise. 608 | 609 | --- 610 | 611 | ### ▶ The surprising comma 612 | 613 | **Output:** 614 | ```py 615 | >>> def f(x, y,): 616 | ... print(x, y) 617 | ... 618 | >>> def g(x=4, y=5,): 619 | ... print(x, y) 620 | ... 621 | >>> def h(x, **kwargs,): 622 | File "", line 1 623 | def h(x, **kwargs,): 624 | ^ 625 | SyntaxError: invalid syntax 626 | >>> def h(*args,): 627 | File "", line 1 628 | def h(*args,): 629 | ^ 630 | SyntaxError: invalid syntax 631 | ``` 632 | 633 | #### 💡 Explanation: 634 | 635 | - Trailing comma is not always legal in formal parameters list of a Python function. 636 | - In Python, the argument list is defined partially with leading commas and partially with trailing commas. This conflict causes situations where a comma is trapped in the middle, and no rule accepts it. 637 | - **Note:** The trailing comma problem is [fixed in Python 3.6](https://bugs.python.org/issue9232). The remarks in [this](https://bugs.python.org/issue9232#msg248399) post discuss in brief different usages of trailing commas in Python. 638 | 639 | --- 640 | 641 | ### ▶ Backslashes at the end of string 642 | 643 | **Output:** 644 | ``` 645 | >>> print("\\ C:\\") 646 | \ C:\ 647 | >>> print(r"\ C:") 648 | \ C: 649 | >>> print(r"\ C:\") 650 | 651 | File "", line 1 652 | print(r"\ C:\") 653 | ^ 654 | SyntaxError: EOL while scanning string literal 655 | ``` 656 | 657 | #### 💡 Explanation 658 | 659 | - In a raw string literal, as indicated by the prefix `r`, the backslash doesn't have the special meaning. 660 | ```py 661 | >>> print(repr(r"wt\"f")) 662 | 'wt\\"f' 663 | ``` 664 | - What the interpreter actually does, though, is simply change the behavior of backslashes, so they pass themselves and the following character through. That's why backslashes don't work at the end of a raw string. 665 | 666 | --- 667 | 668 | ### ▶ not knot! 669 | 670 | ```py 671 | x = True 672 | y = False 673 | ``` 674 | 675 | **Output:** 676 | ```py 677 | >>> not x == y 678 | True 679 | >>> x == not y 680 | File "", line 1 681 | x == not y 682 | ^ 683 | SyntaxError: invalid syntax 684 | ``` 685 | 686 | #### 💡 Explanation: 687 | 688 | * Operator precedence affects how an expression is evaluated, and `==` operator has higher precedence than `not` operator in Python. 689 | * So `not x == y` is equivalent to `not (x == y)` which is equivalent to `not (True == False)` finally evaluating to `True`. 690 | * But `x == not y` raises a `SyntaxError` because it can be thought of being equivalent to `(x == not) y` and not `x == (not y)` which you might have expected at first sight. 691 | * The parser expected the `not` token to be a part of the `not in` operator (because both `==` and `not in` operators have the same precedence), but after not being able to find an `in` token following the `not` token, it raises a `SyntaxError`. 692 | 693 | --- 694 | 695 | ### ▶ Half triple-quoted strings 696 | 697 | **Output:** 698 | ```py 699 | >>> print('wtfpython''') 700 | wtfpython 701 | >>> print("wtfpython""") 702 | wtfpython 703 | >>> # The following statements raise `SyntaxError` 704 | >>> # print('''wtfpython') 705 | >>> # print("""wtfpython") 706 | ``` 707 | 708 | #### 💡 Explanation: 709 | + Python supports implicit [string literal concatenation](https://docs.python.org/2/reference/lexical_analysis.html#string-literal-concatenation), Example, 710 | ``` 711 | >>> print("wtf" "python") 712 | wtfpython 713 | >>> print("wtf" "") # or "wtf""" 714 | wtf 715 | ``` 716 | + `'''` and `"""` are also string delimiters in Python which causes a SyntaxError because the Python interpreter was expecting a terminating triple quote as delimiter while scanning the currently encountered triple quoted string literal. 717 | 718 | --- 719 | 720 | ### ▶ Midnight time doesn't exist? 721 | 722 | ```py 723 | from datetime import datetime 724 | 725 | midnight = datetime(2018, 1, 1, 0, 0) 726 | midnight_time = midnight.time() 727 | 728 | noon = datetime(2018, 1, 1, 12, 0) 729 | noon_time = noon.time() 730 | 731 | if midnight_time: 732 | print("Time at midnight is", midnight_time) 733 | 734 | if noon_time: 735 | print("Time at noon is", noon_time) 736 | ``` 737 | 738 | **Output:** 739 | ```sh 740 | ('Time at noon is', datetime.time(12, 0)) 741 | ``` 742 | The midnight time is not printed. 743 | 744 | #### 💡 Explanation: 745 | 746 | Before Python 3.5, the boolean value for `datetime.time` object was considered to be `False` if it represented midnight in UTC. It is error-prone when using the `if obj:` syntax to check if the `obj` is null or some equivalent of "empty." 747 | 748 | --- 749 | 750 | ### ▶ What's wrong with booleans? 751 | 752 | 1\. 753 | ```py 754 | # A simple example to count the number of boolean and 755 | # integers in an iterable of mixed data types. 756 | mixed_list = [False, 1.0, "some_string", 3, True, [], False] 757 | integers_found_so_far = 0 758 | booleans_found_so_far = 0 759 | 760 | for item in mixed_list: 761 | if isinstance(item, int): 762 | integers_found_so_far += 1 763 | elif isinstance(item, bool): 764 | booleans_found_so_far += 1 765 | ``` 766 | 767 | **Output:** 768 | ```py 769 | >>> booleans_found_so_far 770 | 0 771 | >>> integers_found_so_far 772 | 4 773 | ``` 774 | 775 | 2\. 776 | ```py 777 | another_dict = {} 778 | another_dict[True] = "JavaScript" 779 | another_dict[1] = "Ruby" 780 | another_dict[1.0] = "Python" 781 | ``` 782 | 783 | **Output:** 784 | ```py 785 | >>> another_dict[True] 786 | "Python" 787 | ``` 788 | 789 | 3\. 790 | ```py 791 | >>> some_bool = True 792 | >>> "wtf"*some_bool 793 | 'wtf' 794 | >>> some_bool = False 795 | >>> "wtf"*some_bool 796 | '' 797 | ``` 798 | 799 | #### 💡 Explanation: 800 | 801 | * Booleans are a subclass of `int` 802 | ```py 803 | >>> isinstance(True, int) 804 | True 805 | >>> isinstance(False, int) 806 | True 807 | ``` 808 | 809 | * The integer value of `True` is `1` and that of `False` is `0`. 810 | ```py 811 | >>> True == 1 == 1.0 and False == 0 == 0.0 812 | True 813 | ``` 814 | 815 | * See this StackOverflow [answer](https://stackoverflow.com/a/8169049/4354153) for the rationale behind it. 816 | 817 | --- 818 | 819 | ### ▶ Class attributes and instance attributes 820 | 821 | 1\. 822 | ```py 823 | class A: 824 | x = 1 825 | 826 | class B(A): 827 | pass 828 | 829 | class C(A): 830 | pass 831 | ``` 832 | 833 | **Ouptut:** 834 | ```py 835 | >>> A.x, B.x, C.x 836 | (1, 1, 1) 837 | >>> B.x = 2 838 | >>> A.x, B.x, C.x 839 | (1, 2, 1) 840 | >>> A.x = 3 841 | >>> A.x, B.x, C.x 842 | (3, 2, 3) 843 | >>> a = A() 844 | >>> a.x, A.x 845 | (3, 3) 846 | >>> a.x += 1 847 | >>> a.x, A.x 848 | (4, 3) 849 | ``` 850 | 851 | 2\. 852 | ```py 853 | class SomeClass: 854 | some_var = 15 855 | some_list = [5] 856 | another_list = [5] 857 | def __init__(self, x): 858 | self.some_var = x + 1 859 | self.some_list = self.some_list + [x] 860 | self.another_list += [x] 861 | ``` 862 | 863 | **Output:** 864 | 865 | ```py 866 | >>> some_obj = SomeClass(420) 867 | >>> some_obj.some_list 868 | [5, 420] 869 | >>> some_obj.another_list 870 | [5, 420] 871 | >>> another_obj = SomeClass(111) 872 | >>> another_obj.some_list 873 | [5, 111] 874 | >>> another_obj.another_list 875 | [5, 420, 111] 876 | >>> another_obj.another_list is SomeClass.another_list 877 | True 878 | >>> another_obj.another_list is some_obj.another_list 879 | True 880 | ``` 881 | 882 | #### 💡 Explanation: 883 | 884 | * Class variables and variables in class instances are internally handled as dictionaries of a class object. If a variable name is not found in the dictionary of the current class, the parent classes are searched for it. 885 | * The `+=` operator modifies the mutable object in-place without creating a new object. So changing the attribute of one instance affects the other instances and the class attribute as well. 886 | 887 | --- 888 | 889 | ### ▶ yielding None 890 | 891 | ```py 892 | some_iterable = ('a', 'b') 893 | 894 | def some_func(val): 895 | return "something" 896 | ``` 897 | 898 | **Output:** 899 | ```py 900 | >>> [x for x in some_iterable] 901 | ['a', 'b'] 902 | >>> [(yield x) for x in some_iterable] 903 | at 0x7f70b0a4ad58> 904 | >>> list([(yield x) for x in some_iterable]) 905 | ['a', 'b'] 906 | >>> list((yield x) for x in some_iterable) 907 | ['a', None, 'b', None] 908 | >>> list(some_func((yield x)) for x in some_iterable) 909 | ['a', 'something', 'b', 'something'] 910 | ``` 911 | 912 | #### 💡 Explanation: 913 | - Source and explanation can be found here: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions 914 | - Related bug report: http://bugs.python.org/issue10544 915 | 916 | --- 917 | 918 | ### ▶ Mutating the immutable! 919 | 920 | ```py 921 | some_tuple = ("A", "tuple", "with", "values") 922 | another_tuple = ([1, 2], [3, 4], [5, 6]) 923 | ``` 924 | 925 | **Output:** 926 | ```py 927 | >>> some_tuple[2] = "change this" 928 | TypeError: 'tuple' object does not support item assignment 929 | >>> another_tuple[2].append(1000) #This throws no error 930 | >>> another_tuple 931 | ([1, 2], [3, 4], [5, 6, 1000]) 932 | >>> another_tuple[2] += [99, 999] 933 | TypeError: 'tuple' object does not support item assignment 934 | >>> another_tuple 935 | ([1, 2], [3, 4], [5, 6, 1000, 99, 999]) 936 | ``` 937 | 938 | But I thought tuples were immutable... 939 | 940 | #### 💡 Explanation: 941 | 942 | * Quoting from https://docs.python.org/2/reference/datamodel.html 943 | 944 | > Immutable sequences 945 | An object of an immutable sequence type cannot change once it is created. (If the object contains references to other objects, these other objects may be mutable and may be modified; however, the collection of objects directly referenced by an immutable object cannot change.) 946 | 947 | * `+=` operator changes the list in-place. The item assignment doesn't work, but when the exception occurs, the item has already been changed in place. 948 | 949 | --- 950 | 951 | ### ▶ The disappearing variable from outer scope 952 | 953 | ```py 954 | e = 7 955 | try: 956 | raise Exception() 957 | except Exception as e: 958 | pass 959 | ``` 960 | 961 | **Output (Python 2.x):** 962 | ```py 963 | >>> print(e) 964 | # prints nothing 965 | ``` 966 | 967 | **Output (Python 3.x):** 968 | ```py 969 | >>> print(e) 970 | NameError: name 'e' is not defined 971 | ``` 972 | 973 | #### 💡 Explanation: 974 | 975 | * Source: https://docs.python.org/3/reference/compound_stmts.html#except 976 | 977 | When an exception has been assigned using `as` target, it is cleared at the end of the except clause. This is as if 978 | 979 | ```py 980 | except E as N: 981 | foo 982 | ``` 983 | 984 | was translated into 985 | 986 | ```py 987 | except E as N: 988 | try: 989 | foo 990 | finally: 991 | del N 992 | ``` 993 | 994 | This means the exception must be assigned to a different name to be able to refer to it after the except clause. Exceptions are cleared because, with the traceback attached to them, they form a reference cycle with the stack frame, keeping all locals in that frame alive until the next garbage collection occurs. 995 | 996 | * The clauses are not scoped in Python. Everything in the example is present in the same scope, and the variable `e` got removed due to the execution of the `except` clause. The same is not the case with functions which have their separate inner-scopes. The example below illustrates this: 997 | 998 | ```py 999 | def f(x): 1000 | del(x) 1001 | print(x) 1002 | 1003 | x = 5 1004 | y = [5, 4, 3] 1005 | ``` 1006 | 1007 | **Output:** 1008 | ```py 1009 | >>>f(x) 1010 | UnboundLocalError: local variable 'x' referenced before assignment 1011 | >>>f(y) 1012 | UnboundLocalError: local variable 'x' referenced before assignment 1013 | >>> x 1014 | 5 1015 | >>> y 1016 | [5, 4, 3] 1017 | ``` 1018 | 1019 | * In Python 2.x the variable name `e` gets assigned to `Exception()` instance, so when you try to print, it prints nothing. 1020 | 1021 | **Output (Python 2.x):** 1022 | ```py 1023 | >>> e 1024 | Exception() 1025 | >>> print e 1026 | # Nothing is printed! 1027 | ``` 1028 | 1029 | --- 1030 | 1031 | ### ▶ When True is actually False 1032 | 1033 | ```py 1034 | True = False 1035 | if True == False: 1036 | print("I've lost faith in truth!") 1037 | ``` 1038 | 1039 | **Output:** 1040 | ``` 1041 | I've lost faith in truth! 1042 | ``` 1043 | 1044 | #### 💡 Explanation: 1045 | 1046 | - Initially, Python used to have no `bool` type (people used 0 for false and non-zero value like 1 for true). Then they added `True`, `False`, and a `bool` type, but, for backward compatibility, they couldn't make `True` and `False` constants- they just were built-in variables. 1047 | - Python 3 was backward-incompatible, so it was now finally possible to fix that, and so this example won't work with Python 3.x! 1048 | 1049 | --- 1050 | 1051 | ### ▶ From filled to None in one instruction... 1052 | 1053 | ```py 1054 | some_list = [1, 2, 3] 1055 | some_dict = { 1056 | "key_1": 1, 1057 | "key_2": 2, 1058 | "key_3": 3 1059 | } 1060 | 1061 | some_list = some_list.append(4) 1062 | some_dict = some_dict.update({"key_4": 4}) 1063 | ``` 1064 | 1065 | **Output:** 1066 | ```py 1067 | >>> print(some_list) 1068 | None 1069 | >>> print(some_dict) 1070 | None 1071 | ``` 1072 | 1073 | #### 💡 Explanation 1074 | 1075 | Most methods that modify the items of sequence/mapping objects like `list.append`, `dict.update`, `list.sort`, etc. modify the objects in-place and return `None`. The rationale behind this is to improve performance by avoiding making a copy of the object if the operation can be done in-place (Referred from [here](http://docs.python.org/2/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list)) 1076 | 1077 | --- 1078 | 1079 | ### ▶ Subclass relationships * 1080 | 1081 | **Output:** 1082 | ```py 1083 | >>> from collections import Hashable 1084 | >>> issubclass(list, object) 1085 | True 1086 | >>> issubclass(object, Hashable) 1087 | True 1088 | >>> issubclass(list, Hashable) 1089 | False 1090 | ``` 1091 | 1092 | The Subclass relationships were expected to be transitive, right? (i.e., if `A` is a subclass of `B`, and `B` is a subclass of `C`, the `A` _should_ a subclass of `C`) 1093 | 1094 | #### 💡 Explanation: 1095 | 1096 | * Subclass relationships are not necessarily transitive in Python. Anyone is allowed to define their own, arbitrary `__subclasscheck__` in a metaclass. 1097 | * When `issubclass(cls, Hashable)` is called, it simply looks for non-Falsey "`__hash__`" method in `cls` or anything it inherits from. 1098 | * Since `object` is hashable, but `list` is non-hashable, it breaks the transitivity relation. 1099 | * More detailed explanation can be found [here](https://www.naftaliharris.com/blog/python-subclass-intransitivity/). 1100 | 1101 | --- 1102 | 1103 | ### ▶ The mysterious key type conversion * 1104 | 1105 | ```py 1106 | class SomeClass(str): 1107 | pass 1108 | 1109 | some_dict = {'s':42} 1110 | ``` 1111 | 1112 | **Output:** 1113 | ```py 1114 | >>> type(list(some_dict.keys())[0]) 1115 | str 1116 | >>> s = SomeClass('s') 1117 | >>> some_dict[s] = 40 1118 | >>> some_dict # expected: Two different keys-value pairs 1119 | {'s': 40} 1120 | >>> type(list(some_dict.keys())[0]) 1121 | str 1122 | ``` 1123 | 1124 | #### 💡 Explanation: 1125 | 1126 | * Both the object `s` and the string `"s"` hash to the same value because `SomeClass` inherits the `__hash__` method of `str` class. 1127 | * `SomeClass("s") == "s"` evaluates to `True` because `SomeClass` also inherits `__eq__` method from `str` class. 1128 | * Since both the objects hash to the same value and are equal, they are represented by the same key in the dictionary. 1129 | * For the desired behavior, we can redefine the `__eq__` method in `SomeClass` 1130 | ```py 1131 | class SomeClass(str): 1132 | def __eq__(self, other): 1133 | return ( 1134 | type(self) is SomeClass 1135 | and type(other) is SomeClass 1136 | and super().__eq__(other) 1137 | ) 1138 | 1139 | # When we define a custom __eq__, Python stops automatically inheriting the 1140 | # __hash__ method, so we need to define it as well 1141 | __hash__ = str.__hash__ 1142 | 1143 | some_dict = {'s':42} 1144 | ``` 1145 | 1146 | **Output:** 1147 | ```py 1148 | >>> s = SomeClass('s') 1149 | >>> some_dict[s] = 40 1150 | >>> some_dict 1151 | {'s': 40} 1152 | >>> keys = list(some_dict.keys()) 1153 | >>> type(keys[0]), type(keys[1]) 1154 | (__main__.SomeClass, str) 1155 | ``` 1156 | 1157 | --- 1158 | 1159 | ### ▶ Let's see if you can guess this? 1160 | 1161 | ```py 1162 | a, b = a[b] = {}, 5 1163 | ``` 1164 | 1165 | **Output:** 1166 | ```py 1167 | >>> a 1168 | {5: ({...}, 5)} 1169 | ``` 1170 | 1171 | #### 💡 Explanation: 1172 | 1173 | * According to [Python language reference](https://docs.python.org/2/reference/simple_stmts.html#assignment-statements), assignment statements have the form 1174 | ``` 1175 | (target_list "=")+ (expression_list | yield_expression) 1176 | ``` 1177 | and 1178 | > 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. 1179 | 1180 | * 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`). 1181 | 1182 | * 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`. 1183 | 1184 | * `a` is now assigned to `{}` which is a mutable object. 1185 | 1186 | * The second target list is `a[b]` (you may expect this to throw an error because both `a` and `b` have not been defined in the statements before. But remember, we just assigned `a` to `{}` and `b` to `5`). 1187 | 1188 | * Now, we are setting the key `5` in the dictionary to the tuple `({}, 5)` creating a circular reference (the `{...}` in the output refers to the same object that `a` is already referencing). Another simpler example of circular reference could be 1189 | ```py 1190 | >>> some_list = some_list[0] = [0] 1191 | >>> some_list 1192 | [[...]] 1193 | >>> some_list[0] 1194 | [[...]] 1195 | >>> some_list is some_list[0] 1196 | True 1197 | >>> some_list[0][0][0][0][0][0] == some_list 1198 | True 1199 | ``` 1200 | Similar is the case in our example (`a[b][0]` is the same object as `a`) 1201 | 1202 | * So to sum it up, you can break the example down to 1203 | ```py 1204 | a, b = {}, 5 1205 | a[b] = a, b 1206 | ``` 1207 | And the circular reference can be justified by the fact that `a[b][0]` is the same object as `a` 1208 | ```py 1209 | >>> a[b][0] is a 1210 | True 1211 | ``` 1212 | 1213 | --- 1214 | 1215 | --- 1216 | 1217 | ## Section: Appearances are deceptive! 1218 | 1219 | ### ▶ Skipping lines? 1220 | 1221 | **Output:** 1222 | ```py 1223 | >>> value = 11 1224 | >>> valuе = 32 1225 | >>> value 1226 | 11 1227 | ``` 1228 | 1229 | Wut? 1230 | 1231 | **Note:** The easiest way to reproduce this is to simply copy the statements from the above snippet and paste them into your file/shell. 1232 | 1233 | #### 💡 Explanation 1234 | 1235 | Some non-Western characters look identical to letters in the English alphabet but are considered distinct by the interpreter. 1236 | 1237 | ```py 1238 | >>> ord('е') # cyrillic 'e' (Ye) 1239 | 1077 1240 | >>> ord('e') # latin 'e', as used in English and typed using standard keyboard 1241 | 101 1242 | >>> 'е' == 'e' 1243 | False 1244 | 1245 | >>> value = 42 # latin e 1246 | >>> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here 1247 | >>> value 1248 | 42 1249 | ``` 1250 | 1251 | The built-in `ord()` function returns a character's Unicode [code point](https://en.wikipedia.org/wiki/Code_point), and different code positions of Cyrillic 'e' and Latin 'e' justify the behavior of the above example. 1252 | 1253 | --- 1254 | 1255 | ### ▶ Teleportation * 1256 | 1257 | ```py 1258 | import numpy as np 1259 | 1260 | def energy_send(x): 1261 | # Initializing a numpy array 1262 | np.array([float(x)]) 1263 | 1264 | def energy_receive(): 1265 | # Return an empty numpy array 1266 | return np.empty((), dtype=np.float).tolist() 1267 | ``` 1268 | 1269 | **Output:** 1270 | ```py 1271 | >>> energy_send(123.456) 1272 | >>> energy_receive() 1273 | 123.456 1274 | ``` 1275 | 1276 | Where's the Nobel Prize? 1277 | 1278 | #### 💡 Explanation: 1279 | 1280 | * Notice that the numpy array created in the `energy_send` function is not returned, so that memory space is free to reallocate. 1281 | * `numpy.empty()` returns the next free memory slot without reinitializing it. This memory spot just happens to be the same one that was just freed (usually, but not always). 1282 | 1283 | --- 1284 | 1285 | ### ▶ Well, something is fishy... 1286 | 1287 | ```py 1288 | def square(x): 1289 | """ 1290 | A simple function to calculate the square of a number by addition. 1291 | """ 1292 | sum_so_far = 0 1293 | for counter in range(x): 1294 | sum_so_far = sum_so_far + x 1295 | return sum_so_far 1296 | ``` 1297 | 1298 | **Output (Python 2.x):** 1299 | 1300 | ```py 1301 | >>> square(10) 1302 | 10 1303 | ``` 1304 | 1305 | Shouldn't that be 100? 1306 | 1307 | **Note:** If you're not able to reproduce this, try running the file [mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py) via the shell. 1308 | 1309 | #### 💡 Explanation 1310 | 1311 | * **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. 1312 | * This is how Python handles tabs: 1313 | > 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 <...> 1314 | * So the "tab" at the last line of `square` function is replaced with eight spaces, and it gets into the loop. 1315 | * Python 3 is kind enough to throw an error for such cases automatically. 1316 | 1317 | **Output (Python 3.x):** 1318 | ```py 1319 | TabError: inconsistent use of tabs and spaces in indentation 1320 | ``` 1321 | 1322 | --- 1323 | 1324 | --- 1325 | 1326 | ## Section: Watch out for the landmines! 1327 | 1328 | 1329 | ### ▶ Modifying a dictionary while iterating over it 1330 | 1331 | ```py 1332 | x = {0: None} 1333 | 1334 | for i in x: 1335 | del x[i] 1336 | x[i+1] = None 1337 | print(i) 1338 | ``` 1339 | 1340 | **Output (Python 2.7- Python 3.5):** 1341 | 1342 | ``` 1343 | 0 1344 | 1 1345 | 2 1346 | 3 1347 | 4 1348 | 5 1349 | 6 1350 | 7 1351 | ``` 1352 | 1353 | Yes, it runs for exactly **eight** times and stops. 1354 | 1355 | #### 💡 Explanation: 1356 | 1357 | * Iteration over a dictionary that you edit at the same time is not supported. 1358 | * 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. 1359 | * How deleted keys are handled and when the resize occurs might be different for different Python implementations. 1360 | * For more information, you may refer to this StackOverflow [thread](https://stackoverflow.com/questions/44763802/bug-in-python-dict) explaining a similar example in detail. 1361 | 1362 | --- 1363 | 1364 | ### ▶ Stubborn `del` operator * 1365 | 1366 | ```py 1367 | class SomeClass: 1368 | def __del__(self): 1369 | print("Deleted!") 1370 | ``` 1371 | 1372 | **Output:** 1373 | 1\. 1374 | ```py 1375 | >>> x = SomeClass() 1376 | >>> y = x 1377 | >>> del x # this should print "Deleted!" 1378 | >>> del y 1379 | Deleted! 1380 | ``` 1381 | 1382 | Phew, deleted at last. You might have guessed what saved from `__del__` being called in our first attempt to delete `x`. Let's add more twist to the example. 1383 | 1384 | 2\. 1385 | ```py 1386 | >>> x = SomeClass() 1387 | >>> y = x 1388 | >>> del x 1389 | >>> y # check if y exists 1390 | <__main__.SomeClass instance at 0x7f98a1a67fc8> 1391 | >>> del y # Like previously, this should print "Deleted!" 1392 | >>> globals() # oh, it didn't. Let's check all our global variables and confirm 1393 | Deleted! 1394 | {'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None} 1395 | ``` 1396 | 1397 | Okay, now it's deleted :confused: 1398 | 1399 | #### 💡 Explanation: 1400 | + `del x` doesn’t directly call `x.__del__()`. 1401 | + Whenever `del x` is encountered, Python decrements the reference count for `x` by one, and `x.__del__()` when x’s reference count reaches zero. 1402 | + In the second output snippet, `y.__del__()` was not called because the previous statement (`>>> y`) in the interactive interpreter created another reference to the same object, thus preventing the reference count to reach zero when `del y` was encountered. 1403 | + Calling `globals` caused the existing reference to be destroyed and hence we can see "Deleted!" being printed (finally!). 1404 | 1405 | --- 1406 | 1407 | ### ▶ Deleting a list item while iterating 1408 | 1409 | ```py 1410 | list_1 = [1, 2, 3, 4] 1411 | list_2 = [1, 2, 3, 4] 1412 | list_3 = [1, 2, 3, 4] 1413 | list_4 = [1, 2, 3, 4] 1414 | 1415 | for idx, item in enumerate(list_1): 1416 | del item 1417 | 1418 | for idx, item in enumerate(list_2): 1419 | list_2.remove(item) 1420 | 1421 | for idx, item in enumerate(list_3[:]): 1422 | list_3.remove(item) 1423 | 1424 | for idx, item in enumerate(list_4): 1425 | list_4.pop(idx) 1426 | ``` 1427 | 1428 | **Output:** 1429 | ```py 1430 | >>> list_1 1431 | [1, 2, 3, 4] 1432 | >>> list_2 1433 | [2, 4] 1434 | >>> list_3 1435 | [] 1436 | >>> list_4 1437 | [2, 4] 1438 | ``` 1439 | 1440 | Can you guess why the output is `[2, 4]`? 1441 | 1442 | #### 💡 Explanation: 1443 | 1444 | * It's never a good idea to change the object you're iterating over. The correct way to do so is to iterate over a copy of the object instead, and `list_3[:]` does just that. 1445 | 1446 | ```py 1447 | >>> some_list = [1, 2, 3, 4] 1448 | >>> id(some_list) 1449 | 139798789457608 1450 | >>> id(some_list[:]) # Notice that python creates new object for sliced list. 1451 | 139798779601192 1452 | ``` 1453 | 1454 | **Difference between `del`, `remove`, and `pop`:** 1455 | * `del var_name` just removes the binding of the `var_name` from the local or global namespace (That's why the `list_1` is unaffected). 1456 | * `remove` removes the first matching value, not a specific index, raises `ValueError` if the value is not found. 1457 | * `pop` removes the element at a specific index and returns it, raises `IndexError` if an invalid index is specified. 1458 | 1459 | **Why the output is `[2, 4]`?** 1460 | - The list iteration is done index by index, and when we remove `1` from `list_2` or `list_4`, the contents of the lists are now `[2, 3, 4]`. The remaining elements are shifted down, i.e., `2` is at index 0, and `3` is at index 1. Since the next iteration is going to look at index 1 (which is the `3`), the `2` gets skipped entirely. A similar thing will happen with every alternate element in the list sequence. 1461 | 1462 | * Refer to this StackOverflow [thread](https://stackoverflow.com/questions/45946228/what-happens-when-you-try-to-delete-a-list-element-while-iterating-over-it) explaining the example 1463 | * See also this nice StackOverflow [thread](https://stackoverflow.com/questions/45877614/how-to-change-all-the-dictionary-keys-in-a-for-loop-with-d-items) for a similar example related to dictionaries in Python. 1464 | 1465 | --- 1466 | 1467 | ### ▶ Loop variables leaking out! 1468 | 1469 | 1\. 1470 | ```py 1471 | for x in range(7): 1472 | if x == 6: 1473 | print(x, ': for x inside loop') 1474 | print(x, ': x in global') 1475 | ``` 1476 | 1477 | **Output:** 1478 | ```py 1479 | 6 : for x inside loop 1480 | 6 : x in global 1481 | ``` 1482 | 1483 | But `x` was never defined outside the scope of for loop... 1484 | 1485 | 2\. 1486 | ```py 1487 | # This time let's initialize x first 1488 | x = -1 1489 | for x in range(7): 1490 | if x == 6: 1491 | print(x, ': for x inside loop') 1492 | print(x, ': x in global') 1493 | ``` 1494 | 1495 | **Output:** 1496 | ```py 1497 | 6 : for x inside loop 1498 | 6 : x in global 1499 | ``` 1500 | 1501 | 3\. 1502 | ``` 1503 | x = 1 1504 | print([x for x in range(5)]) 1505 | print(x, ': x in global') 1506 | ``` 1507 | 1508 | **Output (on Python 2.x):** 1509 | ``` 1510 | [0, 1, 2, 3, 4] 1511 | (4, ': x in global') 1512 | ``` 1513 | 1514 | **Output (on Python 3.x):** 1515 | ``` 1516 | [0, 1, 2, 3, 4] 1517 | 1 : x in global 1518 | ``` 1519 | 1520 | #### 💡 Explanation: 1521 | 1522 | - In Python, for-loops use the scope they exist in and leave their defined loop-variable behind. This also applies if we explicitly defined the for-loop variable in the global namespace before. In this case, it will rebind the existing variable. 1523 | 1524 | - The differences in the output of Python 2.x and Python 3.x interpreters for list comprehension example can be explained by following change documented in [What’s New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html) documentation: 1525 | 1526 | > "List comprehensions no longer support the syntactic form `[... for var in item1, item2, ...]`. Use `[... for var in (item1, item2, ...)]` instead. Also, note that list comprehensions have different semantics: they are closer to syntactic sugar for a generator expression inside a `list()` constructor, and in particular the loop control variables are no longer leaked into the surrounding scope." 1527 | 1528 | --- 1529 | 1530 | ### ▶ Beware of default mutable arguments! 1531 | 1532 | ```py 1533 | def some_func(default_arg=[]): 1534 | default_arg.append("some_string") 1535 | return default_arg 1536 | ``` 1537 | 1538 | **Output:** 1539 | ```py 1540 | >>> some_func() 1541 | ['some_string'] 1542 | >>> some_func() 1543 | ['some_string', 'some_string'] 1544 | >>> some_func([]) 1545 | ['some_string'] 1546 | >>> some_func() 1547 | ['some_string', 'some_string', 'some_string'] 1548 | ``` 1549 | 1550 | #### 💡 Explanation: 1551 | 1552 | - The default mutable arguments of functions in Python aren't really initialized every time you call the function. Instead, the recently assigned value to them is used as the default value. When we explicitly passed `[]` to `some_func` as the argument, the default value of the `default_arg` variable was not used, so the function returned as expected. 1553 | 1554 | ```py 1555 | def some_func(default_arg=[]): 1556 | default_arg.append("some_string") 1557 | return default_arg 1558 | ``` 1559 | 1560 | **Output:** 1561 | ```py 1562 | >>> some_func.__defaults__ #This will show the default argument values for the function 1563 | ([],) 1564 | >>> some_func() 1565 | >>> some_func.__defaults__ 1566 | (['some_string'],) 1567 | >>> some_func() 1568 | >>> some_func.__defaults__ 1569 | (['some_string', 'some_string'],) 1570 | >>> some_func([]) 1571 | >>> some_func.__defaults__ 1572 | (['some_string', 'some_string'],) 1573 | ``` 1574 | 1575 | - A common practice to avoid bugs due to mutable arguments is to assign `None` as the default value and later check if any value is passed to the function corresponding to that argument. Example: 1576 | 1577 | ```py 1578 | def some_func(default_arg=None): 1579 | if not default_arg: 1580 | default_arg = [] 1581 | default_arg.append("some_string") 1582 | return default_arg 1583 | ``` 1584 | 1585 | --- 1586 | 1587 | ### ▶ Catching the Exceptions 1588 | 1589 | ```py 1590 | some_list = [1, 2, 3] 1591 | try: 1592 | # This should raise an ``IndexError`` 1593 | print(some_list[4]) 1594 | except IndexError, ValueError: 1595 | print("Caught!") 1596 | 1597 | try: 1598 | # This should raise a ``ValueError`` 1599 | some_list.remove(4) 1600 | except IndexError, ValueError: 1601 | print("Caught again!") 1602 | ``` 1603 | 1604 | **Output (Python 2.x):** 1605 | ```py 1606 | Caught! 1607 | 1608 | ValueError: list.remove(x): x not in list 1609 | ``` 1610 | 1611 | **Output (Python 3.x):** 1612 | ```py 1613 | File "", line 3 1614 | except IndexError, ValueError: 1615 | ^ 1616 | SyntaxError: invalid syntax 1617 | ``` 1618 | 1619 | #### 💡 Explanation 1620 | 1621 | * To add multiple Exceptions to the except clause, you need to pass them as parenthesized tuple as the first argument. The second argument is an optional name, which when supplied will bind the Exception instance that has been raised. Example, 1622 | ```py 1623 | some_list = [1, 2, 3] 1624 | try: 1625 | # This should raise a ``ValueError`` 1626 | some_list.remove(4) 1627 | except (IndexError, ValueError), e: 1628 | print("Caught again!") 1629 | print(e) 1630 | ``` 1631 | **Output (Python 2.x):** 1632 | ``` 1633 | Caught again! 1634 | list.remove(x): x not in list 1635 | ``` 1636 | **Output (Python 3.x):** 1637 | ```py 1638 | File "", line 4 1639 | except (IndexError, ValueError), e: 1640 | ^ 1641 | IndentationError: unindent does not match any outer indentation level 1642 | ``` 1643 | 1644 | * Separating the exception from the variable with a comma is deprecated and does not work in Python 3; the correct way is to use `as`. Example, 1645 | ```py 1646 | some_list = [1, 2, 3] 1647 | try: 1648 | some_list.remove(4) 1649 | 1650 | except (IndexError, ValueError) as e: 1651 | print("Caught again!") 1652 | print(e) 1653 | ``` 1654 | **Output:** 1655 | ``` 1656 | Caught again! 1657 | list.remove(x): x not in list 1658 | ``` 1659 | 1660 | --- 1661 | 1662 | ### ▶ Same operands, different story! 1663 | 1664 | 1\. 1665 | ```py 1666 | a = [1, 2, 3, 4] 1667 | b = a 1668 | a = a + [5, 6, 7, 8] 1669 | ``` 1670 | 1671 | **Output:** 1672 | ```py 1673 | >>> a 1674 | [1, 2, 3, 4, 5, 6, 7, 8] 1675 | >>> b 1676 | [1, 2, 3, 4] 1677 | ``` 1678 | 1679 | 2\. 1680 | ```py 1681 | a = [1, 2, 3, 4] 1682 | b = a 1683 | a += [5, 6, 7, 8] 1684 | ``` 1685 | 1686 | **Output:** 1687 | ```py 1688 | >>> a 1689 | [1, 2, 3, 4, 5, 6, 7, 8] 1690 | >>> b 1691 | [1, 2, 3, 4, 5, 6, 7, 8] 1692 | ``` 1693 | 1694 | #### 💡 Explanation: 1695 | 1696 | * `a += b` doesn't always behave the same way as `a = a + b`. Classes *may* implement the *`op=`* operators differently, and lists do this. 1697 | 1698 | * The expression `a = a + [5,6,7,8]` generates a new list and sets `a`'s reference to that new list, leaving `b` unchanged. 1699 | 1700 | * The expression `a + =[5,6,7,8]` is actually mapped to an "extend" function that operates on the list such that `a` and `b` still point to the same list that has been modified in-place. 1701 | 1702 | --- 1703 | 1704 | ### ▶ The out of scope variable 1705 | 1706 | ```py 1707 | a = 1 1708 | def some_func(): 1709 | return a 1710 | 1711 | def another_func(): 1712 | a += 1 1713 | return a 1714 | ``` 1715 | 1716 | **Output:** 1717 | ```py 1718 | >>> some_func() 1719 | 1 1720 | >>> another_func() 1721 | UnboundLocalError: local variable 'a' referenced before assignment 1722 | ``` 1723 | 1724 | #### 💡 Explanation: 1725 | * When you make an assignment to a variable in scope, it becomes local to that scope. So `a` becomes local to the scope of `another_func`, but it has not been initialized previously in the same scope which throws an error. 1726 | * Read [this](http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html) short but an awesome guide to learn more about how namespaces and scope resolution works in Python. 1727 | * To modify the outer scope variable `a` in `another_func`, use `global` keyword. 1728 | ```py 1729 | def another_func() 1730 | global a 1731 | a += 1 1732 | return a 1733 | ``` 1734 | 1735 | **Output:** 1736 | ```py 1737 | >>> another_func() 1738 | 2 1739 | ``` 1740 | 1741 | --- 1742 | 1743 | ### ▶ Be careful with chained operations 1744 | 1745 | ```py 1746 | >>> (False == False) in [False] # makes sense 1747 | False 1748 | >>> False == (False in [False]) # makes sense 1749 | False 1750 | >>> False == False in [False] # now what? 1751 | True 1752 | 1753 | >>> True is False == False 1754 | False 1755 | >>> False is False is False 1756 | True 1757 | 1758 | >>> 1 > 0 < 1 1759 | True 1760 | >>> (1 > 0) < 1 1761 | False 1762 | >>> 1 > (0 < 1) 1763 | False 1764 | ``` 1765 | 1766 | #### 💡 Explanation: 1767 | 1768 | As per https://docs.python.org/2/reference/expressions.html#not-in 1769 | 1770 | > 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. 1771 | 1772 | 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`. 1773 | 1774 | * `False is False is False` is equivalent to `(False is False) and (False is False)` 1775 | * `True is False == False` is equivalent to `True is False and False == False` and since the first part of the statement (`True is False`) evaluates to `False`, the overall expression evaluates to `False`. 1776 | * `1 > 0 < 1` is equivalent to `1 > 0 and 0 < 1` which evaluates to `True`. 1777 | * The expression `(1 > 0) < 1` is equivalent to `True < 1` and 1778 | ```py 1779 | >>> int(True) 1780 | 1 1781 | >>> True + 1 #not relevant for this example, but just for fun 1782 | 2 1783 | ``` 1784 | So, `1 < 1` evaluates to `False` 1785 | 1786 | --- 1787 | 1788 | ### ▶ Name resolution ignoring class scope 1789 | 1790 | 1\. 1791 | ```py 1792 | x = 5 1793 | class SomeClass: 1794 | x = 17 1795 | y = (x for i in range(10)) 1796 | ``` 1797 | 1798 | **Output:** 1799 | ```py 1800 | >>> list(SomeClass.y)[0] 1801 | 5 1802 | ``` 1803 | 1804 | 2\. 1805 | ```py 1806 | x = 5 1807 | class SomeClass: 1808 | x = 17 1809 | y = [x for i in range(10)] 1810 | ``` 1811 | 1812 | **Output (Python 2.x):** 1813 | ```py 1814 | >>> SomeClass.y[0] 1815 | 17 1816 | ``` 1817 | 1818 | **Output (Python 3.x):** 1819 | ```py 1820 | >>> SomeClass.y[0] 1821 | 5 1822 | ``` 1823 | 1824 | #### 💡 Explanation 1825 | - Scopes nested inside class definition ignore names bound at the class level. 1826 | - A generator expression has its own scope. 1827 | - Starting from Python 3.X, list comprehensions also have their own scope. 1828 | 1829 | --- 1830 | 1831 | ### ▶ Needle in a Haystack 1832 | 1833 | 1\. 1834 | ```py 1835 | x, y = (0, 1) if True else None, None 1836 | ``` 1837 | 1838 | **Output:** 1839 | ``` 1840 | >>> x, y # expected (0, 1) 1841 | ((0, 1), None) 1842 | ``` 1843 | 1844 | Almost every Python programmer has faced a similar situation. 1845 | 1846 | 2\. 1847 | ```py 1848 | t = ('one', 'two') 1849 | for i in t: 1850 | print(i) 1851 | 1852 | t = ('one') 1853 | for i in t: 1854 | print(i) 1855 | 1856 | t = () 1857 | print(t) 1858 | ``` 1859 | 1860 | **Output:** 1861 | ```py 1862 | one 1863 | two 1864 | o 1865 | n 1866 | e 1867 | tuple() 1868 | ``` 1869 | 1870 | #### 💡 Explanation: 1871 | * For 1, the correct statement for expected behavior is `x, y = (0, 1) if True else (None, None)`. 1872 | * For 2, the correct statement for expected behavior is `t = ('one',)` or `t = 'one',` (missing comma) otherwise the interpreter considers `t` to be a `str` and iterates over it character by character. 1873 | * `()` is a special token and denotes empty `tuple`. 1874 | 1875 | --- 1876 | 1877 | --- 1878 | 1879 | 1880 | ## Section: The Hidden treasures! 1881 | 1882 | This section contains few of the lesser-known interesting things about Python that most beginners like me are unaware of (well, not anymore). 1883 | 1884 | ### ▶ Okay Python, Can you make me fly? * 1885 | 1886 | Well, here you go 1887 | 1888 | ```py 1889 | import antigravity 1890 | ``` 1891 | 1892 | **Output:** 1893 | Sshh.. It's a super secret. 1894 | 1895 | #### 💡 Explanation: 1896 | + `antigravity` module is one of the few easter eggs released by Python developers. 1897 | + `import antigravity` opens up a web browser pointing to the [classic XKCD comic](http://xkcd.com/353/) about Python. 1898 | + Well, there's more to it. There's **another easter egg inside the easter egg**. If look at the [code](https://github.com/python/cpython/blob/master/Lib/antigravity.py#L7-L17), there's a function defined that purports to implement the [XKCD's geohashing algorithm](https://xkcd.com/426/). 1899 | 1900 | --- 1901 | 1902 | ### ▶ `goto`, but why? * 1903 | 1904 | ```py 1905 | from goto import goto, label 1906 | for i in range(9): 1907 | for j in range(9): 1908 | for k in range(9): 1909 | print("I'm trapped, please rescue!") 1910 | if k == 2: 1911 | goto .breakout # breaking out from a deeply nested loop 1912 | label .breakout 1913 | print("Freedom!") 1914 | ``` 1915 | 1916 | **Output (Python 2.3):** 1917 | ```py 1918 | I'm trapped, please rescue! 1919 | I'm trapped, please rescue! 1920 | Freedom! 1921 | ``` 1922 | 1923 | #### 💡 Explanation: 1924 | - 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. 1925 | - Current versions of Python do not have this module. 1926 | - 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. 1927 | 1928 | --- 1929 | 1930 | ### ▶ Brace yourself! * 1931 | 1932 | 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, 1933 | 1934 | ```py 1935 | from __future__ import braces 1936 | ``` 1937 | 1938 | **Output:** 1939 | ```py 1940 | File "some_file.py", line 1 1941 | from __future__ import braces 1942 | SyntaxError: not a chance 1943 | ``` 1944 | 1945 | Braces? No way! If you think that's disappointing, use Java. 1946 | 1947 | #### 💡 Explanation: 1948 | + The `__future__` module is normally used to provide features from future versions of Python. The "future" here is however ironic. 1949 | + This is an easter egg concerned with the community's feelings on this issue. 1950 | 1951 | --- 1952 | 1953 | ### ▶ Let's meet Friendly Language Uncle For Life * 1954 | 1955 | **Output (Python 3.x)** 1956 | ```py 1957 | >>> from __future__ import barry_as_FLUFL 1958 | >>> "Ruby" != "Python" # there's no doubt about it 1959 | File "some_file.py", line 1 1960 | "Ruby" != "Python" 1961 | ^ 1962 | SyntaxError: invalid syntax 1963 | 1964 | >>> "Ruby" <> "Python" 1965 | True 1966 | ``` 1967 | 1968 | There we go. 1969 | 1970 | #### 💡 Explanation: 1971 | - 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). 1972 | - Quoting from the PEP-401 1973 | > 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. 1974 | - 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/). 1975 | 1976 | --- 1977 | 1978 | ### ▶ Even Python understands that love is complicated * 1979 | 1980 | ```py 1981 | import this 1982 | ``` 1983 | 1984 | Wait, what's **this**? `this` is love :heart: 1985 | 1986 | **Output:** 1987 | ``` 1988 | The Zen of Python, by Tim Peters 1989 | 1990 | Beautiful is better than ugly. 1991 | Explicit is better than implicit. 1992 | Simple is better than complex. 1993 | Complex is better than complicated. 1994 | Flat is better than nested. 1995 | Sparse is better than dense. 1996 | Readability counts. 1997 | Special cases aren't special enough to break the rules. 1998 | Although practicality beats purity. 1999 | Errors should never pass silently. 2000 | Unless explicitly silenced. 2001 | In the face of ambiguity, refuse the temptation to guess. 2002 | There should be one-- and preferably only one --obvious way to do it. 2003 | Although that way may not be obvious at first unless you're Dutch. 2004 | Now is better than never. 2005 | Although never is often better than *right* now. 2006 | If the implementation is hard to explain, it's a bad idea. 2007 | If the implementation is easy to explain, it may be a good idea. 2008 | Namespaces are one honking great idea -- let's do more of those! 2009 | ``` 2010 | 2011 | It's the Zen of Python! 2012 | 2013 | ```py 2014 | >>> love = this 2015 | >>> this is love 2016 | True 2017 | >>> love is True 2018 | False 2019 | >>> love is False 2020 | False 2021 | >>> love is not True or False 2022 | True 2023 | >>> love is not True or False; love is love # Love is complicated 2024 | True 2025 | ``` 2026 | 2027 | #### 💡 Explanation: 2028 | 2029 | * `this` module in Python is an easter egg for The Zen Of Python ([PEP 20](https://www.python.org/dev/peps/pep-0020)). 2030 | * 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). 2031 | * Regarding the statement `love is not True or False; love is love`, ironic but it's self-explanatory. 2032 | 2033 | --- 2034 | 2035 | ### ▶ Yes, it exists! 2036 | 2037 | **The `else` clause for loops.** One typical example might be: 2038 | 2039 | ```py 2040 | def does_exists_num(l, to_find): 2041 | for num in l: 2042 | if num == to_find: 2043 | print("Exists!") 2044 | break 2045 | else: 2046 | print("Does not exist") 2047 | ``` 2048 | 2049 | **Output:** 2050 | ```py 2051 | >>> some_list = [1, 2, 3, 4, 5] 2052 | >>> does_exists_num(some_list, 4) 2053 | Exists! 2054 | >>> does_exists_num(some_list, -1) 2055 | Does not exist 2056 | ``` 2057 | 2058 | **The `else` clause in exception handling.** An example, 2059 | 2060 | ```py 2061 | try: 2062 | pass 2063 | except: 2064 | print("Exception occurred!!!") 2065 | else: 2066 | print("Try block executed successfully...") 2067 | ``` 2068 | 2069 | **Output:** 2070 | ```py 2071 | Try block executed successfully... 2072 | ``` 2073 | 2074 | #### 💡 Explanation: 2075 | - The `else` clause after a loop is executed only when there's no explicit `break` after all the iterations. 2076 | - `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. 2077 | 2078 | --- 2079 | 2080 | ### ▶ Inpinity * 2081 | 2082 | The spelling is intended. Please, don't submit a patch for this. 2083 | 2084 | **Output (Python 3.x):** 2085 | ```py 2086 | >>> infinity = float('infinity') 2087 | >>> hash(infinity) 2088 | 314159 2089 | >>> hash(float('-inf')) 2090 | -314159 2091 | ``` 2092 | 2093 | #### 💡 Explanation: 2094 | - Hash of infinity is 10⁵ x π. 2095 | - Interestingly, the hash of `float('-inf')` is "-10⁵ x π" in Python 3, whereas "-10⁵ x e" in Python 2. 2096 | 2097 | --- 2098 | 2099 | ### ▶ Mangling time! * 2100 | 2101 | ```py 2102 | class Yo(object): 2103 | def __init__(self): 2104 | self.__honey = True 2105 | self.bitch = True 2106 | ``` 2107 | 2108 | **Output:** 2109 | ```py 2110 | >>> Yo().bitch 2111 | True 2112 | >>> Yo().__honey 2113 | AttributeError: 'Yo' object has no attribute '__honey' 2114 | >>> Yo()._Yo__honey 2115 | True 2116 | ``` 2117 | 2118 | Why did `Yo()._Yo__honey` worked? Only Indian readers would understand. 2119 | 2120 | #### 💡 Explanation: 2121 | 2122 | * [Name Mangling](https://en.wikipedia.org/wiki/Name_mangling) is used to avoid naming collisions between different namespaces. 2123 | * 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. 2124 | * 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. 2125 | 2126 | --- 2127 | 2128 | --- 2129 | 2130 | ## Section: Miscallaneous 2131 | 2132 | 2133 | ### ▶ `+=` is faster 2134 | 2135 | ```py 2136 | # using "+", three strings: 2137 | >>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100) 2138 | 0.25748300552368164 2139 | # using "+=", three strings: 2140 | >>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100) 2141 | 0.012188911437988281 2142 | ``` 2143 | 2144 | #### 💡 Explanation: 2145 | + `+=` 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. 2146 | 2147 | --- 2148 | 2149 | ### ▶ Let's make a giant string! 2150 | 2151 | ```py 2152 | def add_string_with_plus(iters): 2153 | s = "" 2154 | for i in range(iters): 2155 | s += "xyz" 2156 | assert len(s) == 3*iters 2157 | 2158 | def add_bytes_with_plus(iters): 2159 | s = b"" 2160 | for i in range(iters): 2161 | s += b"xyz" 2162 | assert len(s) == 3*iters 2163 | 2164 | def add_string_with_format(iters): 2165 | fs = "{}"*iters 2166 | s = fs.format(*(["xyz"]*iters)) 2167 | assert len(s) == 3*iters 2168 | 2169 | def add_string_with_join(iters): 2170 | l = [] 2171 | for i in range(iters): 2172 | l.append("xyz") 2173 | s = "".join(l) 2174 | assert len(s) == 3*iters 2175 | 2176 | def convert_list_to_string(l, iters): 2177 | s = "".join(l) 2178 | assert len(s) == 3*iters 2179 | ``` 2180 | 2181 | **Output:** 2182 | ```py 2183 | >>> timeit(add_string_with_plus(10000)) 2184 | 1000 loops, best of 3: 972 µs per loop 2185 | >>> timeit(add_bytes_with_plus(10000)) 2186 | 1000 loops, best of 3: 815 µs per loop 2187 | >>> timeit(add_string_with_format(10000)) 2188 | 1000 loops, best of 3: 508 µs per loop 2189 | >>> timeit(add_string_with_join(10000)) 2190 | 1000 loops, best of 3: 878 µs per loop 2191 | >>> l = ["xyz"]*10000 2192 | >>> timeit(convert_list_to_string(l, 10000)) 2193 | 10000 loops, best of 3: 80 µs per loop 2194 | ``` 2195 | 2196 | Let's increase the number of iterations by a factor of 10. 2197 | 2198 | ```py 2199 | >>> timeit(add_string_with_plus(100000)) # Linear increase in execution time 2200 | 100 loops, best of 3: 9.75 ms per loop 2201 | >>> timeit(add_bytes_with_plus(100000)) # Quadratic increase 2202 | 1000 loops, best of 3: 974 ms per loop 2203 | >>> timeit(add_string_with_format(100000)) # Linear increase 2204 | 100 loops, best of 3: 5.25 ms per loop 2205 | >>> timeit(add_string_with_join(100000)) # Linear increase 2206 | 100 loops, best of 3: 9.85 ms per loop 2207 | >>> l = ["xyz"]*100000 2208 | >>> timeit(convert_list_to_string(l, 100000)) # Linear increase 2209 | 1000 loops, best of 3: 723 µs per loop 2210 | ``` 2211 | 2212 | #### 💡 Explanation 2213 | - 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. 2214 | - 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) 2215 | - Therefore, it's advised to use `.format.` or `%` syntax (however, they are slightly slower than `+` for short strings). 2216 | - Or better, if already you've contents available in the form of an iterable object, then use `''.join(iterable_object)` which is much faster. 2217 | - `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. 2218 | ```py 2219 | def add_string_with_plus(iters): 2220 | s = "" 2221 | for i in range(iters): 2222 | s = s + "x" + "y" + "z" 2223 | assert len(s) == 3*iters 2224 | 2225 | >>> timeit(add_string_with_plus(10000)) 2226 | 100 loops, best of 3: 9.87 ms per loop 2227 | >>> timeit(add_string_with_plus(100000)) # Quadratic increase in execution time 2228 | 1 loops, best of 3: 1.09 s per loop 2229 | ``` 2230 | 2231 | --- 2232 | 2233 | ### ▶ Explicit typecast of strings 2234 | 2235 | ```py 2236 | a = float('inf') 2237 | b = float('nan') 2238 | c = float('-iNf') #These strings are case-insensitive 2239 | d = float('nan') 2240 | ``` 2241 | 2242 | **Output:** 2243 | ```py 2244 | >>> a 2245 | inf 2246 | >>> b 2247 | nan 2248 | >>> c 2249 | -inf 2250 | >>> float('some_other_string') 2251 | ValueError: could not convert string to float: some_other_string 2252 | >>> a == -c #inf==inf 2253 | True 2254 | >>> None == None # None==None 2255 | True 2256 | >>> b == d #but nan!=nan 2257 | False 2258 | >>> 50/a 2259 | 0.0 2260 | >>> a/a 2261 | nan 2262 | >>> 23 + b 2263 | nan 2264 | ``` 2265 | 2266 | #### 💡 Explanation: 2267 | 2268 | `'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. 2269 | 2270 | --- 2271 | 2272 | ### ▶ Minor Ones 2273 | 2274 | * `join()` is a string operation instead of list operation. (sort of counter-intuitive at first usage) 2275 | 2276 | **💡 Explanation:** 2277 | 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. 2278 | 2279 | * Few weird looking but semantically correct statements: 2280 | + `[] = ()` is a semantically correct statement (unpacking an empty `tuple` into an empty `list`) 2281 | + `'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. 2282 | + `3 --0-- 5 == 8` and `--5 == 5` are both semantically correct statements and evaluate to `True`. 2283 | 2284 | * 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. 2285 | ```py 2286 | >>> a = 5 2287 | >>> a 2288 | 5 2289 | >>> ++a 2290 | 5 2291 | >>> --a 2292 | 5 2293 | ``` 2294 | 2295 | **💡 Explanation:** 2296 | + There is no `++` operator in Python grammar. It is actually two `+` operators. 2297 | + `++a` parses as `+(+a)` which translates to `a`. Similarly, the output of the statement `--a` can be justified. 2298 | + 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. 2299 | 2300 | * 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!): 2301 | ```py 2302 | import dis 2303 | exec(""" 2304 | def f(): 2305 | """ + """ 2306 | """.join(["X"+str(x)+"=" + str(x) for x in range(65539)])) 2307 | 2308 | f() 2309 | 2310 | print(dis.dis(f)) 2311 | ``` 2312 | 2313 | * 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. 2314 | 2315 | * List slicing with out of the bounds indices throws no errors 2316 | ```py 2317 | >>> some_list = [1, 2, 3, 4, 5] 2318 | >>> some_list[111:] 2319 | [] 2320 | ``` 2321 | 2322 | * `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. 2323 | 2324 | * `'abc'.count('') == 4`. Here's an approximate implementation of `count` method, which would make the things more clear 2325 | ```py 2326 | def count(s, sub): 2327 | result = 0 2328 | for i in range(len(s) + 1 - len(sub)): 2329 | result += (s[i:i + len(sub)] == sub) 2330 | return result 2331 | ``` 2332 | The behavior is due to the matching of empty substring(`''`) with slices of length 0 in the original string. 2333 | 2334 | --- 2335 | 2336 | # Contributing 2337 | 2338 | All patches are Welcome! Please see [CONTRIBUTING.md](/CONTRIBUTING.md) for further details. 2339 | 2340 | 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) 2341 | 2342 | # Acknowledgements 2343 | 2344 | 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. 2345 | 2346 | #### Some nice Links! 2347 | * https://www.youtube.com/watch?v=sH4XF6pKKmk 2348 | * https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python 2349 | * https://sopython.com/wiki/Common_Gotchas_In_Python 2350 | * https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines 2351 | * https://stackoverflow.com/questions/1011431/common-pitfalls-in-python 2352 | * https://www.python.org/doc/humor/ 2353 | * https://www.satwikkansal.xyz/archives/posts/python/My-Python-archives/ 2354 | 2355 | # 🎓 License 2356 | 2357 | [![CC 4.0][license-image]][license-url] 2358 | 2359 | © [Satwik Kansal](https://satwikkansal.xyz) 2360 | 2361 | [license-url]: http://www.wtfpl.net 2362 | [license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square 2363 | 2364 | ## Help 2365 | 2366 | If you have any wtfs, ideas or suggestions, please share. 2367 | 2368 | ## Want to share wtfpython with friends? 2369 | 2370 | You can use these quick links for Twitter and Linkedin. 2371 | 2372 | [Twitter](https://twitter.com/intent/tweet?url=https://github.com/satwikkansal/wtfpython&hastags=python,wtfpython) | 2373 | [Linkedin](https://www.linkedin.com/shareArticle?url=https://github.com/satwikkansal&title=What%20the%20f*ck%20Python!&summary=An%20interesting%20collection%20of%20subtle%20and%20tricky%20Python%20snippets.) 2374 | 2375 | ## Need a pdf version? 2376 | 2377 | 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. 2378 | -------------------------------------------------------------------------------- /wtfpython-pypi/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | if __name__ == "__main__": 4 | setup(name='wtfpython', 5 | version='0.2', 6 | description='What the f*ck Python!', 7 | author="Satwik Kansal", 8 | maintainer="Satwik Kansal", 9 | maintainer_email='satwikkansal@gmail.com', 10 | url='https://github.com/satwikkansal/wtfpython', 11 | platforms='any', 12 | license="WTFPL 2.0", 13 | long_description="An interesting collection of subtle & tricky Python Snippets" 14 | " and features.", 15 | keywords="wtfpython gotchas snippets tricky", 16 | packages=find_packages(), 17 | entry_points = { 18 | 'console_scripts': ['wtfpython = wtf_python.main:load_and_read'] 19 | }, 20 | classifiers=[ 21 | 'Development Status :: 4 - Beta', 22 | 23 | 'Environment :: Console', 24 | 'Environment :: MacOS X', 25 | 'Environment :: Win32 (MS Windows)', 26 | 27 | 'Intended Audience :: Science/Research', 28 | 'Intended Audience :: Developers', 29 | 'Intended Audience :: Education', 30 | 'Intended Audience :: End Users/Desktop', 31 | 32 | 'Operating System :: OS Independent', 33 | 34 | 'Programming Language :: Python :: 3', 35 | 'Programming Language :: Python :: 2', 36 | 37 | 'Topic :: Documentation', 38 | 'Topic :: Education', 39 | 'Topic :: Scientific/Engineering', 40 | 'Topic :: Software Development'], 41 | ) 42 | -------------------------------------------------------------------------------- /wtfpython-pypi/wtf_python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jatinkatyal13/wtfpython/2db8002197fe50101bf209dd7c09b73e61cb9930/wtfpython-pypi/wtf_python/__init__.py -------------------------------------------------------------------------------- /wtfpython-pypi/wtf_python/main.py: -------------------------------------------------------------------------------- 1 | import pydoc 2 | try: 3 | from urllib.request import urlretrieve 4 | except ImportError: 5 | from urllib import urlretrieve 6 | 7 | url = ("https://raw.githubusercontent.com/satwikkansal/" 8 | "wtfpython/master/README.md") 9 | file_name = "content.md" 10 | 11 | 12 | def fetch_updated_doc(): 13 | try: 14 | print("Fetching the latest version...") 15 | urlretrieve(url, file_name) 16 | print("Done!") 17 | except Exception as e: 18 | print(e) 19 | print("Uh oh, failed to check for the latest version, " 20 | "using the local version for now.") 21 | 22 | 23 | def render_doc(): 24 | with open(file_name, 'r') as f: 25 | content = f.read() 26 | pydoc.pager(content) 27 | 28 | 29 | def load_and_read(): 30 | fetch_updated_doc() 31 | render_doc() 32 | 33 | 34 | if __name__== "__main__": 35 | load_and_read() 36 | -------------------------------------------------------------------------------- /wtfpython-pypi/wtfpython: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | from wtf_python.main import load_and_read 6 | 7 | if __name__ == "__main__": 8 | sys.exit(load_and_read()) 9 | --------------------------------------------------------------------------------