├── .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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------