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