diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml
new file mode 100644
index 0000000..7340793
--- /dev/null
+++ b/.github/workflows/documentation.yml
@@ -0,0 +1,28 @@
+name: GitHub Actions
+on: [push]
+jobs:
+ Pages:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: [3.10.x]
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install dependencies
+ run: pip install poetry
+ - name: Activate venv
+ run: poetry install
+ - name: Build the book
+ run: poetry run mkdocs build --site-dir public
+ - name: GitHub Pages action
+ uses: peaceiris/actions-gh-pages@v3
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./public
+
+
+
diff --git a/docs/CN.md b/docs/CN.md
new file mode 100644
index 0000000..420fabd
--- /dev/null
+++ b/docs/CN.md
@@ -0,0 +1,3813 @@
+---
+hide:
+ - toc
+---
+
+
+What the f*ck Python! 🐍
+一些有趣且鲜为人知的 Python 特性.
+
+翻译版本: [English](https://github.com/satwikkansal/wtfpython) | [Vietnamese Tiếng Việt](https://github.com/vuduclyunitn/wtfptyhon-vi) | [Spanish Español](https://web.archive.org/web/20220511161045/https://github.com/JoseDeFreitas/wtfpython-es) | [Korean 한국어](https://github.com/buttercrab/wtfpython-ko) | [Russian Русский](https://github.com/frontdevops/wtfpython) | [German Deutsch](https://github.com/BenSt099/wtfpython) | [Add translation](https://github.com/satwikkansal/wtfpython/issues/new?title=Add%20translation%20for%20[LANGUAGE]&body=Expected%20time%20to%20finish:%20[X]%20weeks.%20I%27ll%20start%20working%20on%20it%20from%20[Y].)
+
+
+其他模式: [Interactive](https://mybinder.org/v2/gh/robertparley/wtfpython-cn/master?labpath=irrelevant%2Fwtf.ipynb)
+
+[![WTFPL 2.0][license-image]][license-url] [![Commit id][commit-image]][commit-url]
+
+
+Python, 是一个设计优美的解释型高级语言, 它提供了很多能让程序员感到舒适的功能特性. 但有的时候, Python 的一些输出结果对于初学者来说似乎并不是那么一目了然.
+
+这个有趣的项目意在收集 Python 中那些难以理解和反人类直觉的例子以及鲜为人知的功能特性, 并尝试讨论这些现象背后真正的原理!
+
+虽然下面的有些例子并不一定会让你觉得 WTFs, 但它们依然有可能会告诉你一些你所不知道的 Python 有趣特性. 我觉得这是一种学习编程语言内部原理的好办法, 而且我相信你也会从中获得乐趣!
+
+如果您是一位经验比较丰富的 Python 程序员, 你可以尝试挑战看是否能一次就找到例子的正确答案. 你可能对其中的一些例子已经比较熟悉了, 那这也许能唤起你当年踩这些坑时的甜蜜回忆 :sweat_smile:
+
+PS: 如果你不是第一次读了, 你可以在[这里](https://github.com/satwikkansal/wtfpython/releases/)获取变动内容.
+
+那么, 让我们开始吧...
+
+# Table of Contents/目录
+
+
+- [Table of Contents/目录](#table-of-contents目录)
+- [Structure of the Examples/示例结构](#structure-of-the-examples示例结构)
+- [Usage/用法](#usage用法)
+- [👀 Examples/示例](#-examples示例)
+ - [Section: Strain your brain!/大脑运动!](#section-strain-your-brain大脑运动)
+ - [> First things first!/要事优先 *](#-First-things-first!/要事优先-*)
+ - [> Strings can be tricky sometimes/微妙的字符串 *](#-strings-can-be-tricky-sometimes微妙的字符串-)
+ - [> Be careful with chained operations/小心链式操作](#-be-careful-with-chained-operations小心链式操作)
+ - [> How not to use `is` operator/为什么不使用 `is` 操作符](#-How-not-to-use-is-operator/为什么不使用-is-操作符-)
+ - [> Hash brownies/是时候来点蛋糕了!](#-Hash-brownies是时候来点蛋糕了)
+ - [> Deep down, we're all the same./本质上,我们都一样. *](#-deep-down-were-all-the-same本质上我们都一样-)
+ - [> Disorder within order/有序中潜藏着无序 *](#-disorder-within-order/有序中潜藏着无序-*)
+ - [> Keep trying.../不停的try *](#-Keep-trying不停的try-)
+ - [> For what?/为什么?](#-for-what为什么)
+ - [> Evaluation time discrepancy/执行时机差异](#-evaluation-time-discrepancy执行时机差异)
+ - [> `is not ...` is not `is (not ...)` / `is not ...` 不是 `is (not ...)`](#-is-not--is-not-is-not-is-not--不是-is-not-)
+ - [> A tic-tac-toe where X wins in the first attempt!/一蹴即至!](#-a-tic-tac-toe-where-x-wins-in-the-first-attempt一蹴即至)
+ - [> Schrödinger's variable/薛定谔的变量 *](#-Schrödingers-variable薛定谔的变量-)
+ - [> The chicken-egg problem/先有鸡还是先有蛋 *](#-the-chicken-egg-problem/先有鸡还是先有蛋-*)
+ - [> Subclass relationships/子类关系 *](#-subclass-relationships子类关系-)
+ - [> Methods equality and identity/方法的相等性和唯一性 *](#-Methods-equality-and-identity/方法的相等性和唯一性-)
+ - [> All-true-ation/返回True的all函数 *](#-All-true-ation/返回True的all函数-)
+ - [> The surprising comma/意外的逗号](#-the-surprising-comma意外的逗号)
+ - [> Strings and the backslashes/字符串与反斜杠](#-strings-and-the-backslashes字符串与反斜杠)
+ - [> not knot!/别纠结!](#-not-knot别纠结)
+ - [> Half triple-quoted strings/三个引号](#-half-triple-quoted-strings三个引号)
+ - [> What's wrong with booleans?/布尔你咋了?](#-whats-wrong-with-booleans布尔你咋了)
+ - [> Class attributes and instance attributes/类属性和实例属性](#-class-attributes-and-instance-attributes类属性和实例属性)
+ - [> yielding None/生成 None](#-yielding-none生成-none)
+ - [> Yielding from... return!/生成器里的return *](#-Yielding-from-return/生成器里的return-)
+ - [> Nan-reflexivity/Nan的自反性](#-Nan-reflexivityNan的自反性)
+ - [> Mutating the immutable!/强人所难](#-mutating-the-immutable强人所难)
+ - [> The disappearing variable from outer scope/消失的外部变量](#-the-disappearing-variable-from-outer-scope消失的外部变量)
+ - [> The mysterious key type conversion/神秘的键型转换 *](#-the-mysterious-key-type-conversion神秘的键型转换-)
+ - [> Let's see if you can guess this?/看看你能否猜到这一点?](#-lets-see-if-you-can-guess-this看看你能否猜到这一点)
+ - [> Exceeds the limit for integer string conversion/整型转字符串越界](#-exceeds-the-limit-for-integer-string-conversion整型转字符串越界)
+ - [Section: Slippery Slopes/滑坡谬误](#section-slippery-slopes滑坡谬误)
+ - [> Modifying a dictionary while iterating over it/迭代字典时的修改](#-modifying-a-dictionary-while-iterating-over-it迭代字典时的修改)
+ - [> Stubborn `del` operator/坚强的 `del` *](#-stubborn-del-operator坚强的-del-)
+ - [> The out of scope variable/外部作用域变量](#-the-out-of-scope-variable外部作用域变量)
+ - [> Deleting a list item while iterating/迭代列表时删除元素](#-deleting-a-list-item-while-iterating迭代列表时删除元素)
+ - [> Lossy zip of iterators/丢三落四的zip *](#->-Lossy-zip-of-iterators/丢三落四的zip-)
+ - [> Loop variables leaking out!/循环变量泄漏!](#-loop-variables-leaking-out循环变量泄漏)
+ - [> Beware of default mutable arguments!/当心默认的可变参数!](#-beware-of-default-mutable-arguments当心默认的可变参数)
+ - [> Catching the Exceptions/捕获异常](#-catching-the-exceptions捕获异常)
+ - [> Same operands, different story!/同人不同命!](#-same-operands-different-story同人不同命)
+ - [> Name resolution ignoring class scope/忽略类作用域的名称解析](#-name-resolution-ignoring-class-scope忽略类作用域的名称解析)
+ - [> Rounding like a banker/像银行家一样舍入 *](#-rounding-like-a-banker/像银行家一样舍入-)
+ - [> Needles in a Haystack/大海捞针](#-needles-in-a-haystack大海捞针)
+ - [> Splitsies/分割函数](#-Splitsies分割函数-)
+ - [> Wild imports/通配符导入方式 *](#-Wild-imports通配符导入方式-)
+ - [> All sorted?/都排序了吗? *](#-All-sorted都排序了吗-)
+ - [> Midnight time doesn't exist?/不存在的午夜?](#-midnight-time-doesnt-exist不存在的午夜)
+ - [Section: The Hidden treasures!/隐藏的宝藏!](#section-the-hidden-treasures隐藏的宝藏)
+ - [> Okay Python, Can you make me fly?/Python, 可否带我飞? *](#-okay-python-can-you-make-me-flypython-可否带我飞-)
+ - [> `goto`, but why?/`goto`, 但为什么? *](#-goto-but-whygoto-但为什么-)
+ - [> Brace yourself!/做好思想准备 *](#-brace-yourself做好思想准备-)
+ - [> Let's meet Friendly Language Uncle For Life/让生活更友好 *](#-lets-meet-friendly-language-uncle-for-life让生活更友好-)
+ - [> Even Python understands that love is complicated/连Python也知道爱是难言的 *](#-even-python-understands-that-love-is-complicated连Python也知道爱是难言的-)
+ - [> Yes, it exists!/是的, 它存在!](#-yes-it-exists是的-它存在)
+ - [> Ellipsis/省略 *](#-Ellipsis省略-)
+ - [> Inpinity/无限 *](#-inpinity无限-)
+ - [> Let's mangle/修饰时间! *](#-Lets-mangle修饰时间-)
+ - [Section: Appearances are deceptive!/外表是靠不住的!](#section-appearances-are-deceptive外表是靠不住的)
+ - [> Skipping lines?/跳过一行?](#-skipping-lines跳过一行)
+ - [> Teleportation/空间移动 *](#-teleportation空间移动-)
+ - [> Well, something is fishy.../嗯, 有些可疑...](#-well-something-is-fishy嗯有些可疑)
+ - [Section: Miscellaneous/杂项](#section-miscellaneous杂项)
+ - [> `+=` is faster/更快的 `+=` ](#--is-faster更快的-)
+ - [> Let's make a giant string!/来做个巨大的字符串吧!](#-lets-make-a-giant-string来做个巨大的字符串吧)
+ - [> Slowing down `dict` lookups/让字典的查找慢下来 *](#-Slowing-down-dict-lookups让字典的查找慢下来-)
+ - [> Bloating instance `dict`s/变臃肿的`dict`实例们 *](#-Bloating-instance-dicts/变臃肿的dict实例们-)
+ - [> Minor Ones/小知识点](#-minor-ones小知识点)
+- [Contributing/贡献](#contributing贡献)
+- [Acknowledgements/致谢](#acknowledgements致谢)
+- [🎓 License/许可](#-license许可)
+ - [Help/帮助](#help帮助)
+ - [Surprise your geeky pythonist friends?/想给你的极客朋友一个惊喜?](#surprise-your-geeky-pythonist-friends想给你的极客朋友一个惊喜)
+ - [Need a pdf version?/需要来一份pdf版的?](#need-a-pdf-version需要来一份pdf版的)
+ - [Follow Commit/追踪Commit](#follow-commit追踪Commit)
+ - [996.icu](#996icu)
+
+
+
+# Structure of the Examples/示例结构
+
+所有示例的结构都如下所示:
+
+> ### > 一个精选的标题 *
+> 标题末尾的星号表示该示例在第一版中不存在,是最近添加的.
+>
+> ```py
+> # 准备代码.
+> # 释放魔法...
+> ```
+>
+> **Output (Python version):**
+> ```py
+> >>> 触发语句
+> 出乎意料的输出结果
+> ```
+> (可选): 对意外输出结果的简短描述.
+>
+>
+> #### 💡 说明:
+>
+> * 简要说明发生了什么以及为什么会发生.
+> ```py
+> 如有必要, 举例说明
+> ```
+> **Output:**
+> ```py
+> >>> 触发语句 # 一些让魔法变得容易理解的例子
+> # 一些正常的输入
+> ```
+
+**注意:** 所有的示例都在 Python 3.5.2 版本的交互解释器上测试过, 如果不特别说明应该适用于所有 Python 版本.
+
+# Usage/用法
+
+我个人建议, 最好依次阅读下面的示例, 并对每个示例:
+- 仔细阅读设置例子最开始的代码. 如果您是一位经验丰富的 Python 程序员, 那么大多数时候您都能成功预期到后面的结果.
+- 阅读输出结果,
+ + 确认结果是否如你所料.
+ + 确认你是否知道这背后的原理.
+ - 如果不知道, 深呼吸然后阅读说明 (如果你还是看不明白, 别沉默! 可以在[这](https://github.com/satwikkansal/wtfPython)提个 issue).
+ - 如果知道, 给自己点奖励, 然后去看下一个例子.
+
+PS: 你也可以在命令行阅读 WTFpython. 我们有 pypi 包 和 npm 包(支持代码高亮).(译: 这两个都是英文版的)
+
+安装 npm 包 [`wtfpython`](https://www.npmjs.com/package/wtfpython)
+```sh
+$ npm install -g wtfpython
+```
+
+或者, 安装 pypi 包 [`wtfpython`](https://pypi.python.org/pypi/wtfpython)
+```sh
+$ pip install wtfpython -U
+```
+
+现在, 在命令行中运行 `wtfpython`, 你就可以开始浏览了.
+
+---
+
+# 👀 Examples/示例
+
+
+## Section: Strain your brain!/大脑运动!
+
+### > First things first!/要事优先 *
+
+
+
+
+众所周知,Python 3.8 推出"海象"运算符 (`:=`) 方便易用,让我们一起看看。
+
+1\.
+
+```py
+# Python 版本 3.8+
+
+>>> a = "wtf_walrus"
+>>> a
+'wtf_walrus'
+
+>>> a := "wtf_walrus"
+File "", line 1
+ a := "wtf_walrus"
+ ^
+SyntaxError: invalid syntax
+
+>>> (a := "wtf_walrus") # 该语句有效
+'wtf_walrus'
+>>> a
+'wtf_walrus'
+```
+
+2 \.
+
+```py
+# Python 版本 3.8+
+
+>>> a = 6, 9
+>>> a
+(6, 9)
+
+>>> (a := 6, 9)
+(6, 9)
+>>> a
+6
+
+>>> a, b = 6, 9 # 典型拆包操作
+>>> a, b
+(6, 9)
+>>> (a, b = 16, 19) # 出错啦
+ File "", line 1
+ (a, b = 16, 19)
+ ^
+SyntaxError: invalid syntax
+
+>>> (a, b := 16, 19) # 这里的结果是一个奇怪的三元组
+(6, 16, 19)
+
+>>> a # a值仍然没变?
+6
+
+>>> b
+16
+```
+
+
+
+#### 💡 说明
+
+**“海象”运算符简介**
+
+海象运算符 (`:=`) 在Python 3.8中被引入,用来在表达式中为变量赋值。
+
+```py
+def some_func():
+ # 假设这儿有一些耗时的计算
+ # time.sleep(1000)
+ return 5
+
+# 引入“海象”运算符前的例子
+if some_func():
+ print(some_func()) # 糟糕的案例——函数运算了两次
+
+# 或者,加以改进:
+a = some_func()
+if a:
+ print(a)
+
+# 有了“海象”运算符,你可以写的更简洁:
+if a := some_func():
+ print(a)
+```
+
+**输出 (> Python 3.8):**
+
+```py
+5
+5
+5
+```
+这样既减少了一行代码,又避免了两次调用 `some_func` 函数。
+
+- 在顶层的无括号赋值操作(使用“海象”运算符)被限制,因此例1中的 `a := "wtf_walrus"` 出现了 `SyntaxError` 。用括号括起来。它就能正常工作了。
+
+- 一般的,包含 `=` 操作的表达式是不能用括号括起来的,因此 `(a, b = 6, 9)` 中出现了语法错误。
+
+- “海象”运算符的语法形式为:`NAME:= expr`,`NAME` 是一个有效的标识符,而 `expr` 是一个有效的表达式。 因此,这意味着它不支持可迭代的打包和拆包。
+
+ - `(a := 6, 9)` 等价于 `((a := 6), 9)` ,最终得到 `(a, 9) ` (其中 `a` 的值为6)
+
+ ```py
+ >>> (a := 6, 9) == ((a := 6), 9)
+ True
+ >>> x = (a := 696, 9)
+ >>> x
+ (696, 9)
+ >>> x[0] is a # 两个变量指向同一内存空间
+ True
+ ```
+
+ - 类似的, `(a, b := 16, 19)` 等价于 `(a, (b := 16), 19)` ,只是一个三元组。
+
+
+---
+
+### > Strings can be tricky sometimes/微妙的字符串 *
+
+1\.
+```py
+>>> a = "some_string"
+>>> id(a)
+140420665652016
+>>> id("some" + "_" + "string") # 注意两个的id值是相同的.
+140420665652016
+```
+
+2\.
+```py
+>>> a = "wtf"
+>>> b = "wtf"
+>>> a is b
+True
+
+>>> a = "wtf!"
+>>> b = "wtf!"
+>>> a is b
+False
+
+>>> a, b = "wtf!", "wtf!"
+>>> a is b
+True # 3.7 版本返回结果为 False.
+```
+
+3\.
+```py
+>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
+True
+>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
+False # 3.7 版本返回结果为 True
+```
+
+很好理解, 对吧?
+
+#### 💡 说明:
+- 这些行为是由于 Cpython 在编译优化时, 某些情况下会尝试使用已经存在的不可变对象而不是每次都创建一个新对象. (这种行为被称作字符串的驻留[string interning])
+- 发生驻留之后, 许多变量可能指向内存中的相同字符串对象. (从而节省内存)
+- 在上面的代码中, 字符串是隐式驻留的. 何时发生隐式驻留则取决于具体的实现. 这里有一些方法可以用来猜测字符串是否会被驻留:
+ - 所有长度为 0 和长度为 1 的字符串都被驻留.
+ - 字符串在编译时被实现 (`'wtf'` 将被驻留, 但是 `''.join(['w', 't', 'f'])` 将不会被驻留)
+ - 字符串中只包含字母,数字或下划线时将会驻留. 所以 `'wtf!'` 由于包含 `!` 而未被驻留. 可以在[这里](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)找到 CPython 对此规则的实现.
+
+
+
+- 当在同一行将 `a` 和 `b` 的值设置为 `"wtf!"` 的时候, Python 解释器会创建一个新对象, 然后同时引用第二个变量(译: 仅适用于3.7以下, 详细情况请看[这里](https://github.com/leisurelicht/wtfpython-cn/issues/13)). 如果你在不同的行上进行赋值操作, 它就不会“知道”已经有一个 `wtf!` 对象 (因为 `"wtf!"` 不是按照上面提到的方式被隐式驻留的). 它是一种编译器优化, 特别适用于交互式环境.
+- 常量折叠(constant folding) 是 Python 中的一种 [窥孔优化(peephole optimization)](https://en.wikipedia.org/wiki/Peephole_optimization) 技术. 这意味着在编译时表达式 `'a'*20` 会被替换为 `'aaaaaaaaaaaaaaaaaaaa'` 以减少运行时的时钟周期. 只有长度小于 20 的字符串才会发生常量折叠. (为啥? 想象一下由于表达式 `'a'*10**10` 而生成的 `.pyc` 文件的大小). 相关的源码实现在[这里](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288).
+- 如果你是使用 3.7 版本中运行上述示例代码, 会发现部分代码的运行结果与注释说明相同. 这是因为在 3.7 版本中, 常量折叠已经从窥孔优化器迁移至新的 AST 优化器, 后者可以以更高的一致性来执行优化. (由 Eugene Toder 和 INADA Naoki 在 [bpo-29469](https://bugs.python.org/issue29469) 和 [bpo-11549](https://bugs.python.org/issue11549) 中贡献.)
+- (译: 但是在最新的 3.8 版本中, 结果又变回去了. 虽然 3.8 版本和 3.7 版本一样, 都是使用 AST 优化器. 目前不确定官方对 3.8 版本的 AST 做了什么调整.)
+
+
+---
+
+### > Be careful with chained operations/小心链式操作
+
+```py
+>>> (False == False) in [False] # 可以理解
+False
+>>> False == (False in [False]) # 可以理解
+False
+>>> False == False in [False] # 为毛?
+True
+
+>>> True is False == False
+False
+>>> False is False is False
+True
+
+>>> 1 > 0 < 1
+True
+>>> (1 > 0) < 1
+False
+>>> 1 > (0 < 1)
+False
+```
+
+#### 💡 说明:
+
+根据 https://docs.python.org/3/reference/expressions.html#comparisons
+
+> 形式上, 如果 a, b, c, ..., y, z 是表达式, 而 op1, op2, ..., opN 是比较运算符, 那么除了每个表达式最多只出现一次以外 a op1 b op2 c ... y opN z 就等于 a op1 b and b op2 c and ... y opN z.
+
+虽然上面的例子似乎很愚蠢, 但是像 `a == b == c` 或 `0 <= x <= 100` 就很棒了.
+
+* `False is False is False` 相当于 `(False is False) and (False is False)`
+* `True is False == False` 相当于 `(True is False) and (False == False)`, 由于语句的第一部分 (`True is False`) 等于 `False`, 因此整个表达式的结果为 `False`.
+* `1 > 0 < 1` 相当于 `(1 > 0) and (0 < 1)`, 所以最终结果为 `True`.
+* 表达式 `(1 > 0) < 1` 相当于 `True < 1` 且
+ ```py
+ >>> int(True)
+ 1
+ >>> True + 1 # 与这个例子无关,只是娱乐一下
+ 2
+ ```
+ 所以, `1 < 1` 等于 `False`
+
+
+---
+
+### > How not to use `is` operator/为什么不使用 `is` 操作符
+
+
+下面是一个在互联网上非常有名的例子.
+
+1\.
+
+```py
+>>> a = 256
+>>> b = 256
+>>> a is b
+True
+
+>>> a = 257
+>>> b = 257
+>>> a is b
+False
+```
+
+2\.
+
+```py
+>>> a = []
+>>> b = []
+>>> a is b
+False
+
+>>> a = tuple()
+>>> b = tuple()
+>>> a is b
+True
+```
+
+3\.
+**Output**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+True
+```
+
+**Output (Python 3.7.x specifically)**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+False
+```
+
+#### 💡 说明:
+
+**`is` 和 `==` 的区别**
+
+* `is` 运算符检查两个运算对象是否引用自同一对象 (即, 它检查两个运算对象是否相同).
+* `==` 运算符比较两个运算对象的值是否相等.
+* 因此 `is` 代表引用相同, `==` 代表值相等. 下面的例子可以很好的说明这点,
+ ```py
+ >>> [] == []
+ True
+ >>> [] is [] # 这两个空列表位于不同的内存地址.
+ False
+ ```
+
+**`256` 是一个已经存在的对象, 而 `257` 不是**
+
+当你启动Python 的时候, 数值为 `-5` 到 `256` 的对象就已经被分配好了. 这些数字因为经常被使用, 所以会被提前准备好.
+
+Python 通过这种创建小整数池的方式来避免小整数频繁的申请和销毁内存空间.
+
+引用自 https://docs.python.org/3/c-api/long.html
+> 当前的实现为-5到256之间的所有整数保留一个整数对象数组, 当你创建了一个该范围内的整数时, 你只需要返回现有对象的引用. 所以改变1的值是有可能的. 我怀疑这种行为在Python中是未定义行为. :-)
+
+```py
+>>> id(256)
+10922528
+>>> a = 256
+>>> b = 256
+>>> id(a)
+10922528
+>>> id(b)
+10922528
+>>> id(257)
+140084850247312
+>>> x = 257
+>>> y = 257
+>>> id(x)
+140084850247440
+>>> id(y)
+140084850247344
+```
+
+这里解释器并没有智能到能在执行 `y = 257` 时意识到我们已经创建了一个整数 `257`, 所以它在内存中又新建了另一个对象.
+
+类似的优化也适用于其他**不可变**对象,例如空元组。由于列表是可变的,这就是为什么 `[] is []` 将返回 `False` 而 `() is ()` 将返回 `True`。 这解释了我们的第二个代码段。而第三个呢:
+
+**当 `a` 和 `b` 在同一行中使用相同的值初始化时,会指向同一个对象.**
+
+```py
+>>> a, b = 257, 257
+>>> id(a)
+140640774013296
+>>> id(b)
+140640774013296
+>>> a = 257
+>>> b = 257
+>>> id(a)
+140640774013392
+>>> id(b)
+140640774013488
+```
+
+* 当 a 和 b 在同一行中被设置为 `257` 时, Python 解释器会创建一个新对象, 然后同时引用第二个变量. 如果你在不同的行上进行, 它就不会 "知道" 已经存在一个 `257` 对象了.
+* 这是一种特别为交互式环境做的编译器优化. 当你在实时解释器中输入两行的时候, 他们会单独编译, 因此也会单独进行优化. 如果你在 `.py` 文件中尝试这个例子, 则不会看到相同的行为, 因为文件是一次性编译的。这种优化不仅限于整数,它也适用于其他不可变数据类型,例如字符串(查看示例“微妙的字符串”)和浮点数,
+
+ ```py
+ >>> a, b = 257.0, 257.0
+ >>> a is b
+ True
+ ```
+
+* 为什么这不适用于 Python 3.7? 大概是因为此类编译器优化是特定于实现的(即可能随版本、操作系统等而变化)。我仍在试图弄清楚导致问题的具体实现更改,您可以查看此 [问题](https://github.com/satwikkansal/wtfpython/issues/100) 以获取更新。
+
+
+---
+
+### > Hash brownies/是时候来点蛋糕了!
+* hash brownie指一种含有大麻成分的蛋糕, 所以这里是句双关
+* 这里保留原作者对于标题的翻译
+
+
+
+1\.
+```py
+some_dict = {}
+some_dict[5.5] = "JavaScript"
+some_dict[5.0] = "Ruby"
+some_dict[5] = "Python"
+```
+
+**Output:**
+
+```py
+>>> some_dict[5.5]
+"JavaScript"
+>>> some_dict[5.0] # "Python" 消除了 "Ruby" 的存在?
+"Python"
+>>> some_dict[5]
+"Python"
+
+>>> complex_five = 5 + 0j
+>>> type(complex_five)
+complex
+>>> some_dict[complex_five]
+"Python"
+```
+
+为什么到处都是Python?
+
+
+#### 💡 说明
+
+
+* 这个 StackOverflow的 [回答](https://stackoverflow.com/a/32211042/4354153) 漂亮地解释了这背后的基本原理.
+
+* Python 字典中键的唯一性是根据 *等价性*,而不是同一性。 因此,即使 `5`、`5.0` 和 `5 + 0j` 是不同类型的不同对象,由于它们是相等的,它们不能都在同一个 `dict`(或 `set`)中。 只要您插入其中任何一个,尝试查找任何不同但等价的键都将使用原始映射值成功(而不是因“KeyError”而失败):
+
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> 5 is not 5.0 is not 5 + 0j
+ True
+ >>> some_dict = {}
+ >>> some_dict[5.0] = "Ruby"
+ >>> 5.0 in some_dict
+ True
+ >>> (5 in some_dict) and (5 + 0j in some_dict)
+ True
+ ```
+
+* 这在赋值的时候也会生效。因此,当您执行 `some_dict[5] = "Python"` 时,Python 会找到具有等价键值 `5.0 -> "Ruby"` 的现有项,覆盖其值,并保留原始键值。
+
+ ```py
+ >>> some_dict
+ {5.0: 'Ruby'}
+ >>> some_dict[5] = "Python"
+ >>> some_dict
+ {5.0: 'Python'}
+ ```
+* 那么我们如何将键值更新为`5`(而不是`5.0`)? 我们实际上不能原地更新,但是我们可以先删除键(`del some_dict[5.0]`),然后重新赋值(`some_dict[5]`)得到整数`5` 作为键而不是浮点数 `5.0`,尽管这属于极少数情况。
+
+* Python 是如何在包含 `5.0` 的字典中找到 `5` 的? Python 只需要花费常数时间,而无需使用哈希函数遍历每一项。当 Python 在 dict 中查找键 `foo` 时,它首先计算 `hash(foo)`(以常数时间运行)。因为在 Python 中,要求相等的对象具有相同的哈希值(此处为[文档](https://docs.python.org/3/reference/datamodel.html#object.__hash__)),`5` 、`5.0` 和 `5 + 0j` 具有相同的哈希值。
+
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> hash(5) == hash(5.0) == hash(5 + 0j)
+ True
+ ```
+
+ **注意:** 反之不一定正确:具有相等哈希值的对象本身可能不相等。(这是[哈希冲突](https://en.wikipedia.org/wiki/Collision_(computer_science))造成的,这也会降低哈希运算的性能。)
+
+
+---
+
+### > Deep down, we're all the same./本质上,我们都一样. *
+
+```py
+class WTF:
+ pass
+```
+
+**Output:**
+```py
+>>> WTF() == WTF() # 两个不同的对象应该不相等
+False
+>>> WTF() is WTF() # 也不相同
+False
+>>> hash(WTF()) == hash(WTF()) # 哈希值也应该不同
+True
+>>> id(WTF()) == id(WTF())
+True
+```
+
+#### 💡 说明:
+
+* 当调用 `id` 函数时, Python 创建了一个 `WTF` 类的对象并传给 `id` 函数. 然后 `id` 函数获取其id值 (也就是内存地址), 然后丢弃该对象. 该对象就被销毁了.
+* 当我们连续两次进行这个操作时, Python会将相同的内存地址分配给第二个对象. 因为 (在CPython中) `id` 函数使用对象的内存地址作为对象的id值, 所以两个对象的id值是相同的.
+* 综上, 对象的id值仅仅在对象的生命周期内唯一. 在对象被销毁之后, 或被创建之前, 其他对象可以具有相同的id值.
+* 那为什么 `is` 操作的结果为 `False` 呢? 让我们看看这段代码.
+ ```py
+ class WTF(object):
+ def __init__(self): print("I")
+ def __del__(self): print("D")
+ ```
+
+ **Output:**
+ ```py
+ >>> WTF() is WTF()
+ I
+ I
+ D
+ D
+ False
+ >>> id(WTF()) == id(WTF())
+ I
+ D
+ I
+ D
+ True
+ ```
+ 正如你所看到的, 对象销毁的顺序是造成所有不同之处的原因.
+
+
+---
+
+### > Disorder within order/有序中潜藏着无序 *
+
+
+```py
+from collections import OrderedDict
+
+dictionary = dict()
+dictionary[1] = 'a'; dictionary[2] = 'b';
+
+ordered_dict = OrderedDict()
+ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+
+another_ordered_dict = OrderedDict()
+another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+
+class DictWithHash(dict):
+ """
+ 实现了 __hash__ 魔法方法的dict类
+ """
+ __hash__ = lambda self: 0
+
+class OrderedDictWithHash(OrderedDict):
+ """
+ 实现了 __hash__ 魔法方法的OrderedDict类
+ """
+ __hash__ = lambda self: 0
+```
+
+**Output**
+```py
+>>> dictionary == ordered_dict # 如果 a == b
+True
+>>> dictionary == another_ordered_dict # 且 b == c
+True
+>>> ordered_dict == another_ordered_dict # 那么为什么 c == a 不成立??
+False
+
+# 众所周知,set数据结构储存不重复元素,
+# 让我们生成以上字典的 set 数据类型,看看会发生什么……
+
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unhashable type: 'dict'
+
+# dict类没有实现 __hash__ ,出错可以理解,接下来使用我们派生的类。
+
+>>> dictionary = DictWithHash()
+>>> dictionary[1] = 'a'; dictionary[2] = 'b';
+>>> ordered_dict = OrderedDictWithHash()
+>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+>>> another_ordered_dict = OrderedDictWithHash()
+>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+1
+>>> len({ordered_dict, another_ordered_dict, dictionary}) # 交换顺序
+2
+```
+
+到底发生了什么?
+
+#### 💡 说明:
+
+- 等号的传递性没有在 `dictionary`, `ordered_dict` 和 `another_ordered_dict` 之间生效是 `OrderedDict` 类中 `__eq__` 方法的实现方式造成的。根据[文档](https://docs.python.org/3/library/collections.html#ordereddict-objects)以下部分:
+
+ > 对于 `OrderedDict` 类之间,相等性的判定是位置敏感的,实现类似于 `list(od1.items())==list(od2.items())`。对于 `OrderedDict` 类与其他 `Mapping` 对象(例如`dict` 类),相等性的判定是非位置敏感的。
+- 这是为了任何使用常规 `dict` 类的地方能够直接使用 `OrderedDict` 对象代替。
+- 好啦,那为什么改变顺序会影响 `set` 对象生成的长度呢? 答案就是上面说的缺乏等号的传递性。因为 `set` 类是唯一元素的无序集合,元素插入的顺序不应该有影响。但在此例中,确有不同。让我们进一步深入。
+
+ ```py
+ >>> some_set = set()
+ >>> some_set.add(dictionary) # 涉及的变量是前序片段定义的 mapping 对象
+ >>> ordered_dict in some_set
+ True
+ >>> some_set.add(ordered_dict)
+ >>> len(some_set)
+ 1
+ >>> another_ordered_dict in some_set
+ True
+ >>> some_set.add(another_ordered_dict)
+ >>> len(some_set)
+ 1
+
+ >>> another_set = set()
+ >>> another_set.add(ordered_dict)
+ >>> another_ordered_dict in another_set
+ False
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ >>> dictionary in another_set
+ True
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ ```
+ 因此,不一致性是由于 `another_ordered_dict in another_set` 结果为 `False`。 因为 `ordered_dict` 已经在 `another_set` 中,但如前所述, `ordered_dict == another_ordered_dict` 的结果为 `False`,会在后续再加入 `another_ordered_dict` 到 `another_set` 中。
+
+
+---
+
+### > Keep trying.../不停的try *
+
+
+```py
+def some_func():
+ try:
+ return 'from_try'
+ finally:
+ return 'from_finally'
+
+def another_func():
+ for _ in range(3):
+ try:
+ continue
+ finally:
+ print("Finally!")
+
+def one_more_func(): # A gotcha!
+ try:
+ for i in range(3):
+ try:
+ 1 / i
+ except ZeroDivisionError:
+ # Let's throw it here and handle it outside for loop
+ raise ZeroDivisionError("A trivial divide by zero error")
+ finally:
+ print("Iteration", i)
+ break
+ except ZeroDivisionError as e:
+ print("Zero division error occurred", e)
+```
+
+**Output:**
+
+```py
+>>> some_func()
+'from_finally'
+
+>>> another_func()
+Finally!
+Finally!
+Finally!
+
+>>> 1 / 0
+Traceback (most recent call last):
+ File "", line 1, in
+ZeroDivisionError: division by zero
+
+>>> one_more_func()
+Iteration 0
+
+```
+
+#### 💡 说明:
+
+- 当在 "try...finally" 语句的 `try` 中执行 `return`, `break` 或 `continue` 后, `finally` 子句依然会执行.
+- 函数的返回值由最后执行的 `return` 语句决定. 由于 `finally` 子句一定会执行, 所以 `finally` 子句中的 `return` 将始终是最后执行的语句.
+- 这里需要注意的是,如果 finally 子句执行 `return` 或 `break` 语句,临时保存的异常将被丢弃。
+
+
+---
+
+### > For what?/为什么?
+
+```py
+some_string = "wtf"
+some_dict = {}
+for i, some_dict[i] in enumerate(some_string):
+ pass
+```
+
+**Output:**
+```py
+>>> some_dict # 创建了索引字典.
+{0: 'w', 1: 't', 2: 'f'}
+```
+
+#### 💡 说明:
+
+* [Python 语法](https://docs.python.org/3/reference/grammar.html) 中对 `for` 的定义是:
+ ```
+ for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
+ ```
+ 其中 `exprlist` 指分配目标. 这意味着对可迭代对象中的**每一项都会执行**类似 `{exprlist} = {next_value}` 的操作.
+
+ 一个有趣的例子说明了这一点:
+ ```py
+ for i in range(4):
+ print(i)
+ i = 10
+ ```
+
+ **Output:**
+ ```
+ 0
+ 1
+ 2
+ 3
+ ```
+
+ 你可曾觉得这个循环只会运行一次?
+
+ **💡 说明:**
+
+ - 由于循环在Python中工作方式, 赋值语句 `i = 10` 并不会影响迭代循环, 在每次迭代开始之前, 迭代器(这里指 `range(4)`) 生成的下一个元素就被解包并赋值给目标列表的变量(这里指 `i`)了.
+
+* 在每一次的迭代中, `enumerate(some_string)` 函数就生成一个新值 `i` (计数器增加) 并从 `some_string` 中获取一个字符. 然后将字典 `some_dict` 键 `i` (刚刚分配的) 的值设为该字符. 本例中循环的展开可以简化为:
+ ```py
+ >>> i, some_dict[i] = (0, 'w')
+ >>> i, some_dict[i] = (1, 't')
+ >>> i, some_dict[i] = (2, 'f')
+ >>> some_dict
+ ```
+
+---
+
+### > Evaluation time discrepancy/执行时机差异
+
+1\.
+```py
+array = [1, 8, 15]
+# 一个典型的生成器表达式
+g = (x for x in array if array.count(x) > 0)
+array = [2, 8, 22]
+```
+
+**Output:**
+```py
+>>> print(list(g)) #其他的值去哪儿了?
+[8]
+```
+
+2\.
+
+```py
+array_1 = [1,2,3,4]
+g1 = (x for x in array_1)
+array_1 = [1,2,3,4,5]
+
+array_2 = [1,2,3,4]
+g2 = (x for x in array_2)
+array_2[:] = [1,2,3,4,5]
+```
+
+**Output:**
+```py
+>>> print(list(g1))
+[1,2,3,4]
+
+>>> print(list(g2))
+[1,2,3,4,5]
+```
+
+3\.
+
+```py
+array_3 = [1, 2, 3]
+array_4 = [10, 20, 30]
+gen = (i + j for i in array_3 for j in array_4)
+
+array_3 = [4, 5, 6]
+array_4 = [400, 500, 600]
+```
+
+**Output:**
+```py
+>>> print(list(gen))
+[401, 501, 601, 402, 502, 602, 403, 503, 603]
+```
+
+#### 💡 说明
+
+- 在[生成器](https://wiki.python.org/moin/Generators)表达式中, `in` 子句在声明时执行, 而条件子句则是在运行时执行.
+- 所以在运行前, `array` 已经被重新赋值为 `[2, 8, 22]`, 因此对于之前的 `1`, `8` 和 `15`, 只有 `count(8)` 的结果是大于 `0` 的, 所以生成器只会生成 `8`.
+- 第二部分中 `g1` 和 `g2` 的输出差异则是由于变量 `array_1` 和 `array_2` 被重新赋值的方式导致的.
+- 在第一种情况下, `array_1` 被绑定到新对象 `[1,2,3,4,5]`, 因为 `in` 子句是在声明时被执行的, 所以它仍然引用旧对象 `[1,2,3,4]`(并没有被销毁).
+- 在第二种情况下, 对 `array_2` 的切片赋值将相同的旧对象 `[1,2,3,4]` 原地更新为 `[1,2,3,4,5]`. 因此 `g2` 和 `array_2` 仍然引用同一个对象(这个对象现在已经更新为 `[1,2,3,4,5]`).
+- 好啦,按照目前讨论的逻辑,第三个代码段中的 `list(gen)` 的值不应该是 `[11, 21, 31, 12, 22, 32, 13, 23, 33]` 吗? (毕竟 `array_3` 和 `array_4` 的行为与 `array_1` 一样)。 [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details) 中解释了(只有)`array_4` 值更新的原因
+ > 只有最外层的 for 表达式会立即计算,其他表达式会延迟到生成器运行。
+
+
+---
+
+### > `is not ...` is not `is (not ...)`/`is not ...` 不是 `is (not ...)`
+
+```py
+>>> 'something' is not None
+True
+>>> 'something' is (not None)
+False
+```
+
+#### 💡 说明:
+
+- `is not` 是个单独的二元运算符, 与分别使用 `is` 和 `not` 不同.
+- 如果操作符两侧的变量指向同一个对象, 则 `is not` 的结果为 `False`, 否则结果为 `True`.
+
+
+---
+
+### > A tic-tac-toe where X wins in the first attempt!/一蹴即至!
+
+```py
+# 我们先初始化一个变量row
+row = [""]*3 #row i['', '', '']
+# 并创建一个变量board
+board = [row]*3
+```
+
+**Output:**
+```py
+>>> board
+[['', '', ''], ['', '', ''], ['', '', '']]
+>>> board[0]
+['', '', '']
+>>> board[0][0]
+''
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['X', '', ''], ['X', '', '']]
+```
+
+我们有没有赋值过3个 "X" 呢?
+
+#### 💡 说明:
+
+当我们初始化 `row` 变量时, 下面这张图展示了内存中的情况。
+
+![image](../images/tic-tac-toe/after_row_initialized.png)
+
+而当通过对 `row` 做乘法来初始化 `board` 时, 内存中的情况则如下图所示 (每个元素 `board[0]`, `board[1]` 和 `board[2]` 都和 `row` 一样引用了同一列表.)
+
+![image](../images/tic-tac-toe/after_board_initialized.png)
+
+我们可以通过不使用变量 `row` 生成 `board` 来避免这种情况. ([这个](https://github.com/satwikkansal/wtfpython/issues/68)issue提出了这个需求.)
+
+```py
+>>> board = [['']*3 for _ in range(3)]
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['', '', ''], ['', '', '']]
+```
+
+---
+
+### > Schrödinger's variable/薛定谔的变量 *
+
+
+```py
+funcs = []
+results = []
+for x in range(7):
+ def some_func():
+ return x
+ funcs.append(some_func)
+ results.append(some_func()) # 注意这里函数被执行了
+
+funcs_results = [func() for func in funcs]
+```
+
+**Output:**
+```py
+>>> results
+[0, 1, 2, 3, 4, 5, 6]
+>>> funcs_results
+[6, 6, 6, 6, 6, 6, 6]
+```
+
+即使每次在迭代中将 `some_func` 加入 `funcs` 前的 `x` 值都不相同, 所有的函数还是都返回6.
+
+// 再换个例子
+
+```py
+>>> powers_of_x = [lambda x: x**i for i in range(10)]
+>>> [f(2) for f in powers_of_x]
+[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
+```
+
+#### 💡 说明:
+
+- 当在循环内部定义一个函数时, 如果该函数在其主体中使用了循环变量, 则闭包函数将与循环变量绑定, 而不是它的值. 因此, 所有的函数都是使用最后分配给变量的值来进行计算的.
+
+- 可以通过将循环变量作为命名变量传递给函数来获得预期的结果. **为什么这样可行?** 因为这会在函数内再次定义一个局部变量。我们可以看到它使用了来自上下文的`x`(即*不是*局部变量):
+(译者注: inspect位于Python标准库中,该模块用于收集python对象的信息,可以获取类或函数的参数的信息,源码,解析堆栈,对对象进行类型检查等等,Python3.3+版本支持getclosurevars函数)
+```py
+>>> import inspect
+>>> inspect.getclosurevars(funcs[0])
+ClosureVars(nonlocals={}, globals={'x': 6}, builtins={}, unbound=set())
+```
+
+由于 `x` 是一个全局值,我们可以通过更新 `x` 来更改 `funcs` 用来查找和返回的值:
+
+```py
+>>> x = 42
+>>> [func() for func in funcs]
+[42, 42, 42, 42, 42, 42, 42]
+```
+
+* 要获得想要的结果,您可以将循环变量作为命名变量传递给函数。 **为什么会这样?** 因为这会在函数的作用域内定义变量。 它将不再进入周围(全局)范围来查找变量值,而是会创建一个局部变量来存储该时间点的“x”值。
+
+```py
+funcs = []
+for x in range(7):
+ def some_func(x=x):
+ return x
+ funcs.append(some_func)
+```
+
+**Output:**
+```py
+>>> funcs_results = [func() for func in funcs]
+>>> funcs_results
+[0, 1, 2, 3, 4, 5, 6]
+```
+
+此时,不再使用全局变量 `x`:
+
+```py
+>>> inspect.getclosurevars(funcs[0])
+ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())
+```
+
+
+---
+
+### > The chicken-egg problem/先有鸡还是先有蛋 *
+
+
+1\.
+```py
+>>> isinstance(3, int)
+True
+>>> isinstance(type, object)
+True
+>>> isinstance(object, type)
+True
+```
+那么到底谁是“最终”的基类呢?下边顺便列出更多的令人困惑的地方
+
+2\.
+
+```py
+>>> class A: pass
+>>> isinstance(A, A)
+False
+>>> isinstance(type, type)
+True
+>>> isinstance(object, object)
+True
+```
+
+3\.
+
+```py
+>>> issubclass(int, object)
+True
+>>> issubclass(type, object)
+True
+>>> issubclass(object, type)
+False
+```
+
+
+#### 💡 说明
+
+- `type` 是 Python 中的[元类](https://realpython.com/python-metaclasses/)。
+- Python 中,**一切**皆对象,其中包括类及其对象(实例)。
+- `type` 类型是`object`类的元类,每个类(包括`type`)都直接或间接地继承自`object`。
+- 对象和类型之间没有真正的基类。上述片段中的令人困惑的地方之所以出现,是因为我们从 Python 类的角度考虑这些关系(issubclass 和 isinstance)。 `object`和`type`之间的关系不能在纯python中重现。 更准确地说,以下关系不能在纯 Python 中重现:
+ + A类是B类的一个实例,B类是A类的一个实例。
+ + A类是它自己的一个实例。
+- `object`和`type`之间的关系(既是彼此的实例,也是它们自己的实例)存在于 Python 中,这是源于实现层级上的“作弊”行为。
+
+
+---
+
+### > Subclass relationships/子类关系 *
+
+**Output:**
+```py
+>>> from collections.abc import Hashable
+>>> issubclass(list, object)
+True
+>>> issubclass(object, Hashable)
+True
+>>> issubclass(list, Hashable)
+False
+```
+
+子类关系应该是可传递的, 对吧? (即, 如果 `A` 是 `B` 的子类, `B` 是 `C` 的子类, 那么 `A` _应该_ 是 `C` 的子类.)
+
+#### 💡 说明:
+
+* Python 中的子类关系并不一定是传递的. 任何人都可以在元类中随意定义 `__subclasscheck__`.
+* 当 `issubclass(cls, Hashable)` 被调用时, 它只是在 `cls` 中寻找 `__hash__` 方法或者从继承的父类中寻找 `__hash__` 方法.
+* 由于 `object` is 可散列的(hashable), 但是 `list` 是不可散列的, 所以它打破了这种传递关系.
+* 在[这里](https://www.naftaliharris.com/blog/python-subclass-intransitivity/)可以找到更详细的解释.
+
+---
+
+### > Methods equality and identity/方法的相等性和唯一性 *
+
+
+1.
+```py
+class SomeClass:
+ def method(self):
+ pass
+
+ @classmethod
+ def classm(cls):
+ pass
+
+ @staticmethod
+ def staticm():
+ pass
+```
+
+**Output:**
+```py
+>>> print(SomeClass.method is SomeClass.method)
+True
+>>> print(SomeClass.classm is SomeClass.classm)
+False
+>>> print(SomeClass.classm == SomeClass.classm)
+True
+>>> print(SomeClass.staticm is SomeClass.staticm)
+True
+```
+访问 `classm` 两次,我们得到一个相等的对象,但不是*同一个*? 让我们看看 `SomeClass` 的实例会发生什么:
+
+2.
+```py
+o1 = SomeClass()
+o2 = SomeClass()
+```
+
+**Output:**
+```py
+>>> print(o1.method == o2.method)
+False
+>>> print(o1.method == o1.method)
+True
+>>> print(o1.method is o1.method)
+False
+>>> print(o1.classm is o1.classm)
+False
+>>> print(o1.classm == o1.classm == o2.classm == SomeClass.classm)
+True
+>>> print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm)
+True
+```
+
+访问 ` classm` or `method` 两次, 为 `SomeClass` 的同一个实例创建了相等但是*不同*的对象。
+
+#### 💡 说明
+* 函数是[描述符](https://docs.python.org/3/howto/descriptor.html)。每当将函数作为属性访问时,就会调用描述符,创建一个方法对象,该对象将函数与拥有该属性的对象“绑定”。如果被调用,该方法调用函数,隐式传递绑定对象作为第一个参数(这就是我们如何将 self 作为第一个参数获取,尽管没有显式传递它)。
+
+```py
+>>> o1.method
+>
+```
+
+* 多次访问该属性,每次都会创建一个方法对象! 因此,`o1.method is o1.method` 永远不会是真的。但是,将函数作为类属性(而不是实例)访问并不会创建方法对象,所以 `SomeClass.method is SomeClass.method` 是真的。
+
+```py
+>>> SomeClass.method
+
+```
+
+* `classmethod` 将函数转换为类方法。 类方法是描述符,当被访问时,它会创建一个绑定*类本身*的方法对象,而不是对象本身。
+
+```py
+>>> o1.classm
+>
+```
+
+* 与函数不同,`classmethod` 在作为类属性访问时也会创建一个方法(在这种情况下,它们绑定类,而不是类的类型)。 所以 `SomeClass.classm is SomeClass.classm` 是假的。
+
+```py
+>>> SomeClass.classm
+>
+```
+
+* 当两个函数相等并且绑定的对象相同时,方法对象比较相等。 所以`o1.method == o1.method` 为真,尽管它们在内存中是两个不同的对象。
+* `staticmethod` 将函数转换为“无操作”描述符,它按原样返回函数。没有方法对象被创建,所以 `is` 的比较运算为真。
+
+```py
+>>> o1.staticm
+
+>>> SomeClass.staticm
+
+```
+
+* 每次 Python 调用实例方法时都必须创建新的“方法”对象,并且每次都必须修改参数以插入 `self` 严重影响性能。CPython 3.7 [解决了这个问题](https://bugs.python.org/issue26110) 。通过引入新的操作码来处理调用方法而不创建临时方法对象。这仅在实际调用访问的函数时使用,因此这里的代码片段不受影响,仍然会生成方法:)
+
+
+---
+
+### > All-true-ation/返回True的all函数 *
+
+
+
+```py
+>>> all([True, True, True])
+True
+>>> all([True, True, False])
+False
+
+>>> all([])
+True
+>>> all([[]])
+False
+>>> all([[[]]])
+True
+```
+
+为什么会有这种True-False的变化?
+
+#### 💡 说明
+
+- `all` 函数的实现等价于:
+
+- ```py
+ def all(iterable):
+ for element in iterable:
+ if not element:
+ return False
+ return True
+ ```
+
+- `all([])` 返回 `True` 因为可迭代对象为空。
+- `all([[]])` 返回 `False` 因为传入的数组有一个元素 `[]`, 在Python中,空列表为假。
+- `all([[[]]])` 和更高的递归变体总是`True`。 这是因为传递的数组的单个元素(`[[...]]`)不再是空的,而有值的列表为真。
+
+
+---
+
+### > The surprising comma/意外的逗号
+
+**Output:**
+```py
+>>> def f(x, y,):
+... print(x, y)
+...
+>>> def g(x=4, y=5,):
+... print(x, y)
+...
+>>> def h(x, **kwargs,):
+ File "", line 1
+ def h(x, **kwargs,):
+ ^
+SyntaxError: invalid syntax
+>>> def h(*args,):
+ File "", line 1
+ def h(*args,):
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 说明:
+
+- 在Python函数的形式参数列表中, 尾随逗号并不一定是合法的.
+- 在Python中, 参数列表部分用前置逗号定义, 部分用尾随逗号定义. 这种冲突导致逗号被夹在中间, 没有规则定义它.(译:这一句看得我也很懵逼,只能强翻了.详细解释看下面的讨论帖会一目了然.)
+- **注意:** 尾随逗号的问题已经在Python 3.6中被[修复](https://bugs.python.org/issue9232)了. 而这篇[帖子](https://bugs.python.org/issue9232#msg248399)中则简要讨论了Python中尾随逗号的不同用法.
+---
+
+### > Strings and the backslashes/字符串与反斜杠
+
+
+**Output:**
+```py
+>>> print("\"")
+"
+
+>>> print(r"\"")
+\"
+
+>>> print(r"\")
+File "", line 1
+ print(r"\")
+ ^
+SyntaxError: EOL while scanning string literal
+
+>>> r'\'' == "\\'"
+True
+```
+
+#### 💡 说明:
+
+- 在一般的python字符串中,反斜杠用于转义可能具有特殊含义的字符(如单引号、双引号和反斜杠本身)。
+
+ ```py
+ >>> "wt\"f"
+ 'wt"f'
+ ```
+
+- 在以 `r` 开头的原始字符串中, 反斜杠并没有特殊含义.
+ ```py
+ >>> r'wt\"f' == 'wt\\"f'
+ True
+ >>> print(repr(r'wt\"f')
+ 'wt\\"f'
+
+ >>> print("\n")
+
+ >>> print(r"\\n")
+ '\\n'
+ ```
+
+- 这意味着当解析器在原始字符串中遇到反斜杠时,它期望后面有另一个字符。 在我们的例子中(`print(r"\")`),反斜杠转义了尾随引号,使解析器没有终止引号(因此产生了`SyntaxError`)。 这就是为什么反斜杠在原始字符串末尾不起作用的原因。
+
+
+---
+
+### > not knot!/别纠结!
+
+```py
+x = True
+y = False
+```
+
+**Output:**
+```py
+>>> not x == y
+True
+>>> x == not y
+ File "", line 1
+ x == not y
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 说明:
+
+* 运算符的优先级会影响表达式的求值顺序, 而在 Python 中 `==` 运算符的优先级要高于 `not` 运算符.
+* 所以 `not x == y` 相当于 `not (x == y)`, 同时等价于 `not (True == False)`, 最后的运算结果就是 `True`.
+* 之所以 `x == not y` 会抛一个 `SyntaxError` 异常, 是因为它会被认为等价于 `(x == not) y`, 而不是你一开始期望的 `x == (not y)`.
+* 解释器期望 `not` 标记是 `not in` 操作符的一部分 (因为 `==` 和 `not in` 操作符具有相同的优先级), 但是它在 `not` 标记后面找不到 `in` 标记, 所以会抛出 `SyntaxError` 异常.
+
+---
+
+### > Half triple-quoted strings/三个引号
+
+**Output:**
+```py
+>>> print('wtfpython''')
+wtfpython
+>>> print("wtfpython""")
+wtfpython
+>>> # 下面的语句会抛出 `SyntaxError` 异常
+>>> # print('''wtfpython')
+>>> # print("""wtfpython")
+```
+
+#### 💡 说明:
++ Python 提供隐式的[字符串连接](https://docs.python.org/2/reference/lexical_analysis.html#string-literal-concatenation), 例如,
+ ```
+ >>> print("wtf" "python")
+ wtfpython
+ >>> print("wtf" "") # or "wtf"""
+ wtf
+ ```
++ `'''` 和 `"""` 在 Python中也是字符串定界符, Python 解释器在先遇到三个引号的的时候会尝试再寻找三个终止引号作为定界符, 如果不存在则会导致 `SyntaxError` 异常.
+
+
+---
+
+### > What's wrong with booleans?/布尔你咋了?
+
+1\.
+```py
+# 一个简单的例子, 统计下面可迭代对象中的布尔型值的个数和整型值的个数
+mixed_list = [False, 1.0, "some_string", 3, True, [], False]
+integers_found_so_far = 0
+booleans_found_so_far = 0
+
+for item in mixed_list:
+ if isinstance(item, int):
+ integers_found_so_far += 1
+ elif isinstance(item, bool):
+ booleans_found_so_far += 1
+```
+
+**Output:**
+```py
+>>> integers_found_so_far
+4
+>>> booleans_found_so_far
+0
+```
+
+2\.
+```py
+>>> some_bool = True
+>>> "wtf" * some_bool
+'wtf'
+>>> some_bool = False
+>>> "wtf" * some_bool
+''
+```
+
+3\.
+```py
+def tell_truth():
+ True = False
+ if True == False:
+ print("I have lost faith in truth!")
+```
+
+**Output (< 3.x):**
+
+```py
+>>> tell_truth()
+I have lost faith in truth!
+```
+
+
+#### 💡 说明:
+
+* 布尔值是 `int` 的子类
+ ```py
+ >>> issubclass(bool, int)
+ True
+ >>> issubclass(int, bool)
+ False
+ ```
+
+* 因此,`True` 和 `False` 是 `int` 的实例
+ ```py
+ >>> isinstance(True, int)
+ True
+ >>> isinstance(False, int)
+ True
+ ```
+
+* `True` 的整数值是 `1`, 而 `False` 的整数值是 `0`
+
+ ```py
+ >>> int(True)
+ 1
+ >>> int(False)
+ 0
+ ```
+
+* 关于其背后的原理, 请看这个 StackOverflow 的[回答](https://stackoverflow.com/a/8169049/4354153).
+
+* 最初,Python 没有 `bool` 类型(人们使用 0 表示假,使用非零值,如 1 表示真)。`True`、`False` 和 `bool` 类型在 2.x 版本中被添加,但为了向后兼容,`True` 和 `False` 不能成为常量。它们只是内置变量,可以重新分配它们
+
+* Python 3 向后不兼容,问题终于得到解决,因此最后一个代码段不适用于 Python 3.x!
+
+
+---
+
+### > Class attributes and instance attributes/类属性和实例属性
+
+1\.
+```py
+class A:
+ x = 1
+
+class B(A):
+ pass
+
+class C(A):
+ pass
+```
+
+**Output:**
+```py
+>>> A.x, B.x, C.x
+(1, 1, 1)
+>>> B.x = 2
+>>> A.x, B.x, C.x
+(1, 2, 1)
+>>> A.x = 3
+>>> A.x, B.x, C.x
+(3, 2, 3)
+>>> a = A()
+>>> a.x, A.x
+(3, 3)
+>>> a.x += 1
+>>> a.x, A.x
+(4, 3)
+```
+
+2\.
+```py
+class SomeClass:
+ some_var = 15
+ some_list = [5]
+ another_list = [5]
+ def __init__(self, x):
+ self.some_var = x + 1
+ self.some_list = self.some_list + [x]
+ self.another_list += [x]
+```
+
+**Output:**
+
+```py
+>>> some_obj = SomeClass(420)
+>>> some_obj.some_list
+[5, 420]
+>>> some_obj.another_list
+[5, 420]
+>>> another_obj = SomeClass(111)
+>>> another_obj.some_list
+[5, 111]
+>>> another_obj.another_list
+[5, 420, 111]
+>>> another_obj.another_list is SomeClass.another_list
+True
+>>> another_obj.another_list is some_obj.another_list
+True
+```
+
+#### 💡 说明:
+
+* 类变量和实例变量在内部是通过类对象的字典来处理(译: 就是 `__dict__` 属性). 如果在当前类的字典中找不到的话就去它的父类中寻找.
+* `+=` 运算符会在原地修改可变对象, 而不是创建新对象. 因此, 在这种情况下, 修改一个实例的属性会影响其他实例和类属性.
+
+---
+
+### > yielding None/生成 None
+
+```py
+some_iterable = ('a', 'b')
+
+def some_func(val):
+ return "something"
+```
+
+**Output:**
+```py
+>>> [x for x in some_iterable]
+['a', 'b']
+>>> [(yield x) for x in some_iterable]
+ at 0x7f70b0a4ad58>
+>>> list([(yield x) for x in some_iterable])
+['a', 'b']
+>>> list((yield x) for x in some_iterable)
+['a', None, 'b', None]
+>>> list(some_func((yield x)) for x in some_iterable)
+['a', 'something', 'b', 'something']
+```
+
+#### 💡 说明:
+- 来源和解释可以在这里找到: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions
+- 相关错误报告: http://bugs.python.org/issue10544
+- 这个bug在3.7以后的版本中不被推荐使用, 并在3.8中被修复. 因此在3.8中尝试在推导式中使用 yield, 只会得到一个 SyntaxError. 详细内容可以看[3.7更新内容](https://docs.python.org/dev/whatsnew/3.7.html#deprecated-python-behavior), [3.8更新内容](https://docs.python.org/dev/whatsnew/3.8.html#changes-in-python-behavior).
+
+
+---
+
+### > Yielding from... return!/生成器里的return *
+
+1\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ yield from range(x)
+```
+
+**Output (> 3.3):**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+`"wtf"` 去哪儿了?是因为`yield from`的一些特殊效果吗?让我们验证一下
+
+2\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ for i in range(x):
+ yield i
+```
+
+**Output:**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+同样的结果,这里也不起作用。
+
+#### 💡 说明
+
++ 从 Python 3.3 开始,可以在生成器中使用带有值的 `return` 语句(参见 [PEP380](https://www.python.org/dev/peps/pep-0380/))。 [官方文档](https://www.python.org/dev/peps/pep-0380/#enhancements-to-stopiteration) 描述,
+
+> "... 生成器中的 `return expr` 会导致在退出生成器时引发 `StopIteration(expr)`。"
+
++ 在 `some_func(3)` 例子中,`return` 语句在开始就引发了`StopIteration`。 `StopIteration` 异常会在`list(...)` 包装器和`for` 循环中自动捕获。 因此,以上两个片段都产生的是一个空列表。
+
++ 要从生成器 `some_func` 中获取 `["wtf"]`,我们需要捕获 `StopIteration` 异常,
+
+ ```py
+ try:
+ next(some_func(3))
+ except StopIteration as e:
+ some_string = e.value
+ ```
+
+ ```py
+ >>> some_string
+ ["wtf"]
+ ```
+
+
+---
+
+### > Nan-reflexivity/Nan的自反性
+
+
+1\.
+```py
+a = float('inf')
+b = float('nan')
+c = float('-iNf') # 这些字符串不区分大小写
+d = float('nan')
+```
+
+**Output:**
+```py
+>>> a
+inf
+>>> b
+nan
+>>> c
+-inf
+>>> float('some_other_string')
+ValueError: could not convert string to float: some_other_string
+>>> a == -c #inf==inf
+True
+>>> None == None # None==None
+True
+>>> b == d #但是 nan!=nan
+False
+>>> 50/a
+0.0
+>>> a/a
+nan
+>>> 23 + b
+nan
+```
+
+2\.
+
+```py
+>>> x = float('nan')
+>>> y = x / x
+>>> y is y # 同一性(identity)具备
+True
+>>> y == y # y不具备相等性(equality)
+False
+>>> [y] == [y] # 但包含y的列表验证相等性(equality)成功了
+True
+```
+
+#### 💡 说明:
+
+`'inf'` 和 `'nan'` 是特殊的字符串(不区分大小写), 当显示转换成 `float` 型时, 它们分别用于表示数学意义上的 "无穷大" 和 "非数字".
+- 由于根据 IEEE 标准 `NaN != NaN`,遵守此规则打破了 Python 中集合元素的自反性假设,即如果 `x` 是 `list` 等集合的一部分,则比较等运算的实现基于假设`x == x`。由于这个假设,在比较两个元素时首先比较身份`identity`(因为它更快),并且仅在身份不匹配时才比较值。以下片段将更清楚地说明,
+
+ ```py
+ >>> x = float('nan')
+ >>> x == x, [x] == [x]
+ (False, True)
+ >>> y = float('nan')
+ >>> y == y, [y] == [y]
+ (False, True)
+ >>> x == y, [x] == [y]
+ (False, False)
+ ```
+
+ 由于 `x` 和 `y` 的身份`identity`不同,所以考虑的值也不同; 因此这次比较返回“False”。
+
+
+- 感兴趣可以阅读 [Reflexivity, and other pillars of civilization](https://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/)
+
+
+---
+
+### > Mutating the immutable!/强人所难
+
+```py
+some_tuple = ("A", "tuple", "with", "values")
+another_tuple = ([1, 2], [3, 4], [5, 6])
+```
+
+**Output:**
+```py
+>>> some_tuple[2] = "change this"
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple[2].append(1000) # 这里不出现错误
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000])
+>>> another_tuple[2] += [99, 999]
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000, 99, 999])
+```
+
+我还以为元组是不可变的呢...
+
+#### 💡 说明:
+
+* 引用 https://docs.python.org/2/reference/datamodel.html
+
+ > 不可变序列
+ 不可变序列的对象一旦创建就不能再改变. (如果对象包含对其他对象的引用,则这些其他对象可能是可变的并且可能会被修改; 但是,由不可变对象直接引用的对象集合不能更改.)
+
+* `+=` 操作符在原地修改了列表. 元素赋值操作并不工作, 但是当异常抛出时, 元素已经在原地被修改了.
+
+(译: 对于不可变对象, 这里指tuple, `+=` 并不是原子操作, 而是 `extend` 和 `=` 两个动作, 这里 `=` 操作虽然会抛出异常, 但 `extend` 操作已经修改成功了. 详细解释可以看[这里](https://segmentfault.com/a/1190000010767068))
+
+---
+
+### > The disappearing variable from outer scope/消失的外部变量
+
+```py
+e = 7
+try:
+ raise Exception()
+except Exception as e:
+ pass
+```
+
+**Output (Python 2.x):**
+```py
+>>> print(e)
+# prints nothing
+```
+
+**Output (Python 3.x):**
+```py
+>>> print(e)
+NameError: name 'e' is not defined
+```
+
+#### 💡 说明:
+
+* 出处: https://docs.python.org/3/reference/compound_stmts.html#except
+
+ 当使用 `as` 为目标分配异常的时候, 将在except子句的末尾清除该异常.
+
+ 这就好像
+
+ ```py
+ except E as N:
+ foo
+ ```
+
+ 会被翻译成
+
+ ```py
+ except E as N:
+ try:
+ foo
+ finally:
+ del N
+ ```
+
+ 这意味着异常必须在被赋值给其他变量才能在 `except` 子句之后引用它. 而异常之所以会被清除, 则是由于上面附加的回溯信息(trackback)会和栈帧(stack frame)形成循环引用, 使得该栈帧中的所有本地变量在下一次垃圾回收发生之前都处于活动状态.(译: 也就是说不会被回收)
+
+* 子句在 Python 中并没有独立的作用域. 示例中的所有内容都处于同一作用域内, 所以变量 `e` 会由于执行了 `except` 子句而被删除. 而对于有独立的内部作用域的函数来说情况就不一样了. 下面的例子说明了这一点:
+
+ ```py
+ def f(x):
+ del(x)
+ print(x)
+
+ x = 5
+ y = [5, 4, 3]
+ ```
+
+ **Output:**
+ ```py
+ >>>f(x)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>>f(y)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> x
+ 5
+ >>> y
+ [5, 4, 3]
+ ```
+
+* 在 Python 2.x 中, `Exception()` 实例被赋值给了变量 `e`, 所以当你尝试打印结果的时候, 它的输出为空.(译: 正常的Exception实例打印出来就是空)
+
+ **Output (Python 2.x):**
+ ```py
+ >>> e
+ Exception()
+ >>> print e
+ # 没有打印任何内容!
+ ```
+
+
+---
+
+### > The mysterious key type conversion/神秘的键型转换 *
+
+```py
+class SomeClass(str):
+ pass
+
+some_dict = {'s':42}
+```
+
+**Output:**
+```py
+>>> type(list(some_dict.keys())[0])
+str
+>>> s = SomeClass('s')
+>>> some_dict[s] = 40
+>>> some_dict # 预期: 两个不同的键值对
+{'s': 40}
+>>> type(list(some_dict.keys())[0])
+str
+```
+
+#### 💡 说明:
+
+* 由于 `SomeClass` 会从 `str` 自动继承 `__hash__` 方法, 所以 `s` 对象和 `"s"` 字符串的哈希值是相同的.
+* 而 `SomeClass("s") == "s"` 为 `True` 是因为 `SomeClass` 也继承了 `str` 类 `__eq__` 方法.
+* 由于两者的哈希值相同且相等, 所以它们在字典中表示相同的键.
+* 如果想要实现期望的功能, 我们可以重定义 `SomeClass` 的 `__eq__` 方法.
+ ```py
+ class SomeClass(str):
+ def __eq__(self, other):
+ return (
+ type(self) is SomeClass
+ and type(other) is SomeClass
+ and super().__eq__(other)
+ )
+
+ # 当我们自定义 __eq__ 方法时, Python 不会再自动继承 __hash__ 方法
+ # 所以我们也需要定义它
+ __hash__ = str.__hash__
+
+ some_dict = {'s':42}
+ ```
+
+ **Output:**
+ ```py
+ >>> s = SomeClass('s')
+ >>> some_dict[s] = 40
+ >>> some_dict
+ {'s': 40, 's': 42}
+ >>> keys = list(some_dict.keys())
+ >>> type(keys[0]), type(keys[1])
+ (__main__.SomeClass, str)
+ ```
+
+---
+
+### > Let's see if you can guess this?/看看你能否猜到这一点?
+
+```py
+a, b = a[b] = {}, 5
+```
+
+**Output:**
+```py
+>>> a
+{5: ({...}, 5)}
+```
+
+#### 💡 说明:
+
+* 根据 [Python 语言参考](https://docs.python.org/2/reference/simple_stmts.html#assignment-statements), 赋值语句的形式如下
+ ```
+ (target_list "=")+ (expression_list | yield_expression)
+ ```
+
+ > 赋值语句计算表达式列表(expression list)(牢记 这可以是单个表达式或以逗号分隔的列表, 后者返回元组)并将单个结果对象从左到右分配给目标列表中的每一项.
+
+* `(target_list "=")+` 中的 `+` 意味着可以有**一个或多个**目标列表. 在这个例子中, 目标列表是 `a, b` 和 `a[b]` (注意表达式列表只能有一个, 在我们的例子中是 `{}, 5`).
+
+* 表达式列表计算结束后, 将其值自动解包后**从左到右**分配给目标列表(target list). 因此, 在我们的例子中, 首先将 `{}, 5` 元组并赋值给 `a, b`, 然后我们就可以得到 `a = {}` 且 `b = 5`.
+
+* `a` 被赋值的 `{}` 是可变对象.
+
+* 第二个目标列表是 `a[b]` (你可能觉得这里会报错, 因为在之前的语句中 `a` 和 `b` 都还没有被定义. 但是别忘了, 我们刚刚将 `a` 赋值 `{}` 且将 `b` 赋值为 `5`).
+
+* 现在, 我们将通过将字典中键 `5` 的值设置为元组 `({}, 5)` 来创建循环引用 (输出中的 `{...}` 指与 `a` 引用了相同的对象). 下面是一个更简单的循环引用的例子
+ ```py
+ >>> some_list = some_list[0] = [0]
+ >>> some_list
+ [[...]]
+ >>> some_list[0]
+ [[...]]
+ >>> some_list is some_list[0]
+ True
+ >>> some_list[0][0][0][0][0][0] == some_list
+ True
+ ```
+ 我们的例子就是这种情况 (`a[b][0]` 与 `a` 是相同的对象)
+
+* 总结一下, 你也可以把例子拆成
+ ```py
+ a, b = {}, 5
+ a[b] = a, b
+ ```
+ 并且可以通过 `a[b][0]` 与 `a` 是相同的对象来证明是循环引用
+ ```py
+ >>> a[b][0] is a
+ True
+ ```
+
+
+---
+
+### > Exceeds the limit for integer string conversion/整型转字符串越界
+```py
+>>> # Python 3.10.6
+>>> int("2" * 5432)
+
+>>> # Python 3.10.8
+>>> int("2" * 5432)
+```
+
+**Output:**
+```py
+>>> # Python 3.10.6
+222222222222222222222222222222222222222222222222222222222222222...
+
+>>> # Python 3.10.8
+Traceback (most recent call last):
+ ...
+ValueError: Exceeds the limit (4300) for integer string conversion:
+ value has 5432 digits; use sys.set_int_max_str_digits()
+ to increase the limit.
+```
+
+#### 💡 说明:
+
+* 对`int()`的调用在Python 3.10.6中运行良好,但在Python 3.10.8中引发ValueError。请注意,Python仍然可以处理大整数。只有在整型和字符串之间转换时才会出现此错误。
+* 幸运的是,当您希望操作超过允许的位数限制时,可以增加该限制的上限。为此,可以使用以下方法之一:
+ - 使用 -X int_max_str_digits 的命令行参数(例如, python3 -X int_max_str_digits=640)
+ - 使用来自sys模块的set_int_max_str_digits()函数
+ - 设定 PYTHONINTMAXSTRDIGITS 环境变量
+
+更多更改设置上限的操作细节查看[文档](https://docs.python.org/3/library/stdtypes.html#int-max-str-digits)。
+
+---
+
+## Section: Slippery Slopes/滑坡谬误
+
+
+### > Modifying a dictionary while iterating over it/迭代字典时的修改
+
+```py
+x = {0: None}
+
+for i in x:
+ del x[i]
+ x[i+1] = None
+ print(i)
+```
+
+**Output (Python 2.7- Python 3.5):**
+
+```
+0
+1
+2
+3
+4
+5
+6
+7
+```
+
+是的, 它运行了**八次**然后才停下来.
+
+#### 💡 说明:
+
+* Python不支持对字典进行迭代的同时修改它.
+* 它之所以运行8次, 是因为字典会自动扩容以容纳更多键值(我们有8次删除记录, 因此需要扩容). 这实际上是一个实现细节. (译: 应该是因为字典的初始最小值是8, 扩容会导致散列表地址发生变化而中断循环.)
+* 在不同的Python实现中删除键的处理方式以及调整大小的时间可能会有所不同.(译: 就是说什么时候扩容在不同版本中可能是不同的, 在3.6及3.7的版本中到[5](https://github.com/python/cpython/blob/v3.6.1/Objects/dictobject.c#L103-L110)就会自动扩容了. 以后也有可能再次发生变化. 这是为了避免散列冲突. 顺带一提, 后面两次扩容会扩展为32和256. 即`8->32->256`.)
+* 更多的信息, 你可以参考这个StackOverflow的[回答](https://stackoverflow.com/questions/44763802/bug-in-python-dict), 它详细的解释一个类似的例子.
+
+---
+
+### > Stubborn `del` operator/坚强的 `del` *
+
+```py
+class SomeClass:
+ def __del__(self):
+ print("Deleted!")
+```
+
+**Output:**
+1\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x # 这里应该会输出 "Deleted!"
+>>> del y
+Deleted!
+```
+
+唷, 终于删除了. 你可能已经猜到了在我们第一次尝试删除 `x` 时是什么让 `__del__` 免于被调用的. 那让我们给这个例子增加点难度.
+
+2\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x
+>>> y # 检查一下y是否存在
+<__main__.SomeClass instance at 0x7f98a1a67fc8>
+>>> del y # 像之前一样, 这里应该会输出 "Deleted!"
+>>> globals() # 好吧, 并没有. 让我们看一下所有的全局变量
+Deleted!
+{'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
+```
+
+好了,现在它被删除了 :confused:
+
+#### 💡 说明:
++ `del x` 并不会立刻调用 `x.__del__()`.
++ 每当遇到 `del x`, Python 会将 `x` 的引用数减1, 当 `x` 的引用数减到0时就会调用 `x.__del__()`.
++ 在第二个例子中, `y.__del__()` 之所以未被调用, 是因为前一条语句 (`>>> y`) 对同一对象创建了另一个引用, 从而防止在执行 `del y` 后对象的引用数变为0.
++ 调用 `globals` 导致引用被销毁, 因此我们可以看到 "Deleted!" 终于被输出了.
++ (译: 这其实是 Python 交互解释器的特性, 它会自动让 `_` 保存上一个表达式输出的值, 详细可以看[这里](https://www.cnblogs.com/leisurelylicht/p/diao-pi-de-kong-zhi-tai.html).)
+
+
+---
+
+### > The out of scope variable/外部作用域变量
+
+
+1\.
+```py
+a = 1
+def some_func():
+ return a
+
+def another_func():
+ a += 1
+ return a
+```
+
+2\.
+```py
+def some_closure_func():
+ a = 1
+ def some_inner_func():
+ return a
+ return some_inner_func()
+
+def another_closure_func():
+ a = 1
+ def another_inner_func():
+ a += 1
+ return a
+ return another_inner_func()
+```
+
+**Output:**
+```py
+>>> some_func()
+1
+>>> another_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+
+>>> some_closure_func()
+1
+>>> another_closure_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+```
+
+#### 💡 说明:
+* 当你在作用域中对变量进行赋值时, 变量会变成该作用域内的局部变量. 因此 `a` 会变成 `another_func` 函数作用域中的局部变量, 但它在函数作用域中并没有被初始化, 所以会引发错误.
+* 想要在 `another_func` 中修改外部作用域变量 `a` 的话, 可以使用 `global` 关键字.
+ ```py
+ def another_func()
+ global a
+ a += 1
+ return a
+ ```
+
+ **Output:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+
+* 在 `another_closure_func` 函数中,`a` 会变成 `another_inner_func` 函数作用域中的局部变量, 但它在同一作用域中并没有被初始化, 所以会引发错误。
+* 想要在 `another_inner_func` 中修改外部作用域变量 `a` 的话, 可以使用 `nonlocal` 关键字。nonlocal 表达式用于(除全局作用域外)最近一级的外部作用域。
+
+ ```py
+ def another_func():
+ a = 1
+ def another_inner_func():
+ nonlocal a
+ a += 1
+ return a
+ return another_inner_func()
+ ```
+
+ **Output:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+
+* `global` and `nonlocal` 关键字告诉 `Python` 解释器,不要声明新变量,而是在相应的外部作用域中查找变量。
+* 可以阅读[这个](https://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html)简短却很棒的指南, 了解更多关于 Python 中命名空间和作用域的工作原理。
+
+
+---
+
+### > Deleting a list item while iterating/迭代列表时删除元素
+
+```py
+list_1 = [1, 2, 3, 4]
+list_2 = [1, 2, 3, 4]
+list_3 = [1, 2, 3, 4]
+list_4 = [1, 2, 3, 4]
+
+for idx, item in enumerate(list_1):
+ del item
+
+for idx, item in enumerate(list_2):
+ list_2.remove(item)
+
+for idx, item in enumerate(list_3[:]):
+ list_3.remove(item)
+
+for idx, item in enumerate(list_4):
+ list_4.pop(idx)
+```
+
+**Output:**
+```py
+>>> list_1
+[1, 2, 3, 4]
+>>> list_2
+[2, 4]
+>>> list_3
+[]
+>>> list_4
+[2, 4]
+```
+
+你能猜到为什么输出是 `[2, 4]` 吗?
+
+#### 💡 说明:
+
+* 在迭代时修改对象是一个很愚蠢的主意. 正确的做法是迭代对象的副本, `list_3[:]` 就是这么做的.
+
+ ```py
+ >>> some_list = [1, 2, 3, 4]
+ >>> id(some_list)
+ 139798789457608
+ >>> id(some_list[:]) # 注意python为切片列表创建了新对象.
+ 139798779601192
+ ```
+
+**`del`, `remove` 和 `pop` 的不同:**
+* `del var_name` 只是从本地或全局命名空间中删除了 `var_name` (这就是为什么 `list_1` 没有受到影响).
+* `remove` 会删除第一个匹配到的指定值, 而不是特定的索引, 如果找不到值则抛出 `ValueError` 异常.
+* `pop` 则会删除指定索引处的元素并返回它, 如果指定了无效的索引则抛出 `IndexError` 异常.
+
+**为什么输出是 `[2, 4]`?**
+- 列表迭代是按索引进行的, 所以当我们从 `list_2` 或 `list_4` 中删除 `1` 时, 列表的内容就变成了 `[2, 3, 4]`. 剩余元素会依次位移, 也就是说, `2` 的索引会变为 0, `3` 会变为 1. 由于下一次迭代将获取索引为 1 的元素 (即 `3`), 因此 `2` 将被彻底的跳过. 类似的情况会交替发生在列表中的每个元素上.
+
+* 参考这个StackOverflow的[回答](https://stackoverflow.com/questions/45946228/what-happens-when-you-try-to-delete-a-list-element-while-iterating-over-it)来解释这个例子
+* 关于Python中字典的类似例子, 可以参考这个Stackoverflow的[回答](https://stackoverflow.com/questions/45877614/how-to-change-all-the-dictionary-keys-in-a-for-loop-with-d-items).
+
+
+---
+
+### > Lossy zip of iterators/丢三落四的zip *
+
+
+```py
+>>> numbers = list(range(7))
+>>> numbers
+[0, 1, 2, 3, 4, 5, 6]
+>>> first_three, remaining = numbers[:3], numbers[3:]
+>>> first_three, remaining
+([0, 1, 2], [3, 4, 5, 6])
+>>> numbers_iter = iter(numbers)
+>>> list(zip(numbers_iter, first_three))
+[(0, 0), (1, 1), (2, 2)]
+# so far so good, let's zip the remaining
+>>> list(zip(numbers_iter, remaining))
+[(4, 3), (5, 4), (6, 5)]
+```
+
+`numbers` 列表中的元素 `3` 哪里去了?
+
+#### 💡 说明
+
+- 根据Python [文档](https://docs.python.org/3.3/library/functions.html#zip), `zip` 函数的大概实现如下:
+
+ ```py
+ def zip(*iterables):
+ sentinel = object()
+ iterators = [iter(it) for it in iterables]
+ while iterators:
+ result = []
+ for it in iterators:
+ elem = next(it, sentinel)
+ if elem is sentinel: return
+ result.append(elem)
+ yield tuple(result)
+ ```
+
+- 该函数接受任意数量的可迭代对象,通过调用 `next` 函数将它们的每个项目添加到 `result` 列表中,并在任一可迭代对象耗尽时停止。
+- 这里需要注意的是,当任一可迭代对象用尽时,`result` 列表中的现有元素将被丢弃。这就是 `numbers_iter` 中的 `3` 所发生的情况。
+- 使用 zip 执行上述操作的正确方法是:
+
+ ```py
+ >>> numbers = list(range(7))
+ >>> numbers_iter = iter(numbers)
+ >>> list(zip(first_three, numbers_iter))
+ [(0, 0), (1, 1), (2, 2)]
+ >>> list(zip(remaining, numbers_iter))
+ [(3, 3), (4, 4), (5, 5), (6, 6)]
+ ```
+
+ `zip` 的第一个参数应当是有最少元素的那个。
+
+
+---
+
+### > Loop variables leaking out!/循环变量泄漏!
+
+1\.
+```py
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Output:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+但是 `x` 从未在循环外被定义...
+
+2\.
+```py
+# 这次我们先初始化x
+x = -1
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Output:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+3\.
+```
+x = 1
+print([x for x in range(5)])
+print(x, ': x in global')
+```
+
+**Output (on Python 2.x):**
+```
+[0, 1, 2, 3, 4]
+(4, ': x in global')
+```
+
+**Output (on Python 3.x):**
+```
+[0, 1, 2, 3, 4]
+1 : x in global
+```
+
+#### 💡 说明:
+
+- 在 Python 中, for 循环使用所在作用域并在结束后保留定义的循环变量. 如果我们曾在全局命名空间中定义过循环变量. 在这种情况下, 它会重新绑定现有变量.
+
+- Python 2.x 和 Python 3.x 解释器在列表推导式示例中的输出差异, 在文档 [What’s New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html) 中可以找到相关的解释:
+
+ > "列表推导不再支持句法形式 `[... for var in item1, item2, ...]`. 取而代之的是 `[... for var in (item1, item2, ...)]`. 另外, 注意列表推导具有不同的语义: 它们更接近于 `list()` 构造函数中生成器表达式的语法糖(译: 这一句我也不是很明白), 特别是循环控制变量不再泄漏到周围的作用域中."
+
+---
+
+### > Beware of default mutable arguments!/当心默认的可变参数!
+
+```py
+def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+```
+
+**Output:**
+```py
+>>> some_func()
+['some_string']
+>>> some_func()
+['some_string', 'some_string']
+>>> some_func([])
+['some_string']
+>>> some_func()
+['some_string', 'some_string', 'some_string']
+```
+
+#### 💡 说明:
+
+- Python中函数的默认可变参数并不是每次调用该函数时都会被初始化. 相反, 它们会使用最近分配的值作为默认值. 当我们明确的将 `[]` 作为参数传递给 `some_func` 的时候, 就不会使用 `default_arg` 的默认值, 所以函数会返回我们所期望的结果.
+
+ ```py
+ def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+ **Output:**
+ ```py
+ >>> some_func.__defaults__ # 这里会显示函数的默认参数的值
+ ([],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string'],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ >>> some_func([])
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ ```
+
+- 避免可变参数导致的错误的常见做法是将 `None` 指定为参数的默认值, 然后检查是否有值传给对应的参数. 例:
+
+ ```py
+ def some_func(default_arg=None):
+ if not default_arg:
+ default_arg = []
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+---
+
+### > Catching the Exceptions/捕获异常
+
+```py
+some_list = [1, 2, 3]
+try:
+ # 这里会抛出异常 ``IndexError``
+ print(some_list[4])
+except IndexError, ValueError:
+ print("Caught!")
+
+try:
+ # 这里会抛出异常 ``ValueError``
+ some_list.remove(4)
+except IndexError, ValueError:
+ print("Caught again!")
+```
+
+**Output (Python 2.x):**
+```py
+Caught!
+
+ValueError: list.remove(x): x not in list
+```
+
+**Output (Python 3.x):**
+```py
+ File "", line 3
+ except IndexError, ValueError:
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 说明:
+
+* 如果你想要同时捕获多个不同类型的异常时, 你需要将它们用括号包成一个元组作为第一个参数传递. 第二个参数是可选名称, 如果你提供, 它将与被捕获的异常实例绑定. 例,
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ # 这里会抛出异常 ``ValueError``
+ some_list.remove(4)
+ except (IndexError, ValueError), e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Output (Python 2.x):**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+ **Output (Python 3.x):**
+ ```py
+ File "", line 4
+ except (IndexError, ValueError), e:
+ ^
+ IndentationError: unindent does not match any outer indentation level
+ ```
+
+* 在 Python 3 中, 用逗号区分异常与可选名称是无效的; 正确的做法是使用 `as` 关键字. 例,
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ some_list.remove(4)
+
+ except (IndexError, ValueError) as e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Output:**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+
+---
+
+### > Same operands, different story!/同人不同命!
+
+1\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a = a + [5, 6, 7, 8]
+```
+
+**Output:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4]
+```
+
+2\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a += [5, 6, 7, 8]
+```
+
+**Output:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4, 5, 6, 7, 8]
+```
+
+#### 💡 说明:
+
+* `a += b` 并不总是与 `a = a + b` 表现相同. 类实现 *`op=`* 运算符的方式 *也许* 是不同的, 列表就是这样做的.
+
+* 表达式 `a = a + [5,6,7,8]` 会生成一个新列表, 并让 `a` 引用这个新列表, 同时保持 `b` 不变.
+
+* 表达式 `a += [5,6,7,8]` 实际上是使用的是 "extend" 函数, 所以 `a` 和 `b` 仍然指向已被修改的同一列表.
+
+
+---
+
+### > Name resolution ignoring class scope/忽略类作用域的名称解析
+
+1\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = (x for i in range(10))
+```
+
+**Output:**
+```py
+>>> list(SomeClass.y)[0]
+5
+```
+
+2\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = [x for i in range(10)]
+```
+
+**Output (Python 2.x):**
+```py
+>>> SomeClass.y[0]
+17
+```
+
+**Output (Python 3.x):**
+```py
+>>> SomeClass.y[0]
+5
+```
+
+#### 💡 说明:
+- 类定义中嵌套的作用域会忽略类内的名称绑定.
+- 生成器表达式有它自己的作用域.
+- 从 Python 3.X 开始, 列表推导式也有自己的作用域.
+
+
+---
+
+### > Rounding like a banker/像银行家一样舍入 *
+
+让我们实现一个简单的函数来获取列表的中间元素:
+
+```py
+def get_middle(some_list):
+ mid_index = round(len(some_list) / 2)
+ return some_list[mid_index - 1]
+```
+
+**Python 3.x:**
+```py
+>>> get_middle([1]) # looks good
+1
+>>> get_middle([1,2,3]) # looks good
+2
+>>> get_middle([1,2,3,4,5]) # huh?
+2
+>>> len([1,2,3,4,5]) / 2 # good
+2.5
+>>> round(len([1,2,3,4,5]) / 2) # why?
+2
+```
+
+似乎 Python 将 2.5 舍入到 2。
+
+#### 💡 说明
+
+- - 这不是浮点精度错误,实际上,这种行为是故意的。从 Python 3.0 开始,`round()` 使用[银行进位法](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even),其中 0.5 小数四舍五入到最接近的 **偶数** :
+
+```py
+>>> round(0.5)
+0
+>>> round(1.5)
+2
+>>> round(2.5)
+2
+>>> import numpy # numpy的结果也是一样
+>>> numpy.round(0.5)
+0.0
+>>> numpy.round(1.5)
+2.0
+>>> numpy.round(2.5)
+2.0
+```
+
+- 这是 [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules) 中描述的关于0.5分位舍入的推荐方法。然而,另一种方法(从零取整)大部分时间都是在学校教授的,所以银行进位法可能并不为人所知。此外,一些最流行的编程语言(例如:JavaScript、Java、C/C++、Ruby、Rust)也不使用银行进位法。因此,这对 Python 来说还是比较特殊的,在四舍五入时可能会导致混淆。
+- 了解更多信息,请参阅文档 [round()](https://docs.python.org/3/library/functions.html#round) 或 [this stackoverflow thread](https://stackoverflow.com/questions/10825926/python -3-x-rounding-behavior)
+- 请注意,`get_middle([1])` 只返回1,因为它的索引是 `round(0.5) - 1 = 0 - 1 = -1`,返回列表中的最后一个元素。
+
+
+---
+
+### > Needles in a Haystack/大海捞针
+
+
+迄今为止,每一位Python开发者都会遇到类似以下的情况。
+
+1\.
+```py
+x, y = (0, 1) if True else None, None
+```
+
+**Output:**
+```
+>>> x, y # 期望的结果是 (0, 1)
+((0, 1), None)
+```
+
+2\.
+```py
+t = ('one', 'two')
+for i in t:
+ print(i)
+
+t = ('one')
+for i in t:
+ print(i)
+
+t = ()
+print(t)
+```
+
+**Output:**
+
+```py
+one
+two
+o
+n
+e
+tuple()
+```
+
+3\.
+
+```
+ten_words_list = [
+ "some",
+ "very",
+ "big",
+ "list",
+ "that"
+ "consists",
+ "of",
+ "exactly",
+ "ten",
+ "words"
+]
+```
+
+**Output**
+
+```py
+>>> len(ten_words_list)
+9
+```
+
+4\. 不够健壮的断言机制
+
+```py
+a = "python"
+b = "javascript"
+```
+
+**Output:**
+
+```py
+# 带有失败警告信息的assert表达式
+>>> assert(a == b, "Both languages are different")
+# 未引发 AssertionError
+```
+
+5\.
+
+```py
+some_list = [1, 2, 3]
+some_dict = {
+ "key_1": 1,
+ "key_2": 2,
+ "key_3": 3
+}
+
+some_list = some_list.append(4)
+some_dict = some_dict.update({"key_4": 4})
+```
+
+**Output:**
+
+```py
+>>> print(some_list)
+None
+>>> print(some_dict)
+None
+```
+
+6\.
+
+```py
+def some_recursive_func(a):
+ if a[0] == 0:
+ return
+ a[0] -= 1
+ some_recursive_func(a)
+ return a
+
+def similar_recursive_func(a):
+ if a == 0:
+ return a
+ a -= 1
+ similar_recursive_func(a)
+ return a
+```
+
+**Output:**
+
+```py
+>>> some_recursive_func([5, 0])
+[0, 0]
+>>> similar_recursive_func(5)
+4
+```
+
+#### 💡 说明:
+* 对于 1, 正确的语句是 `x, y = (0, 1) if True else (None, None)`.
+* 对于 2, 正确的语句是 `t = ('one',)` 或者 `t = 'one',` (缺少逗号) 否则解释器会认为 `t` 是一个字符串, 并逐个字符对其进行迭代.
+* `()` 是一个特殊的标记,表示空元组.
+* 对于 3,正如您可能已经弄清楚的那样,列表中的第5个元素("that")后面缺少一个逗号。因此,通过隐式字符串字面连接,
+
+ ```py
+ >>> ten_words_list
+ ['some', 'very', 'big', 'list', 'thatconsists', 'of', 'exactly', 'ten', 'words']
+ ```
+
+* 在第4个代码段中没有引发"AssertionError",因为我们不是断言单个表达式 `a == b`,而是断言整个元组。以下代码段将说明问题,
+
+ ```py
+ >>> a = "python"
+ >>> b = "javascript"
+ >>> assert a == b
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError
+
+ >>> assert (a == b, "Values are not equal")
+ :1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
+
+ >>> assert a == b, "Values are not equal"
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError: Values are not equal
+ ```
+* 至于第五个片段,大多数修改序列/映射对象项的方法,如`list.append`、`dict.update`、`list.sort`等,都在原地修改对象并返回`None`。这背后的基本原理是通过原地操作,避免复制对象来提高性能(参考[这里](https://docs.python.org/3/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list))。
+* 最后一个应该相当明显,可变对象(如`list`)可以在函数中更改,不可变对象(`a -= 1`)的重新赋值则不属于值的改变。
+* 了解这些细节可以在程序长期运行中,为您节省数小时的调试工作。
+
+
+---
+
+### > Splitsies/分割函数 *
+
+
+```py
+>>> 'a'.split()
+['a']
+
+# is same as
+>>> 'a'.split(' ')
+['a']
+
+# but
+>>> len(''.split())
+0
+
+# isn't the same as
+>>> len(''.split(' '))
+1
+```
+
+#### 💡 说明
+
+- 起初人们可能会认为 split 的默认分隔符是单个空格 `' '`,但根据 [文档](https://docs.python.org/3/library/stdtypes.html#str.split):
+ > 如果 sep 未指定或为 `None`,则应用不同的拆分算法:连续的空格被视为单个分隔符,如果字符串有前导或尾随空格,则结果将在开头或结尾不包含空字符串。因此,使用 `None` 分隔符拆分空字符串或仅包含空格的字符串将返回 `[]`。
+ > 如果给定 sep,连续的分隔符不会组合在一起,并被视为分隔空字符串(例如,`'1,,2'.split(',')` 返回 `['1', '', '2 ']`)。使用指定的分隔符拆分空字符串会返回 `['']`。
+- Noticing how the leading and trailing whitespaces are handled in the following snippet will make things clear,
+- 注意以下代码段中如何处理前导和尾随空格,促进更深入的理解:
+
+ ```py
+ >>> ' a '.split(' ')
+ ['', 'a', '']
+ >>> ' a '.split()
+ ['a']
+ >>> ''.split(' ')
+ ['']
+ ```
+
+
+---
+
+### > Wild imports/通配符导入方式 *
+
+
+
+```py
+# File: module.py
+
+def some_weird_name_func_():
+ print("works!")
+
+def _another_weird_name_func():
+ print("works!")
+
+```
+
+**Output**
+
+```py
+>>> from module import *
+>>> some_weird_name_func_()
+"works!"
+>>> _another_weird_name_func()
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name '_another_weird_name_func' is not defined
+```
+
+#### 💡 说明
+
+- 通常建议不要使用通配符导入。第一个明显的原因是,在通配符导入中,带有前导下划线的名称不会被导入。这可能会导致运行时出错。
+- 如果我们使用 `from ... import a, b, c` 语法,上面的 `NameError` 就不会发生。
+
+ ```py
+ >>> from module import some_weird_name_func_, _another_weird_name_func
+ >>> _another_weird_name_func()
+ works!
+ ```
+
+- 如果你真的想使用通配符导入,那么你必须在你的模块中定义列表`__all__`,它包含一系列公共对象,当我们进行通配符导入时,列表中的这些对象将被导入。
+
+ ```py
+ __all__ = ['_another_weird_name_func']
+
+ def some_weird_name_func_():
+ print("works!")
+
+ def _another_weird_name_func():
+ print("works!")
+ ```
+ **Output**
+
+ ```py
+ >>> _another_weird_name_func()
+ "works!"
+ >>> some_weird_name_func_()
+ Traceback (most recent call last):
+ File "", line 1, in
+ NameError: name 'some_weird_name_func_' is not defined
+ ```
+
+
+---
+
+### > All sorted?/都排序了吗? *
+
+
+
+```py
+>>> x = 7, 8, 9
+>>> sorted(x) == x
+False
+>>> sorted(x) == sorted(x)
+True
+
+>>> y = reversed(x)
+>>> sorted(y) == sorted(y)
+False
+```
+
+#### 💡 说明
+
+- `sorted` 方法一定返回列表类型, 比较列表与元组在Python中一定返回 `False`.
+
+- ```py
+ >>> [] == tuple()
+ False
+ >>> x = 7, 8, 9
+ >>> type(x), type(sorted(x))
+ (tuple, list)
+ ```
+
+- 与 `sorted` 不同,`reversed` 方法返回一个迭代器。为什么?因为排序需要就地修改迭代器或使用额外的容器(列表),而反向可以简单地通过从最后一个索引迭代到第一个索引来工作。
+
+- 所以在比较 `sorted(y) == sorted(y)` 时,第一次调用 `sorted()` 会消耗迭代器 `y`,下一次调用只会返回一个空列表。
+
+ ```py
+ >>> x = 7, 8, 9
+ >>> y = reversed(x)
+ >>> sorted(y), sorted(y)
+ ([7, 8, 9], [])
+ ```
+
+
+---
+
+### > Midnight time doesn't exist?/不存在的午夜?
+
+```py
+from datetime import datetime
+
+midnight = datetime(2018, 1, 1, 0, 0)
+midnight_time = midnight.time()
+
+noon = datetime(2018, 1, 1, 12, 0)
+noon_time = noon.time()
+
+if midnight_time:
+ print("Time at midnight is", midnight_time)
+
+if noon_time:
+ print("Time at noon is", noon_time)
+```
+
+**Output:**
+```sh
+('Time at noon is', datetime.time(12, 0))
+```
+
+midnight_time 并没有被输出.
+
+#### 💡 说明:
+
+在Python 3.5之前, 如果 `datetime.time` 对象存储的UTC的午夜时间(译: 就是 `00:00`), 那么它的布尔值会被认为是 `False`. 当使用 `if obj:` 语句来检查 `obj` 是否为 `null` 或者某些“空”值的时候, 很容易出错.
+
+
+---
+
+## Section: The Hidden treasures!/隐藏的宝藏!
+
+本节包含了一些像我这样的大多数初学者都不知道的关于Python的鲜为人知的有趣的事情(好吧,现在不是了)。
+
+### > Okay Python, Can you make me fly?/Python, 可否带我飞? *
+
+好, 去吧.
+
+```py
+import antigravity
+```
+
+**Output:**
+嘘.. 这是个超级秘密.
+
+#### 💡 说明:
++ `antigravity` 模块是 Python 开发人员发布的少数复活节彩蛋之一.
++ `import antigravity` 会打开一个 Python 的[经典 XKCD 漫画](http://xkcd.com/353/)页面.
++ 不止如此. 这个**复活节彩蛋里还有一个复活节彩蛋**. 如果你看一下[代码](https://github.com/python/cpython/blob/master/Lib/antigravity.py#L7-L17), 就会发现还有一个函数实现了 [XKCD's geohashing 算法](https://xkcd.com/426/).
+
+---
+
+### > `goto`, but why?/`goto`, 但为什么? *
+
+```py
+from goto import goto, label
+for i in range(9):
+ for j in range(9):
+ for k in range(9):
+ print("I'm trapped, please rescue!")
+ if k == 2:
+ goto .breakout # 从多重循环中跳出
+label .breakout
+print("Freedom!")
+```
+
+**Output (Python 2.3):**
+```py
+I'm trapped, please rescue!
+I'm trapped, please rescue!
+Freedom!
+```
+
+#### 💡 说明:
+- 2004年4月1日, Python [宣布](https://mail.python.org/pipermail/python-announce-list/2004-April/002982.html) 加入一个可用的 `goto` 作为愚人节礼物.
+- 当前版本的 Python 并没有这个模块.
+- 就算可以用, 也请不要使用它. 这里是为什么Python中没有 `goto` 的[原因](https://docs.python.org/3/faq/design.html#why-is-there-no-goto).
+
+---
+
+### > Brace yourself!/做好思想准备 *
+
+如果你不喜欢在Python中使用空格来表示作用域, 你可以导入 C 风格的 {},
+
+```py
+from __future__ import braces
+```
+
+**Output:**
+```py
+ File "some_file.py", line 1
+ from __future__ import braces
+SyntaxError: not a chance
+```
+
+想用大括号 `braces`? 没门! 觉得不爽, 请去用java。那么,另一个令人惊讶的事情,找一找在 `__future__` 模块中,哪里引发了 `SyntaxError` [code](https://github.com/python/cpython/blob/master/Lib/__future__.py)?
+
+#### 💡 说明:
++ 通常 `__future__` 会提供 Python 未来版本的功能. 然而,这里的 “未来” 是一个讽刺.
++ 这是一个表达社区对此类问题态度的复活节彩蛋.
++ 代码实际上在[`future.c` 文件]中 (https://github.com/python/cpython/blob/025eb98dc0c1dc27404df6c544fc2944e0fa9f3a/Python/future.c#L49).
++ 当 CPython 编译器遇到 [future表达式](https://docs.python.org/3.3/reference/simple_stmts.html#future-statements) 时,它首先在 `future.c` 中运行相应的代码,然后再对其进行处理作为正常的`import`表达式。
+
+
+---
+
+### > Let's meet Friendly Language Uncle For Life/让生活更友好 *
+
+
+**Output (Python 3.x)**
+```py
+>>> from __future__ import barry_as_FLUFL
+>>> "Ruby" != "Python" # 这里没什么疑问
+ File "some_file.py", line 1
+ "Ruby" != "Python"
+ ^
+SyntaxError: invalid syntax
+
+>>> "Ruby" <> "Python"
+True
+```
+
+这就对了.
+
+#### 💡 说明:
+- 相关的 [PEP-401](https://www.python.org/dev/peps/pep-0401/) 发布于 2009年4月1日 (所以你现在知道这意味着什么了吧).
+- 引用 PEP-401
+ > 意识到 Python 3.0 里的 != 运算符是一个会引起手指疼痛的恐怖错误, FLUFL 将 <> 运算符恢复为唯一写法.
+- Uncle Barry 在 PEP 中还分享了其他东西; 你可以在[这里](https://www.python.org/dev/peps/pep-0401/)获得他们.
+- (译: 虽然文档中没写,但应该是只能在交互解释器中使用.)
+- 它在交互式环境中正常运行,但是当您通过 python 文件运行时它会引发 `SyntaxError`(请参阅此 [问题](https://github.com/satwikkansal/wtfpython/issues/94))。您可以将表达式作为`eval` 或 `compile` 参数中使用。
+
+ ```py
+ from __future__ import barry_as_FLUFL
+ print(eval('"Ruby" <> "Python"'))
+ ```
+
+
+---
+
+### > Even Python understands that love is complicated/连Python也知道爱是难言的 *
+
+```py
+import this
+```
+
+等等, **this** 是什么? `this` 是爱 :heart:
+
+**Output:**
+```
+The Zen of Python, by Tim Peters
+
+Beautiful is better than ugly.
+优美胜于丑陋(Python 以编写优美的代码为目标)
+Explicit is better than implicit.
+明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)
+Simple is better than complex.
+简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)
+Complex is better than complicated.
+复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)
+Flat is better than nested.
+扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)
+Sparse is better than dense.
+间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)
+Readability counts.
+可读性很重要(优美的代码一定是可读的)
+Special cases aren't special enough to break the rules.
+没有特例特殊到需要违背这些规则(这些规则至高无上)
+Although practicality beats purity.
+尽管我们更倾向于实用性
+Errors should never pass silently.
+不要安静的包容所有错误
+Unless explicitly silenced.
+除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)
+In the face of ambiguity, refuse the temptation to guess.
+拒绝诱惑你去猜测的暧昧事物
+There should be one-- and preferably only one --obvious way to do it.
+而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)
+Although that way may not be obvious at first unless you're Dutch.
+虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )
+Now is better than never.
+现在行动好过永远不行动
+Although never is often better than *right* now.
+尽管不行动要好过鲁莽行动
+If the implementation is hard to explain, it's a bad idea.
+如果你无法向人描述你的方案,那肯定不是一个好方案;
+If the implementation is easy to explain, it may be a good idea.
+如果你能轻松向人描述你的方案,那也许会是一个好方案(方案测评标准)
+Namespaces are one honking great idea -- let's do more of those!
+命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)
+```
+
+这是 Python 之禅!
+
+```py
+>>> love = this
+>>> this is love
+True
+>>> love is True
+False
+>>> love is False
+False
+>>> love is not True or False
+True
+>>> love is not True or False; love is love # 爱是难言的
+True
+```
+
+#### 💡 说明:
+
+* `this` 模块是关于 Python 之禅的复活节彩蛋 ([PEP 20](https://www.python.org/dev/peps/pep-0020)).
+* 如果你认为这已经够有趣的了, 可以看看 [this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py) 的实现. 有趣的是, Python 之禅的实现代码违反了他自己 (这可能是唯一会发生这种情况的地方).
+*
+至于 `love is not True or False; love is love`, 意外却又不言而喻.
+
+---
+
+### > Yes, it exists!/是的, 它存在!
+
+**循环的 `else`.** 一个典型的例子:
+
+```py
+ def does_exists_num(l, to_find):
+ for num in l:
+ if num == to_find:
+ print("Exists!")
+ break
+ else:
+ print("Does not exist")
+```
+
+**Output:**
+```py
+>>> some_list = [1, 2, 3, 4, 5]
+>>> does_exists_num(some_list, 4)
+Exists!
+>>> does_exists_num(some_list, -1)
+Does not exist
+```
+
+**异常的 `else` .** 例,
+
+```py
+try:
+ pass
+except:
+ print("Exception occurred!!!")
+else:
+ print("Try block executed successfully...")
+```
+
+**Output:**
+```py
+Try block executed successfully...
+```
+
+#### 💡 说明:
+- 循环后的 `else` 子句只会在循环没有触发 `break` 语句, 正常结束的情况下才会执行.
+- try 之后的 `else` 子句也被称为 "完成子句", 因为在 `try` 语句中到达 `else` 子句意味着try块实际上已成功完成.
+
+
+---
+
+### > Ellipsis/省略 *
+
+
+```py
+def some_func():
+ Ellipsis
+```
+
+**Output**
+```py
+>>> some_func()
+# 没有输出,也没有报错
+
+>>> SomeRandomString
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'SomeRandomString' is not defined
+
+>>> Ellipsis
+Ellipsis
+```
+
+#### 💡 说明
+- 在 Python 中,`Ellipsis` 是一个全局可用的内置对象,相当于`...`。
+
+ ```py
+ >>> ...
+ Ellipsis
+ ```
+
+- 省略号可用于多种用途,
+ + 作为尚未编写的代码的占位符(就像`pass`语句)
+ + 在切片语法中表示完整切片的其余维度
+
+ ```py
+ >>> import numpy as np
+ >>> three_dimensional_array = np.arange(8).reshape(2, 2, 2)
+ array([
+ [
+ [0, 1],
+ [2, 3]
+ ],
+
+ [
+ [4, 5],
+ [6, 7]
+ ]
+ ])
+ ```
+
+ 所以我们的 `three_dimensional_array` 是一个数组的数组的数组。假设我们要打印所有最内层数组的第二个元素(索引 `1`),我们可以使用 Ellipsis 绕过所有前面的维度
+
+ ```py
+ >>> three_dimensional_array[:,:,1]
+ array([[1, 3],
+ [5, 7]])
+ >>> three_dimensional_array[..., 1] # 使用Ellipsis.
+ array([[1, 3],
+ [5, 7]])
+ ```
+
+ 注意:这适用于任意数量的维度。您甚至可以在第一个和最后一个维度中选择切片并以这种方式忽略中间的切片(`n_dimensional_array[firs_dim_slice, ..., last_dim_slice]`)
+
+ + 在 [类型提示](https://docs.python.org/3/library/typing.html)中仅表示类型的一部分(如 `(Callable[..., int]` 或 `Tuple[ str, ...]`))
+
+ + 您也可以使用省略号作为默认函数参数(在您想要区分“无参数”和“传递None值”场景的情况下)。
+
+
+---
+
+### > Inpinity/无限 *
+
+英文拼写是有意的, 请不要为此提交补丁.
+(译: 这里是为了突出 Python 中无限的定义与[Pi](https://en.wikipedia.org/wiki/Pi)有关, 所以将两个单词拼接了.)
+
+**Output (Python 3.x):**
+```py
+>>> infinity = float('infinity')
+>>> hash(infinity)
+314159
+>>> hash(float('-inf'))
+-314159
+```
+
+#### 💡 说明:
+- infinity 的哈希值是 10⁵ x π.
+- 有意思的是, `float('-inf')` 的哈希值在 Python 3 中是 "-10⁵ x π" , 而在 Python 2 中是 "-10⁵ x e".
+
+---
+
+### > Let's mangle/修饰时间! *
+
+
+1\.
+```py
+class Yo(object):
+ def __init__(self):
+ self.__honey = True
+ self.bro = True
+```
+
+**Output:**
+```py
+>>> Yo().bro
+True
+>>> Yo().__honey
+AttributeError: 'Yo' object has no attribute '__honey'
+>>> Yo()._Yo__honey
+True
+```
+
+2\.
+```py
+class Yo(object):
+ def __init__(self):
+ # 这次试试对称形式
+ self.__honey__ = True
+ self.bro = True
+```
+
+**Output:**
+```py
+>>> Yo().bro
+True
+
+>>> Yo()._Yo__honey__
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'Yo' object has no attribute '_Yo__honey__'
+```
+
+为什么 `Yo()._Yo__honey` 能运行? 只有印度人理解.(译: 这个梗可能是指印度音乐人[Yo Yo Honey Singh](https://en.wikipedia.org/wiki/Yo_Yo_Honey_Singh))
+
+
+3\.
+
+```py
+_A__variable = "Some value"
+
+class A(object):
+ def some_func(self):
+ return __variable # 没在任何地方初始化
+```
+
+**Output:**
+```py
+>>> A().__variable
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'A' object has no attribute '__variable'
+
+>>> A().some_func()
+'Some value'
+```
+
+
+#### 💡 说明:
+
+* [命名修饰](https://en.wikipedia.org/wiki/Name_mangling) 用于避免不同命名空间之间名称冲突.
+* 在 Python 中, 解释器会通过给类中以 `__` (双下划线)开头且结尾最多只有一个下划线的类成员名称加上`_NameOfTheClass` 来修饰(mangles)名称.
+* 所以, 要访问 `__honey` 对象,我们需要加上 `_Yo` 以防止与其他类中定义的相同名称的属性发生冲突.
+* 但是为什么它在第二个片段中不起作用? 因为命名修饰排除了以双下划线结尾的名称。
+* 第三个片段也是命名修饰的结果。 `return __variable` 语句中的 `__variable` 名称被修改为 `_A__variable`,这也恰好是我们在外部作用域中声明的变量的名称。
+* 此外,如果修饰后的变量名超过255个字符,则会进行截断。
+
+
+---
+
+## Section: Appearances are deceptive!/外表是靠不住的!
+
+### > Skipping lines?/跳过一行?
+
+**Output:**
+```py
+>>> value = 11
+>>> valuе = 32
+>>> value
+11
+```
+
+什么鬼?
+
+**注意:** 如果你想要重现的话最简单的方法是直接复制上面的代码片段到你的文件或命令行里.
+
+#### 💡 说明:
+
+一些非西方字符虽然看起来和英语字母相同, 但会被解释器识别为不同的字母.
+
+```py
+>>> ord('е') # 西里尔语的 'e' (Ye)
+1077
+>>> ord('e') # 拉丁语的 'e', 用于英文并使用标准键盘输入
+101
+>>> 'е' == 'e'
+False
+
+>>> value = 42 # 拉丁语 e
+>>> valuе = 23 # 西里尔语 'e', Python 2.x 的解释器在这会抛出 `SyntaxError` 异常
+>>> value
+42
+```
+
+内置的 `ord()` 函数可以返回一个字符的 Unicode [代码点](https://en.wikipedia.org/wiki/Code_point), 这里西里尔语 'e' 和拉丁语 'e' 的代码点不同证实了上述例子.
+
+---
+
+### > Teleportation/空间移动 *
+
+```py
+import numpy as np
+
+def energy_send(x):
+ # 初始化一个 numpy 数组
+ np.array([float(x)])
+
+def energy_receive():
+ # 返回一个空的 numpy 数组
+ return np.empty((), dtype=np.float).tolist()
+```
+
+**Output:**
+```py
+>>> energy_send(123.456)
+>>> energy_receive()
+123.456
+```
+
+谁来给我发个诺贝尔奖?
+
+#### 💡 说明:
+
+* 注意在 `energy_send` 函数中创建的 numpy 数组并没有返回, 因此内存空间被释放并可以被重新分配.
+* `numpy.empty()` 直接返回下一段空闲内存,而不重新初始化. 而这个内存点恰好就是刚刚释放的那个(通常情况下, 并不绝对).
+
+---
+
+### > Well, something is fishy.../嗯,有些可疑...
+
+```py
+def square(x):
+ """
+ 一个通过加法计算平方的简单函数.
+ """
+ sum_so_far = 0
+ for counter in range(x):
+ sum_so_far = sum_so_far + x
+ return sum_so_far
+```
+
+**Output (Python 2.x):**
+
+```py
+>>> square(10)
+10
+```
+
+难道不应该是100吗?
+
+**注意:** 如果你无法重现, 可以尝试运行这个文件[mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py).
+
+#### 💡 说明:
+
+* **不要混用制表符(tab)和空格(space)!** 在上面的例子中, return 的前面是"1个制表符", 而其他部分的代码前面是 "4个空格".
+* Python是这么处理制表符的:
+ > 首先, 制表符会从左到右依次被替换成8个空格, 直到被替换后的字符总数是八的倍数 <...>
+* 因此, `square` 函数最后一行的制表符会被替换成8个空格, 导致return语句进入循环语句里面.
+* Python 3 很友好, 在这种情况下会自动抛出错误.
+
+ **Output (Python 3.x):**
+ ```py
+ TabError: inconsistent use of tabs and spaces in indentation
+ ```
+
+
+---
+
+## Section: Miscellaneous/杂项
+
+
+### > `+=` is faster/更快的 `+=`
+
+```py
+# 用 "+" 连接三个字符串:
+>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.25748300552368164
+# 用 "+=" 连接三个字符串:
+>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.012188911437988281
+```
+
+#### 💡 说明:
++ 连接两个以上的字符串时 `+=` 比 `+` 更快, 因为在计算过程中第一个字符串 (例如, `s1 += s2 + s3` 中的 `s1`) 不会被销毁.(译: 就是 `+=` 执行的是追加操作,少了一个销毁新建的动作.)
+
+---
+
+### > Let's make a giant string!/来做个巨大的字符串吧!
+
+```py
+def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s += "xyz"
+ assert len(s) == 3*iters
+
+def add_bytes_with_plus(iters):
+ s = b""
+ for i in range(iters):
+ s += b"xyz"
+ assert len(s) == 3*iters
+
+def add_string_with_format(iters):
+ fs = "{}"*iters
+ s = fs.format(*(["xyz"]*iters))
+ assert len(s) == 3*iters
+
+def add_string_with_join(iters):
+ l = []
+ for i in range(iters):
+ l.append("xyz")
+ s = "".join(l)
+ assert len(s) == 3*iters
+
+def convert_list_to_string(l, iters):
+ s = "".join(l)
+ assert len(s) == 3*iters
+```
+
+**Output:**
+```py
+>>> timeit(add_string_with_plus(10000))
+1000 loops, best of 3: 972 µs per loop
+>>> timeit(add_bytes_with_plus(10000))
+1000 loops, best of 3: 815 µs per loop
+>>> timeit(add_string_with_format(10000))
+1000 loops, best of 3: 508 µs per loop
+>>> timeit(add_string_with_join(10000))
+1000 loops, best of 3: 878 µs per loop
+>>> l = ["xyz"]*10000
+>>> timeit(convert_list_to_string(l, 10000))
+10000 loops, best of 3: 80 µs per loop
+```
+
+让我们将迭代次数增加10倍.
+
+```py
+>>> timeit(add_string_with_plus(100000)) # 执行时间线性增加
+100 loops, best of 3: 9.75 ms per loop
+>>> timeit(add_bytes_with_plus(100000)) # 二次增加
+1000 loops, best of 3: 974 ms per loop
+>>> timeit(add_string_with_format(100000)) # 线性增加
+100 loops, best of 3: 5.25 ms per loop
+>>> timeit(add_string_with_join(100000)) # 线性增加
+100 loops, best of 3: 9.85 ms per loop
+>>> l = ["xyz"]*100000
+>>> timeit(convert_list_to_string(l, 100000)) # 线性增加
+1000 loops, best of 3: 723 µs per loop
+```
+
+#### 💡 说明:
+- 你可以在这获得更多 [timeit](https://docs.python.org/3/library/timeit.html) 的相关信息. 它通常用于衡量代码片段的执行时间.
+- 不要用 `+` 去生成过长的字符串, 在 Python 中, `str` 是不可变的, 所以在每次连接中你都要把左右两个字符串复制到新的字符串中. 如果你连接四个长度为10的字符串, 你需要拷贝 (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90 个字符而不是 40 个字符. 随着字符串的数量和大小的增加, 情况会变得越发的糟糕 (就像`add_bytes_with_plus` 函数的执行时间一样)
+- 因此, 更建议使用 `.format.` 或 `%` 语法 (但是, 对于短字符串, 它们比 `+` 稍慢一点).
+- 又或者, 如果你所需的内容已经以可迭代对象的形式提供了, 使用 `''.join(可迭代对象)` 要快多了.
+- `add_string_with_plus` 的执行时间没有像 `add_bytes_with_plus` 一样出现二次增加是因为解释器会如同上一个例子所讨论的一样优化 `+=`. 用 `s = s + "x" + "y" + "z"` 替代 `s += "xyz"` 的话, 执行时间就会二次增加了.
+ ```py
+ def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s = s + "x" + "y" + "z"
+ assert len(s) == 3*iters
+
+ >>> timeit(add_string_with_plus(10000))
+ 100 loops, best of 3: 9.87 ms per loop
+ >>> timeit(add_string_with_plus(100000)) # 执行时间二次增加
+ 1 loops, best of 3: 1.09 s per loop
+ ```
+
+
+---
+
+### > Slowing down `dict` lookups/让字典的查找慢下来 *
+
+
+```py
+some_dict = {str(i): 1 for i in range(1_000_000)}
+another_dict = {str(i): 1 for i in range(1_000_000)}
+```
+
+**Output:**
+```py
+>>> %timeit some_dict['5']
+28.6 ns ± 0.115 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> some_dict[1] = 1
+>>> %timeit some_dict['5']
+37.2 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+
+>>> %timeit another_dict['5']
+28.5 ns ± 0.142 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> another_dict[1] # Trying to access a key that doesn't exist
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 1
+>>> %timeit another_dict['5']
+38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+```
+
+为什么相同的查找会变得越来越慢?
+
+#### 💡 说明
++ CPython 有一个通用的字典查找函数,可以处理所有类型的键(`str`、`int`、任何对象...),以及一个专门用于处理仅由 `str` 键组成的字典的常见情况。
++ 专用函数(在 CPython 的 [源](https://github.com/python/cpython/blob/522691c46e2ae51faaad5bbbce7d959dd61770df/Objects/dictobject.c#L841) 中名为 `lookdict_unicode`)知道所有现有的键(包括查找的 key) 是字符串,并使用更快和更简单的字符串比较来比较键,而不是调用 `__eq__` 方法。
++ 第一次使用非 `str` 键访问 `dict` 实例时,会对其进行修改,以便将来的查找使用通用函数。
++ 这个过程对于特定的 `dict` 实例是不可逆的,并且键甚至不必存在于字典中。 这就是为什么对不存在的键进行查找具有相同副作用的原因。
+
+
+---
+
+### > Bloating instance `dict`s/变臃肿的`dict`实例们 *
+
+
+```py
+import sys
+
+class SomeClass:
+ def __init__(self):
+ self.some_attr1 = 1
+ self.some_attr2 = 2
+ self.some_attr3 = 3
+ self.some_attr4 = 4
+
+
+def dict_size(o):
+ return sys.getsizeof(o.__dict__)
+
+```
+
+**Output:** (Python 3.8, 其他 Python 3 的版本也许稍有不同)
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104
+>>> dict_size(o2)
+104
+>>> del o1.some_attr1
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+>>> dict_size(o1)
+232
+```
+
+让我们在一个新的解释器中再试一次:
+
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104 # 意料之中
+>>> o1.some_attr5 = 5
+>>> o1.some_attr6 = 6
+>>> dict_size(o1)
+360
+>>> dict_size(o2)
+272
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+```
+
+是什么让那些字典变得臃肿? 为什么新创建的对象也会变臃肿?
+
+#### 💡 说明
++ CPython 能够在多个字典中重用相同的“键”对象。 这添加在 [PEP 412](https://www.python.org/dev/peps/pep-0412/) 中,目的是减少内存使用,特别是在实例字典中 —— 键(实例属性)几乎在所有实例都通用。
++ 这种优化对于实例字典来说是十分自然的,但如果某些假设被打破,它就会被禁用。
++ 密钥共享字典不支持删除;如果删除了实例属性,则字典是“未共享的”,并且同一类的所有未来实例都禁用密钥共享。
++ 另外,如果字典键已被调整大小(因为插入了新键),它们保持共享*仅当*它们被一个完全单一的字典使用时(这允许在第一个创建的实例的 `__init__` 中添加许多属性,而不会导致“取消共享”)。如果发生调整大小时存在多个实例,则为同一类的所有未来实例禁用密钥共享:CPython 无法判断您的实例是否正在使用相同的属性集,并决定放弃尝试共享它们的键值。
++ 一个小提示,如果你的目标是降低程序的内存占用:不要删除实例属性,并确保在 `__init__` 中初始化所有的属性!
+
+
+---
+
+### > Minor Ones/小知识点
+
+* `join()` 是一个字符串操作而不是列表操作. (第一次接触会觉得有点违反直觉)
+
+ **💡 说明:**
+ 如果 `join()` 是字符串方法 那么它就可以处理任何可迭代的对象(列表,元组,迭代器). 如果它是列表方法, 则必须在每种类型中单独实现. 另外, 在 `list` 对象的通用API中实现一个专用于字符串的方法没有太大的意义.
+
+* 看着奇怪但能正确运行的语句:
+ + `[] = ()` 语句在语义上是正确的 (解包一个空的 `tuple` 并赋值给 `list`)
+ + `'a'[0][0][0][0][0]` 在语义上也是正确的, 因为 Python 不像C语言及其派生语言那样,具有字符数据类型。因此,从字符串中选择单个字符将返回单个字符串。
+ + `3 --0-- 5 == 8` 和 `--5 == 5` 在语义上都是正确的, 且结果等于 `True`.(译: 3减负0等于3,再减负5相当于加5等于8;负的负5等于5.)
+
+* 鉴于 `a` 是一个数字, `++a` 和 `--a` 都是有效的 Python 语句, 但其效果与 C, C++ 或 Java 等不一样.
+ ```py
+ >>> a = 5
+ >>> a
+ 5
+ >>> ++a
+ 5
+ >>> --a
+ 5
+ ```
+
+ **💡 说明:**
+ + python 里没有 `++` 操作符. 这其实是两个 `+` 操作符.
+ + `++a` 被解析为 `+(+a)` 最后等于 `a`. `--a` 同理.
+ + 这个 StackOverflow [回答](https://stackoverflow.com/questions/3654830/why-are-there-no-and-operators-in-python) 讨论了为什么 Python 中缺少增量和减量运算符.
+
+* Python 使用 2个字节存储函数中的本地变量. 理论上, 这意味着函数中只能定义65536个变量. 但是,Python 内置了一个方便的解决方案,可用于存储超过2^16个变量名. 下面的代码演示了当定义了超过65536个局部变量时堆栈中发生的情况 (警告: 这段代码会打印大约2^18行文本, 请做好准备!):
+ ```py
+ import dis
+ exec("""
+ def f():
+ """ + """
+ """.join(["X"+str(x)+"=" + str(x) for x in range(65539)]))
+
+ f()
+
+ print(dis.dis(f))
+ ```
+
+* 你的 *Python 代码* 并不会多线程同时运行 (是的, 你没听错!). 虽然你觉得会产生多个线程并让它们同时执行你的代码, 但是, 由于 [全局解释锁](https://wiki.python.org/moin/GlobalInterpreterLock)的存在, 你所做的只是让你的线程依次在同一个核心上执行. Python 多线程适用于IO密集型的任务, 但如果想要并行处理CPU密集型的任务, 你应该会想使用 [multiprocessing](https://docs.python.org/2/library/multiprocessing.html) 模块.
+
+* 列表切片超出索引边界而不引发任何错误
+ ```py
+ >>> some_list = [1, 2, 3, 4, 5]
+ >>> some_list[111:]
+ []
+ ```
+
+* `int('١٢٣٤٥٦٧٨٩')` 在 Python 3 中会返回 `123456789`. 在 Python 中, 十进制字符包括数字字符, 以及可用于形成十进制数字的所有字符, 例如: U+0660, ARABIC-INDIC DIGIT ZERO. 这有一个关于此的 [有趣故事](http://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/).
+
+* `'abc'.count('') == 4`. 这有一个 `count` 方法的相近实现, 能更好的说明问题
+ ```py
+ def count(s, sub):
+ result = 0
+ for i in range(len(s) + 1 - len(sub)):
+ result += (s[i:i + len(sub)] == sub)
+ return result
+ ```
+ 这个行为是由于空子串(`''`)与原始字符串中长度为0的切片相匹配导致的.
+
+---
+
+# Contributing/贡献
+
+欢迎各种补丁! 详情请看[CONTRIBUTING.md](https://github.com/satwikkansal/wtfpython/blob/master/CONTRIBUTING.md).(译: 这是给原库提贡献的要求模版)
+
+你可以通过新建 [issue](https://github.com/satwikkansal/wtfpython/issues/new) 或者在上 [Gitter](https://gitter.im/wtfpython/Lobby) 与我们进行讨论.
+
+(译: 如果你想对这个翻译项目提供帮助, 请看[这里](https://github.com/leisurelicht/wtfpython-cn/blob/master/CONTRIBUTING.md))
+
+# Acknowledgements/致谢
+
+这个系列最初的想法和设计灵感来自于 Denys Dovhan 的项目 [wtfjs](https://github.com/denysdovhan/wtfjs). 社区的强大支持让它成长为现在的模样.
+
+#### Some nice Links!/一些不错的资源
+* https://www.youtube.com/watch?v=sH4XF6pKKmk
+* https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python
+* https://sopython.com/wiki/Common_Gotchas_In_Python
+* https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines
+* https://stackoverflow.com/questions/1011431/common-pitfalls-in-python
+* https://www.python.org/doc/humor/
+* https://www.codementor.io/satwikkansal/python-practices-for-efficient-code-performance-memory-and-usability-aze6oiq65
+
+# 🎓 License/许可
+
+[![CC 4.0][license-image]][license-url]
+
+© [Satwik Kansal](https://satwikkansal.xyz)
+
+[license-url]: http://www.wtfpl.net
+[license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square
+
+## Help/帮助
+
+如果您有任何想法或建议,欢迎分享.
+
+## Surprise your geeky pythonist friends?/想给你的极客朋友一个惊喜?
+
+您可以使用这些快链向 Twitter 和 Linkedin 上的朋友推荐 wtfpython,
+
+[Twitter](https://twitter.com/intent/tweet?url=https://github.com/satwikkansal/wtfpython&hastags=python,wtfpython)
+ | [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.)
+
+## Need a pdf version?/需要来一份pdf版的?
+
+你可以快速在[这](https://form.jotform.com/221593245656057)获得英文作者制作的版本.
+
+## Follow Commit/追踪Commit
+
+这是中文版 fork 时所处的原库 Commit, 当原库更新时我会跟随更新.
+
+[![Commit id][commit-image]][commit-url]
+
+[commit-url]: https://github.com/satwikkansal/wtfpython/commit/19d4b075152d93e5bc75c5d08279338a895cfa27
+[commit-image]: https://img.shields.io/badge/Commit-19d4b0-yellow.svg
+
diff --git a/docs/GE.md b/docs/GE.md
new file mode 100644
index 0000000..6afd309
--- /dev/null
+++ b/docs/GE.md
@@ -0,0 +1,3943 @@
+---
+hide:
+ - toc
+---
+
+
+What the f*ck Python! 😱
+Entdecke und verstehe Python durch überraschende Code-Schnipsel.
+
+Übersetzungen: [English](https://github.com/satwikkansal/wtfpython) | [Chinesisch 中文](https://github.com/robertparley/wtfpython-cn) | [Vietnamesisch Tiếng Việt](https://github.com/vuduclyunitn/wtfptyhon-vi) | [Spanisch Español](https://web.archive.org/web/20220511161045/https://github.com/JoseDeFreitas/wtfpython-es) | [Koreanisch 한국어](https://github.com/buttercrab/wtfpython-ko) | [Russisch Русский](https://github.com/frontdevops/wtfpython) | [Übersetzung hinzufügen](https://github.com/satwikkansal/wtfpython/issues/new?title=Add%20translation%20for%20[LANGUAGE]&body=Expected%20time%20to%20finish:%20[X]%20weeks.%20I%27ll%20start%20working%20on%20it%20from%20[Y].)
+
+Andere Modi: [Interaktive Website](https://wtfpython-interactive.vercel.app) | [Interaktives Notebook](https://colab.research.google.com/github/satwikkansal/wtfpython/blob/master/irrelevant/wtf.ipynb) | [CLI](https://pypi.python.org/pypi/wtfpython)
+
+Python, bekannt als gut designte High-Level und Interpreter-basierte Programmiersprache, stellt viele Features zur Verfügung, um dem Programmierer das Leben zu erleichtern. Allerdings kann es vorkommen, dass ein Python-Schnipsel ein unerwartetes Verhalten zeigt.
+
+Hier ist ein schönes Projekt, das versucht die Dinge aufzuzeigen, die bei einigen Code-Schnipseln unter der Haube passieren und darüber hinaus einige weniger bekannte Features von Python zu erklären.
+
+Während manche Beispiele nicht unbedingt beeindruckend erscheinen, zeigen sie dennoch interessante Details von Python, die dir womöglich noch nicht aufgefallen sind. Ich finde, dass es eine schöne Möglichkeit ist, die Interna einer Programmiersprache zu lernen und ich glaube das findest du auch !
+
+Wenn du ein erfahrener Python-Programmierer bist, kannst du dies als Herausforderung ansehen, um möglichst viel beim ersten Anlauf
+richtig zu machen. Du hast vielleicht manches schon erlebt, sodass ich möglicherweise alte Erinnerungen wecken kann! :sweat_smile:
+
+PS: Wenn du bereits mehrfach hier warst, kannst du dich [hier](https://github.com/satwikkansal/wtfpython/releases/) über neue Modifikationen informieren (die Beispiele, die mit einem Stern markiert sind, sind Teil des letzten Releases).
+
+Also, los gehts...
+
+# Inhaltsverzeichnis
+
+
+
+
+
+- [Inhaltsverzeichnis](#inhaltsverzeichnis)
+- [Sruktur der Beispiele](#sruktur-der-beispiele)
+- [Benutzung](#benutzung)
+- [👀 Beispiele](#-beispiele)
+ - [Kapitel: Fordere dein Gehirn heraus!](#kapitel-fordere-dein-gehirn-heraus)
+ - [▶ Das Wichtigste zuerst! \*](#-das-wichtigste-zuerst-)
+ - [💡 Erklärung](#-erklärung)
+ - [▶ Strings können manchmal schwierig sein](#-strings-können-manchmal-schwierig-sein)
+ - [💡 Erklärung:](#-erklärung-1)
+ - [▶ Vorsicht bei verketteten Operationen](#-vorsicht-bei-verketteten-operationen)
+ - [💡 Erklärung:](#-erklärung-2)
+ - [▶ Wie man den `is` Operator nicht nutzt](#-wie-man-den-is-operator-nicht-nutzt)
+ - [💡 Erklärung:](#-erklärung-3)
+ - [▶ Hash brownies](#-hash-brownies)
+ - [💡 Erklärung](#-erklärung-4)
+ - [▶ Tief im Inneren sind wir alle gleich](#-tief-im-inneren-sind-wir-alle-gleich)
+ - [💡 Erklärung:](#-erklärung-5)
+ - [Unterschied macht.](#unterschied-macht)
+ - [▶ Unordnung in der Ordnung \*](#-unordnung-in-der-ordnung-)
+ - [💡 Erklärung:](#-erklärung-6)
+ - [▶ Versuche es weiter... \*](#-versuche-es-weiter-)
+ - [💡 Erklärung:](#-erklärung-7)
+ - [▶ Wofür?](#-wofür)
+ - [💡 Erklärung:](#-erklärung-8)
+ - [▶ Diskrepanz in der Auswertungszeit](#-diskrepanz-in-der-auswertungszeit)
+ - [💡 Erklärung](#-erklärung-9)
+ - [▶ `is not ...` ist nicht `is (not ...)`](#-is-not--ist-nicht-is-not-)
+ - [💡 Erklärung](#-erklärung-10)
+ - [▶ Ein tic-tac-toe wo X im ersten Versuch gewinnt!](#-ein-tic-tac-toe-wo-x-im-ersten-versuch-gewinnt)
+ - [💡 Erklärung:](#-erklärung-11)
+ - [▶ Schrödingers Variable \*](#-schrödingers-variable-)
+ - [💡 Erklärung:](#-erklärung-12)
+ - [▶ Das Henne-Ei-Problem \*](#-das-henne-ei-problem-)
+ - [💡 Erklärung](#-erklärung-13)
+ - [▶ Beziehungen in Unterklassen](#-beziehungen-in-unterklassen)
+ - [💡 Erklärung:](#-erklärung-14)
+ - [▶ Methodengleichheit und -identität](#-methodengleichheit-und--identität)
+ - [💡 Erklärung](#-erklärung-15)
+ - [▶ All-true-ation \*](#-all-true-ation-)
+ - [💡 Erklärung:](#-erklärung-16)
+ - [💡 Erklärung:](#-erklärung-17)
+ - [▶ Strings und die Backslashes](#-strings-und-die-backslashes)
+ - [💡 Erklärung](#-erklärung-18)
+ - [▶ not knot!](#-not-knot)
+ - [💡 Erklärung:](#-erklärung-19)
+ - [▶ Halbe Zeichenketten in dreifachen Anführungszeichen](#-halbe-zeichenketten-in-dreifachen-anführungszeichen)
+ - [💡 Erklärung:](#-erklärung-20)
+ - [▶ Was ist falsch an booleans?](#-was-ist-falsch-an-booleans)
+ - [💡 Erklärung:](#-erklärung-21)
+ - [▶ Klassen- und Instanzattribute](#-klassen--und-instanzattribute)
+ - [💡 Erklärung:](#-erklärung-22)
+ - [▶ yielding None](#-yielding-none)
+ - [💡 Erklärung:](#-erklärung-23)
+ - [▶ Yielding from... return! \*](#-yielding-from-return-)
+ - [💡 Erklärung:](#-erklärung-24)
+ - [▶ Nan-Reflexivität \*](#-nan-reflexivität-)
+ - [💡 Erklärung:](#-erklärung-25)
+ - [▶ Verändern des Unveränderlichen!](#-verändern-des-unveränderlichen)
+ - [💡 Erklärung:](#-erklärung-26)
+ - [▶ Die verschwindende Variable aus dem äußeren Gültigkeitsbereich](#-die-verschwindende-variable-aus-dem-äußeren-gültigkeitsbereich)
+ - [💡 Erklärung:](#-erklärung-27)
+ - [▶ Die mysteriöse key type Umwandlung](#-die-mysteriöse-key-type-umwandlung)
+ - [💡 Erklärung:](#-erklärung-28)
+ - [▶ Lass uns sehen, ob du dies errätst?](#-lass-uns-sehen-ob-du-dies-errätst)
+ - [💡 Erklärung:](#-erklärung-29)
+ - [▶ Überschreitet den Grenzwert für die Umwandlung von Integer-Strings](#-überschreitet-den-grenzwert-für-die-umwandlung-von-integer-strings)
+ - [💡 Erklärung:](#-erklärung-30)
+ - [Kapitel: Slippery Slopes](#kapitel-slippery-slopes)
+ - [▶ Modifizieren eines Dictionarys während einer Iteration](#-modifizieren-eines-dictionarys-während-einer-iteration)
+ - [💡 Erklärung:](#-erklärung-31)
+ - [▶ Hartnäckige `del` Operation](#-hartnäckige-del-operation)
+ - [💡 Erklärung:](#-erklärung-32)
+ - [▶ Die Variable aus dem äußeren Geltungsbereich](#-die-variable-aus-dem-äußeren-geltungsbereich)
+ - [💡 Erklärung:](#-erklärung-33)
+ - [▶ Löschen eines Listenelements während einer Iteration](#-löschen-eines-listenelements-während-einer-iteration)
+ - [💡 Erklärung:](#-erklärung-34)
+ - [▶ Lossy Zips von Iteratoren \*](#-lossy-zips-von-iteratoren-)
+ - [💡 Erklärung:](#-erklärung-35)
+ - [▶ Schleifenvariablen, die auslaufen!](#-schleifenvariablen-die-auslaufen)
+ - [💡 Erklärung:](#-erklärung-36)
+ - [▶ Vorsicht vor standardmäßig veränderbaren Argumenten!](#-vorsicht-vor-standardmäßig-veränderbaren-argumenten)
+ - [💡 Erklärung:](#-erklärung-37)
+ - [▶ Fangen der Exceptions](#-fangen-der-exceptions)
+ - [💡 Erklärung](#-erklärung-38)
+ - [▶ Gleiche Operanden, unterschiedliche Story!](#-gleiche-operanden-unterschiedliche-story)
+ - [💡 Erklärung:](#-erklärung-39)
+ - [▶ Namensauflösung ohne Berücksichtigung des Geltungsbereichs der Klasse](#-namensauflösung-ohne-berücksichtigung-des-geltungsbereichs-der-klasse)
+ - [💡 Erklärung](#-erklärung-40)
+ - [▶ Runden wie ein Bankier \*](#-runden-wie-ein-bankier-)
+ - [💡 Erklärung:](#-erklärung-41)
+ - [▶ Nadeln im Heuhaufen \*](#-nadeln-im-heuhaufen-)
+ - [💡 Erklärung:](#-erklärung-42)
+ - [▶ Splitsies \*](#-splitsies-)
+ - [💡 Erklärung:](#-erklärung-43)
+ - [▶ Wilde Imports \*](#-wilde-imports-)
+ - [💡 Erklärung:](#-erklärung-44)
+ - [▶ Alles sortieren ? \*](#--alles-sortieren--)
+ - [💡 Erklärung:](#-erklärung-45)
+ - [▶ Mitternachtszeit gibt es nicht ?](#-mitternachtszeit-gibt-es-nicht-)
+ - [💡 Erklärung:](#-erklärung-46)
+ - [Kapitel: Die verborgenen Schätze!](#kapitel-die-verborgenen-schätze)
+ - [▶ Okay Python, kannst du mich fliegen lassen?](#-okay-python-kannst-du-mich-fliegen-lassen)
+ - [💡 Erklärung:](#-erklärung-47)
+ - [▶ `goto`, aber wieso?](#-goto-aber-wieso)
+ - [💡 Erklärung:](#-erklärung-48)
+ - [▶ Halte dich fest!](#-halte-dich-fest)
+ - [💡 Erklärung:](#-erklärung-49)
+ - [▶ Let's meet Friendly Language Uncle For Life](#-lets-meet-friendly-language-uncle-for-life)
+ - [💡 Erklärung:](#-erklärung-50)
+ - [▶ Selbst Python versteht, dass Liebe kompliziert ist](#-selbst-python-versteht-dass-liebe-kompliziert-ist)
+ - [💡 Erklärung:](#-erklärung-51)
+ - [▶ Ja, es existiert!](#-ja-es-existiert)
+ - [💡 Erklärung:](#-erklärung-52)
+ - [▶ Ellipsen \*](#-ellipsen-)
+ - [💡 Erklärung](#-erklärung-53)
+ - [▶ Einbindung](#-einbindung)
+ - [💡 Erklärung:](#-erklärung-54)
+ - [▶ Lass uns demolieren](#-lass-uns-demolieren)
+ - [💡 Erklärung:](#-erklärung-55)
+ - [Kapitel: Der Schein trügt!](#kapitel-der-schein-trügt)
+ - [▶ Zeilen überspringen?](#-zeilen-überspringen)
+ - [💡 Erklärung](#-erklärung-56)
+ - [▶ Teleportation](#-teleportation)
+ - [💡 Erklärung:](#-erklärung-57)
+ - [▶ Da ist wohl irgendwas faul...](#-da-ist-wohl-irgendwas-faul)
+ - [💡 Erklärung](#-erklärung-58)
+ - [Kapitel: Sonstiges](#kapitel-sonstiges)
+ - [▶ `+=` ist schneller](#--ist-schneller)
+ - [💡 Erklärung:](#-erklärung-59)
+ - [▶ Lass uns einen gigantischen String machen!](#-lass-uns-einen-gigantischen-string-machen)
+ - [💡 Erklärung](#-erklärung-60)
+ - [▶ Verlangsamen von `dict` Lookups \*](#-verlangsamen-von-dict-lookups-)
+ - [💡 Erklärung:](#-erklärung-61)
+ - [▶ Blähende Instanz `dict`s \*](#-blähende-instanz-dicts-)
+ - [💡 Erklärung:](#-erklärung-62)
+ - [▶ Kleinigkeiten \*](#-kleinigkeiten-)
+ - [Das Verhalten ist darauf zurückzuführen, dass leere Teilstrings (`''`) mit Slices der Länge 0 in der ursprünglichen Zeichenkette übereinstimmen](#das-verhalten-ist-darauf-zurückzuführen-dass-leere-teilstrings--mit-slices-der-länge-0-in-der-ursprünglichen-zeichenkette-übereinstimmen)
+- [Contributing](#contributing)
+- [Anerkennung](#anerkennung)
+ - [Ein paar nützliche Links!](#ein-paar-nützliche-links)
+- [🎓 License](#-license)
+ - [Überrasche auch deine Freunde!](#überrasche-auch-deine-freunde)
+ - [Brauchst du eine pdf version?](#brauchst-du-eine-pdf-version)
+
+
+
+# Sruktur der Beispiele
+
+Alle Beispiele sind nach folgendem Muster aufgebaut:
+
+> ### ▶ Ein schicker Titel
+>
+> ```py
+> # Vorbereitung des Codes.
+> # Vorbereitung für etwas Magisches...
+> ```
+>
+> **Ausgabe (Python version(en)):**
+>
+> ```py
+> >>> triggering_statement
+> Irgendeine unerwartete Ausgabe
+> ```
+> (Optional): Eine Zeile, die die unerwartete Ausgabe beschreibt.
+>
+>
+> #### 💡 Erklärung:
+>
+> * Kurze Erklärung was und warum es passiert.
+> ```py
+> # Aufsetzen des Codes
+> # Mehr Beispiele für ein besseres Verständnis (wenn erforderlich)
+> ```
+> **Ausgabe (Python version(en)):**
+>
+> ```py
+> >>> trigger # Ein Beispiel, das es einfach macht, die Magie zu verstehen
+> # Eine begründete Ausgabe
+> ```
+
+**Note:** Alle Beispiele sind mit Pythons 3.5.2 interaktiven Interpreter getestet, und sie sollten für alle Python Versionen funktionieren. Ausnahmen werden vor dem Ausgabe kenntlich gemacht.
+
+# Benutzung
+
+Ein guter Weg, um die Beispiele bestmöglich zu nutzen, ist es, sie von anfang an durchzugehen und bei jedem Beispiel folgendes zu tun:
+- Lese vorsichtig den initialen Code des Beispiels. Wenn du ein erfahrener Python-Programmierer bist, wirst du wahrscheinlich wissen, was
+als nächstes kommt.
+- Lies die Schnippsel durch und
+ + Überprüfe, dass die Ausgabe die ist, die du erwartet hast
+ + Weißt du, warum sich die Ausgabe so gestaltet, wie sie es tut ?
+ - Wenn die Antwort Nein ist (was vollkommen in Ordnung ist), nimm einen tiefen Atemzug, und lies dir die Erklärung durch. Wenn du es dann immernoch nicht verstanden hast, frage nach Hilfe, indem du [hier](https://github.com/satwikkansal/wtfpython/issues/new) ein Issue erstellst.
+ - Wenn Ja, kannst du dir auf die Schulter klopfen und zum nächsten Beispiel springen.
+
+PS: Du kannst dir auch WTFPython im Terminal ansehen, indem du das [pypi package](https://pypi.python.org/pypi/wtfpython) nutzt:
+```sh
+$ pip install wtfpython -U
+$ wtfpython
+```
+---
+
+# 👀 Beispiele
+
+## Kapitel: Fordere dein Gehirn heraus!
+
+### ▶ Das Wichtigste zuerst! *
+
+
+
+
+Aus irgendwelchen Gründen ist der "Walrus" Operator (`:=`) in Python 3.8 ziemlich beliebt. Lass uns starten,
+
+1\.
+
+```py
+# Python version 3.8+
+
+>>> a = "wtf_walrus"
+>>> a
+'wtf_walrus'
+
+>>> a := "wtf_walrus"
+File "", line 1
+ a := "wtf_walrus"
+ ^
+SyntaxError: invalid syntax
+
+>>> (a := "wtf_walrus") # Das funktioniert merkwürdigerweise
+'wtf_walrus'
+>>> a
+'wtf_walrus'
+```
+
+2 \.
+
+```py
+# Python version 3.8+
+
+>>> a = 6, 9
+>>> a
+(6, 9)
+
+>>> (a := 6, 9)
+(6, 9)
+>>> a
+6
+
+>>> a, b = 6, 9 # Typisches Auspacken
+>>> a, b
+(6, 9)
+>>> (a, b = 16, 19) # Oops
+ File "", line 1
+ (a, b = 16, 19)
+ ^
+SyntaxError: invalid syntax
+
+>>> (a, b := 16, 19) # Dies gibt ein eigenartiges 3-Tupel aus
+(6, 16, 19)
+
+>>> a # Ist a immernoch unverändert ?
+6
+
+>>> b
+16
+```
+
+
+
+#### 💡 Erklärung
+
+**Schneller Rückblick zum Walrus Operator**
+
+Der Walrus Operator (`:=`) wurde in Python 3.8 eingeführt. Er kann in Situationen sinvoll sein, in
+denen du ein Wert einer Variablen in einem Ausdruck zuweisen möchtest.
+
+```py
+def irgendeine_funktion():
+ # Irgendeine Berechnung, die teuer ist (=> sehr viel Zeit und Ressourcen in Aspruch nimmt)
+ # time.sleep(1000)
+ return 5
+
+# Anstatt:
+if irgendeine_funktion():
+ print(irgendeine_funktion()) # Schlechter Stil, da die Funktion zweimal aufgerufen wird
+
+# Oder:
+a = irgendeine_funktion()
+if a:
+ print(a)
+
+# Nun kannst du folgendes schreiben:
+if a := irgendeine_funktion():
+ print(a)
+```
+
+**Ausgabe (> 3.8):**
+
+```py
+5
+5
+5
+```
+
+Das hat uns eine Zeile Code erspart. Zudem spart es einen zusätzlichen Aufruf der Funktion `irgendeine_funktion`.
+
+- Nichtgeklammerte "Zuweisung Ausdruck" (Verwendung des Walrus-Operator), ist auf der obersten Ebene beschränkt, daher der `Syntaxfehler` in der Anweisung `a := "wtf_walrus"` des erstes Schnipsels. Einklammeren hat funktioniert und wies `a` zu.
+
+- Wie immer, Einklammerung eines Ausdrucks, welcher `=`- Operator enthält, ist nicht erlaubt. Daher der Syntaxfehler in `(a, b = 6, 9)`.
+
+- Die Syntax des Walrus Operators lautet wie folgt: `NAME:= ausdruck`, wobei `NAME` ist ein gültiger Identifier, und `ausdruck` ist ein gültiger Ausdruck. Zudem werden iterierbares Verpacken und Entpacken nicht unterstützt, d.h.:
+
+ - `(a := 6, 9)` ist äquivalent zu `((a := 6), 9)` und zu `(a, 9) ` (where `a`'s value is 6')
+
+ ```py
+ >>> (a := 6, 9) == ((a := 6), 9)
+ True
+ >>> x = (a := 696, 9)
+ >>> x
+ (696, 9)
+ >>> x[0] is a # Both reference same memory location
+ True
+ ```
+
+ - Ähnlich: `(a, b := 16, 19)` ist äquivalent zu `(a, (b := 16), 19)`, was einfach ein 3-Tupel ist.
+
+---
+
+### ▶ Strings können manchmal schwierig sein
+
+
+1\.
+
+```py
+>>> a = "irgendein_string"
+>>> id(a)
+140420665652016
+>>> id("irgendein" + "_" + "string") # Beachte, dass beide ids dieselben sind.
+140420665652016
+```
+
+2\.
+```py
+>>> a = "wtf"
+>>> b = "wtf"
+>>> a is b
+True
+
+>>> a = "wtf!"
+>>> b = "wtf!"
+>>> a is b
+False
+
+```
+
+3\.
+
+```py
+>>> a, b = "wtf!", "wtf!"
+>>> a is b # Alle Versionen außer 3.7.x
+True
+
+>>> a = "wtf!"; b = "wtf!"
+>>> a is b # Das wird True oder False ausgeben, je nach dem wo du es aufrufst (Python Shell / iPython / in einem Skript)
+False
+```
+
+```py
+# Dieses mal in einer Datei: some_file.py
+a = "wtf!"
+b = "wtf!"
+print(a is b)
+
+# Gibt True aus, wenn das Modul aufgerufen wird!
+```
+
+4\.
+
+**Ausgabe (< Python3.7 )**
+
+```py
+>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
+True
+>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
+False
+```
+
+Ergibt Sinn, Oder?
+
+#### 💡 Erklärung:
++ Das Verhalten im ersten und zweiten Schnipsel erklärt sich durch eine CPython Optimierung (auch string interning genannt), die versucht, existierende
+immutable Objekte zu nutzen anstatt jedes mal ein neues Objekt zu erstellen.
++ Nachdem "interned" (festgehalten) wurde, kann es sein, dass viele Variablen dasselbe String-Objekt im Speicher referenzieren (man spart also Speicher).
+
++ In den Schnipseln oben werden Strings implizit festgehalten. Die Entscheidung, wann ein String implizit festgehalten wird, ist von der Implementierung
+abhängig. Es gibt einige Regeln, die benutzt werden können, um zu erahnen, ob ein String festgehalten wird oder nicht:
+ * Alle String der Länge 0 und 1 werden festgehalten.
+ * Strings werden während der Compilezeit festgehalten (`'wtf'` wird festgehalten, aber `''.join(['w', 't', 'f'])` nicht)
+ * Strings, die nicht aus ASCII-Buchstaben, Ziffern oder Unterstrichen zusammengesetzt sind, werden nicht festgehalten. Das erklärt warum `'wtf!'` nicht festgehalten wurde (wegen `!`).
+ Die CPython-Implementierung dieser Regel kann [hier](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19) gefunden werden
+ ![image](../images/string-intern/string_intern.png)
++ Wenn `a` und `b` in derselben Zeile auf `"wtf!"` gesetzt werden, erzeugt der Python Interpreter ein neues Objekt, welches von der zweiten Variable zur selben Zeit referenziert wird. Wenn du es in zwei verschiedenen Zeilen deklarierst, dann "weiß" der Interpreter nicht, dass `"wtf!"` als Objekt schon existiert (weil `"wtf!"` nicht implizit festgehalten wird, siehe obige Auflistung). Es ist eine Compilezeit-Optimierung. Diese Optimierung gilt nicht für 3.7.x Versionen von CPython (siehe dieses [Issue](https://github.com/satwikkansal/wtfpython/issues/100)).
++ Eine Compile-Unit ist eine interaktive Umgebung, wie z.B. IPython besteht aus einen einzigen Statement, während es aus einem ganzen Modul im Falle von Modulen besteht. `a, b = "wtf!", "wtf!"` ist ein einziges Statement, während `a = "wtf!"; b = "wtf!"` zwei Statements in einer Zeile sind. Das erklärt, warum die Identitäten `a = "wtf!"; b = "wtf!"` verschieden sind. Es erklärt auch, warum sie dieselben sind, wenn sie in `some_file.py` aufgerufen werden.
++ Die abrupte Veränderung in der Ausgabe des 4.Schnipsel ist der [peephole Optimierung](https://en.wikipedia.org/wiki/Peephole_optimization) Technik
+geschuldet, auch als Constant Folding bekannt. Das bedeutet, der Ausdruck `'a'*20` wird durch `'aaaaaaaaaaaaaaaaaaaa'` während der Kompilierung ersetzt, um ein paar Taktzyklen während der Laufzeit zu sparen. Constant Folding wird nur für String mit einer kleineren Länge als 21 angewendet. (Wieso ? Stelle dir die Größe einer `.pyc` Datei vor, die durch den Ausdruck `'a'*10**10` generiert wurde). [Hier](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288) ist die Quelle der Implementierung dafür.
++ Notiz: In Python 3.7, konstantes Folding wurde vom peephole-Optimierer zum neuen AST-Optimierer verschoben (mit ein paar Veränderungen in der Logik), d.h. das 4.Schnipsel funktioniert in Python 3.7 nicht. Du kannst [hier](https://bugs.python.org/issue11549) mehr darüber erfahren.
+
+---
+
+
+### ▶ Vorsicht bei verketteten Operationen
+
+```py
+>>> (False == False) in [False] # ergibt Sinn
+False
+>>> False == (False in [False]) # ergibt Sinn
+False
+>>> False == False in [False] # Was nun?
+True
+
+>>> True is False == False
+False
+>>> False is False is False
+True
+
+>>> 1 > 0 < 1
+True
+>>> (1 > 0) < 1
+False
+>>> 1 > (0 < 1)
+False
+```
+
+#### 💡 Erklärung:
+
+Zitat von https://docs.python.org/3/reference/expressions.html#comparisons
+
+> 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.
+
+Übersetzt:
+
+> Formal ausgedrückt: wenn a, b, c, ..., y, z Ausdrücke und op1, op2, ..., opN Vergleichsoperatoren sind, dann sind a op1 b op2 c ... y opN z äquivalent zu a op1 b and b op2 c and ... y opN z, mit der Ausnahme, dass jeder Ausdruck höchstens einmal ausgewertet wird.
+
+
+Während dieses Verhalten in den Beispielen vielleicht unsinnig erscheint, kann es super verwendet werden, z.B. `a == b == c` und `0 <= x <= 100`.
+
+* `False is False is False` ist äquivalent zu `(False is False) and (False is False)`
+* `True is False == False` ist äquivalent zu `(True is False) and (False == False)` und während der erste Teil des Statements (`True is False`) zu `False` ausgewertet wird, wird der gesamt Ausdruck zu `False` ausgewertet.
+* `1 > 0 < 1` ist äquivalent zu `(1 > 0) and (0 < 1)` which evaluates to `True`.
+* Der Ausdruck `(1 > 0) < 1` ist äquivalent zu `True < 1` und
+ ```py
+ >>> int(True)
+ 1
+ >>> True + 1 # Nicht relevant für dieses Beispiel, aber trotzdem nur zum Spaß
+ 2
+ ```
+ So wird `1 < 1` zu `False` ausgewertet
+
+---
+
+### ▶ Wie man den `is` Operator nicht nutzt
+
+Das folgende Beispiel ist im Internet überall bekannt.
+
+1\.
+
+```py
+>>> a = 256
+>>> b = 256
+>>> a is b
+True
+
+>>> a = 257
+>>> b = 257
+>>> a is b
+False
+```
+
+2\.
+
+```py
+>>> a = []
+>>> b = []
+>>> a is b
+False
+
+>>> a = tuple()
+>>> b = tuple()
+>>> a is b
+True
+```
+
+3\.
+**Ausgabe**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+True
+```
+
+**Ausgabe (Python 3.7.x spezifisch)**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+False
+```
+
+#### 💡 Erklärung:
+
+**Der Unterschied zwischen `is` und `==`**
+
+* `is` Operator checkt, ob sich beide Operanden auf dasselbe Objekt beziehen (i.e., it checks if the identity of the operands matches or not).
+* `==` Operator vergleicht die Werte der beiden Operanden und überprüft, ob diese gleich sind.
+* Also `is` wird für Beziehungsgleichheit und `==` für Wertgleichheit benutzt. Ein Beispiel, um das Gesagte zu vertiefen:
+ ```py
+ >>> class A: pass
+ >>> A() is A() # Das sind zwei leere Objekte an zwei verschiedenen Orten im Speicher.
+ False
+ ```
+
+**`256` ist ein existierendes Objekt, aber `257` nicht**
+
+Wenn du Python startest, werden die Nummern von `-5` bis `256` bereitgestellt. Diese Nummern werden sehr oft benutzt, also ergibt es Sinn,
+sie schnell bereit zu haben.
+
+Zitat von https://docs.python.org/3/c-api/long.html
+> 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. :-)
+
+Übersetzung:
+> Die momentane Implementation stellt ein Array aus Integer-Objekten für alle Integer zwischen -5 und 256 bereit. Wenn du einen int in diesem Bereich erstellst, bekommst du nur eine Referenz auf das existierende Objekt zurück. Also sollte es möglich sein, den Wert von 1 zu ändern. Ich vermute das Verhalten von Python ist in diesem Fall undefiniert. :-)
+
+
+```py
+>>> id(256)
+10922528
+>>> a = 256
+>>> b = 256
+>>> id(a)
+10922528
+>>> id(b)
+10922528
+>>> id(257)
+140084850247312
+>>> x = 257
+>>> y = 257
+>>> id(x)
+140084850247440
+>>> id(y)
+140084850247344
+```
+
+Hier ist der Interpreter nicht schlau genug während des Ausführens von `y = 257` zu erkennen, dass wir bereits ein Integer mit dem Wert `257` erstellt haben und daher wird ein neues Objekt im Speicher angelegt.
+
+Ähnliche Optimierungen treffen auf andere **immutable** Objekte zu, z.B. leere Tuples. Da Listen mutable sind, wird `[] is []` zu `False` ausgewertet und `() is ()` wird zu `True` ausgewertet. Das erklärt unser zweiter Schnipsel. Lass uns mit dem dritten Beispiell weiter machen:
+
+**Sowohl `a` und `b` beziehen sich auf dasselbe Objekt wenn sie in derselben Zeile mit demselben Wert initialisiert werden.**
+
+**Ausgabe**
+
+```py
+>>> a, b = 257, 257
+>>> id(a)
+140640774013296
+>>> id(b)
+140640774013296
+>>> a = 257
+>>> b = 257
+>>> id(a)
+140640774013392
+>>> id(b)
+140640774013488
+```
+
+* Wenn a und b in derselben Zeile auf `257` gesetzt werden, erstellt der Python Interpreter ein neues Objekt, und referenziert die zweite Variable
+zur selben Zeit. Wenn man es in verschiedenen Zeilen macht, dann weiß Python nicht, dass eine `257` als Objekt schon existiert.
+
+* Es ist eine Compiler Optimierung, die speziell für die interaktive Umgebung gilt. Wenn man zwei Zeilen in einem Live Interpreter eingibt, dann
+werden sie getrennt kompiliert und auch getrennt optimiert. Wenn du dieses Beispiel in einer `.py` Datei ausprobierst, würdest du nicht dasselbe
+Verhalten beobachten, denn die Datei wird auf einmal kompiliert. Diese Optimierung ist nicht auf Integer beschränkt, sie funktioniert auch für
+andere immutable Datentypen, wie z.B. Strings (siehe auch das Beispiel "Strings können manchmal schwierig sein") oder floats:
+
+ ```py
+ >>> a, b = 257.0, 257.0
+ >>> a is b
+ True
+ ```
+
+* Warum funktioniert das nicht in Python 3.7 ? Die abstrakte Antwort ist: Die Compiler Optimierungen sind von der Implementierung abhängig (es verändert sich mit der Version, dem Betriebssystem, etc.). Ich versuche noch herauszufinden, welche Implementierungsänderung dieses Problem
+verursacht. Für Updates, schaue dieses [Issue](https://github.com/satwikkansal/wtfpython/issues/100) an.
+
+---
+
+
+### ▶ Hash brownies
+
+1\.
+```py
+some_dict = {}
+some_dict[5.5] = "JavaScript"
+some_dict[5.0] = "Ruby"
+some_dict[5] = "Python"
+```
+
+**Ausgabe:**
+
+```py
+>>> some_dict[5.5]
+"JavaScript"
+>>> some_dict[5.0] # "Python" hat die Existenz von "Ruby" ausgelöscht ?
+"Python"
+>>> some_dict[5]
+"Python"
+
+>>> complex_five = 5 + 0j
+>>> type(complex_five)
+complex
+>>> some_dict[complex_five]
+"Python"
+```
+
+Warum also ist Python überall zu finden ?
+
+
+#### 💡 Erklärung
+
+* Einzigartigkeit der Schlüssel in einem Python Dictionary wird durch *Äquivalenz*, nicht durch Identität festgestellt. Obwohl also `5`, `5.0`, und `5 + 0j` verschiedene Objekte unterschiedlichen Typs sind, können sie, da sie gleichwertig sind, nicht im gleichen `dict` (oder `set`) sein.
+Sobald du einen von ihnen einfügst, wird der Versuch, nach einem anderen, aber gleichwertigen Schlüssel zu suchen, mit dem ursprünglichen zugeordneten Wert erfolgreich sein (und nicht mit einem `KeyError` fehlschlagen):
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> 5 is not 5.0 is not 5 + 0j
+ True
+ >>> some_dict = {}
+ >>> some_dict[5.0] = "Ruby"
+ >>> 5.0 in some_dict
+ True
+ >>> (5 in some_dict) and (5 + 0j in some_dict)
+ True
+ ```
+* Das gilt auch, wenn ein Item festgelegt wird. Wenn du also `some_dict[5] = "Python"` ausführst, findet Python
+das existierende Item mit demselben Key `5.0 -> "Ruby"`, überschreibt den Wert an dieser Stelle, und lässt den
+originalen Wert unangetastet.
+ ```py
+ >>> some_dict
+ {5.0: 'Ruby'}
+ >>> some_dict[5] = "Python"
+ >>> some_dict
+ {5.0: 'Python'}
+ ```
+* Wie können wir also den Key zu `5` anstelle von `5.0` updaten? Wir können das tatsächlich nicht an dieser Stelle
+tun, aber wir können den Schlüssel zuerst löschen (`del some_dict[5.0]`), und ihn danach neu festzulegen (`some_dict[5]`), um den Schlüssel `5` zu bekommen, anstelle des floats `5.0`. Das wird jedoch nur in seltenen Fällen benötigt.
+
+* Wie hat Python `5` in einem Dictionary gefunden, welches `5.0` enthält? Python tut dies in konstanter Zeit, ohne jedes Item zu scannen, indem es Hash-Funktionen benutzt. Wenn Python den Key `foo` in einem Dictionary nachschlägt, dann verarbeitet es zuerst `hash(foo)` (was in konstanter Zeit läuft). Da es in Python notwendig ist, dass Objekte, die als gleich gelten auch den gleichen Hash-Wert haben ( siehe hier [docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__)), haben `5`, `5.0`, und `5 + 0j` denselben Hash-Wert.
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> hash(5) == hash(5.0) == hash(5 + 0j)
+ True
+ ```
+ **Note:** Das Inverse ist nicht unbedingt wahr: Objekte mit gleichem Hashwert können evtl. ungleich sein. (Das versursacht was auch als [hash collision](https://de.wikipedia.org/wiki/Kollisionsresistenz) bekannt ist), und und verschlechtert die zeitlich konstante Leistung, die Hashing normalerweise bietet.
+
+---
+
+### ▶ Tief im Inneren sind wir alle gleich
+
+```py
+class WTF:
+ pass
+```
+
+**Ausgabe:**
+```py
+>>> WTF() == WTF() # zwei verschiedene Instanzen können nicht gleich sein
+False
+>>> WTF() is WTF() # Idetitäten sind ebenfalls unterschiedlich
+False
+>>> hash(WTF()) == hash(WTF()) # Hash-Werte _sollten_ ebenfalls verschieden sein
+True
+>>> id(WTF()) == id(WTF())
+True
+```
+
+#### 💡 Erklärung:
+
+* Wenn `id` genannt wurde, hat Python hat ein `WTF` class-Objekt gebaut und es der `id`-Funktion übergeben. Die `id`-Funktion nimmt die `id` (den Speicherort), und wirft das Objekt weg. Das Objekt ist zerstört.
+* When we do this twice in succession, Python allocates the same memory location to this second object as well. Da (in CPython) `id` denselben Speicherort wie die Objekt-Id benutzt, ist die id der beiden Objekte dieselbe.
+* Also ist die Id des Objektes nur für die Lebensdauer des Objektes einzigartig. Nachdem das Objekt zerstört wurde, oder bevor es gebaut wird, kann etwas anderes diese Id haben.
+* Abe warum wurde der `is` zu `False` ausgewertet? Lass uns das anhand dieses Schnipsels betrachten.
+ ```py
+ class WTF(object):
+ def __init__(self): print("I")
+ def __del__(self): print("D")
+ ```
+
+ **Ausgabe:**
+ ```py
+ >>> WTF() is WTF()
+ I
+ I
+ D
+ D
+ False
+ >>> id(WTF()) == id(WTF())
+ I
+ D
+ I
+ D
+ True
+ ```
+ Wie du villeicht beobachtest hast, ist die Reihenfolge, in der die Objekte zerstört werden, das, was hier den
+ Unterschied macht.
+---
+
+### ▶ Unordnung in der Ordnung *
+
+```py
+from collections import OrderedDict
+
+dictionary = dict()
+dictionary[1] = 'a'; dictionary[2] = 'b';
+
+ordered_dict = OrderedDict()
+ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+
+another_ordered_dict = OrderedDict()
+another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+
+class DictWithHash(dict):
+ """
+ Ein Dictionary auch __hash__ magic implementiert.
+ """
+ __hash__ = lambda self: 0
+
+class OrderedDictWithHash(OrderedDict):
+ """
+ Ein OrderedDict was auch __hash__ magic implementiert.
+ """
+ __hash__ = lambda self: 0
+```
+
+**Ausgabe**
+```py
+>>> dictionary == ordered_dict # Wenn a == b
+True
+>>> dictionary == another_ordered_dict # und b == c
+True
+>>> ordered_dict == another_ordered_dict # warum ist dann c != a ??
+False
+
+# Wir wissen alle, dass ein Set nur aus einzigartigen Elementen besteht,
+# Lass uns ein Set aus Dictionaries bauen und sehen, was passiert...
+
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unhashable type: 'dict'
+
+# Ergibt Sinn, denn ein Dictionary implementiert nicht __hash__, lass uns unsere
+# Wrapper-Klasse benutzen.
+>>> dictionary = DictWithHash()
+>>> dictionary[1] = 'a'; dictionary[2] = 'b';
+>>> ordered_dict = OrderedDictWithHash()
+>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+>>> another_ordered_dict = OrderedDictWithHash()
+>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+1
+>>> len({ordered_dict, another_ordered_dict, dictionary}) # verändere die Reihenfolge
+2
+```
+
+Was geht hier vor ?
+
+#### 💡 Erklärung:
+
+- Der Grund, warum die intransitive Gleichheit zwischen `dictionary`, `ordered_dict` und `another_ordered_dict` nicht gilt, liegt in der `__eq__` Methode und wie diese in der `OrderedDict`-Klasse implementiert ist. Aus der [Dokumentation](https://docs.python.org/3/library/collections.html#ordereddict-objects)
+
+ > Gleichheitstests zwischen OrderedDict-Objekten sind ordnungsabhängig und werden als `list(od1.items())==list(od2.items())` implementiert. Gleichheitstests zwischen `OrderedDict`-Objekten und anderen Mapping-Objekten sind nicht ordnungsabhängig wie bei regulären Dictionaries.
+
+- Der Grund für diese Gleichheit im Verhalten ist, dass sie es ermöglicht, `OrderedDict`-Objekte direkt überall dort zu ersetzen, wo ein reguläres Wörterbuch verwendet wird.
+- Okay, warum also hat die Änderung der Reihenfolge Auswirkungen auf die Länge des erzeugten `set`-Objekts? Die Antwort ist das Fehlen der intransitiven Gleichheit. Da Mengen "ungeordnete" Sammlungen von eindeutigen Elementen sind, sollte die Reihenfolge, in der die Elemente eingefügt werden, keine Rolle spielen. Aber in diesem Fall spielt sie doch eine Rolle. Lass uns das ein wenig aufschlüsseln
+ ```py
+ >>> some_set = set()
+ >>> some_set.add(dictionary) # das sind die Mapping-Objekte von unseren Schnipseln oben
+ >>> ordered_dict in some_set
+ True
+ >>> some_set.add(ordered_dict)
+ >>> len(some_set)
+ 1
+ >>> another_ordered_dict in some_set
+ True
+ >>> some_set.add(another_ordered_dict)
+ >>> len(some_set)
+ 1
+
+ >>> another_set = set()
+ >>> another_set.add(ordered_dict)
+ >>> another_ordered_dict in another_set
+ False
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ >>> dictionary in another_set
+ True
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ ```
+ Die Inkonsistenz liegt bei `another_ordered_dict in another_set`, was `False` ist, weil `ordered_dict` bereits in `another_set` enthalten ist und wie schon vorher beobachtet, `ordered_dict == another_ordered_dict` ist `False`.
+
+---
+
+### ▶ Versuche es weiter... *
+
+```py
+def some_func():
+ try:
+ return 'from_try'
+ finally:
+ return 'from_finally'
+
+def another_func():
+ for _ in range(3):
+ try:
+ continue
+ finally:
+ print("Finally!")
+
+def one_more_func(): # Ein gotcha!
+ try:
+ for i in range(3):
+ try:
+ 1 / i
+ except ZeroDivisionError:
+ # Lass es uns hier hin packen und es außerhalb des Loops behandeln
+ raise ZeroDivisionError("A trivial divide by zero error")
+ finally:
+ print("Iteration", i)
+ break
+ except ZeroDivisionError as e:
+ print("Zero division error occurred", e)
+```
+
+**Ausgabe:**
+
+```py
+>>> some_func()
+'from_finally'
+
+>>> another_func()
+Finally!
+Finally!
+Finally!
+
+>>> 1 / 0
+Traceback (most recent call last):
+ File "", line 1, in
+ZeroDivisionError: division by zero
+
+>>> one_more_func()
+Iteration 0
+
+```
+
+#### 💡 Erklärung:
+
+- Wenn ein `return`-, `break`- oder `continue`-Anweisung in einem `try` ("try…finally") Anweisung ausgeführt wird,
+dann wird der `finally`-Abschnitt am Ende ebenfalls ausgeführt.
+- Der Rückgabewert einer Funktion wird durch die letzte `return`-Anweisung bestimmt. Da der `finally`-Abschnitt
+immer ausgeführt wird, wird eine `return`-Anweisung im `finally`-Abschnitt immer die letzte sein, die ausgeführt wird.
+- Wenn also der `finally`-Abschnitt eine `return`- oder `break`-Anweisung ausführt, dann wird die kurzzeitige
+Exception verworfen.
+
+---
+
+
+### ▶ Wofür?
+
+```py
+some_string = "wtf"
+some_dict = {}
+for i, some_dict[i] in enumerate(some_string):
+ i = 10
+```
+
+**Ausgabe:**
+```py
+>>> some_dict # Ein indiziertes dictionary erscheint.
+{0: 'w', 1: 't', 2: 'f'}
+```
+
+#### 💡 Erklärung:
+
+* Eine `for` Anweisung ist in [Python Syntax](https://docs.python.org/3/reference/grammar.html) wie folgt definiert:
+ ```
+ for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
+ ```
+ `exprlist` ist dabei das Zuweisungsziel. Das heißt, dass das Äquivalente von `{exprlist} = {next_value}` im Iterable **executed for each item** ist.
+ Ein interessantes Beispiel, was dies verdeutlicht:
+ ```py
+ for i in range(4):
+ print(i)
+ i = 10
+ ```
+
+ **Ausgabe:**
+ ```
+ 0
+ 1
+ 2
+ 3
+ ```
+
+ Hast du erwartet, dass die Schleife nur einmal läuft ?
+
+ **💡 Erklärung:**
+
+ - Das Zuweisungs-Statement `i = 10` hat niemals einen Effekt auf die Schleife, aufgrund der Funktionsweise von
+ for-Schleifen in Python. Vor dem Beginn jeder Iteration, wird das nächste Objekt, was vom Iterator (in diesem
+ Fall `range(4)`) bereitgestellt wird, wird ausgepackt und der Zielliste zugewiesen (in diesem Fall `i`).
+
+* Die `enumerate(some_string)` Funktion liefert ein neuen Wert `i` (ein Zähler, der aufwärts läuft) und ein
+Character vom String `some_string` in jeder Iteration. Dann wird der gerade erzeugte Wert `i` des Dictionaries
+`some_dict` als Key zu diesem Character gesetzt. Das Verhalten der Schleife kann wie folgt vereinfacht werden:
+
+ ```py
+ >>> i, some_dict[i] = (0, 'w')
+ >>> i, some_dict[i] = (1, 't')
+ >>> i, some_dict[i] = (2, 'f')
+ >>> some_dict
+ ```
+
+---
+
+### ▶ Diskrepanz in der Auswertungszeit
+
+1\.
+```py
+array = [1, 8, 15]
+# Ein typischer Generator-Ausdruck
+gen = (x for x in array if array.count(x) > 0)
+array = [2, 8, 22]
+```
+
+**Ausgabe:**
+
+```py
+>>> print(list(gen)) # Wo sind die anderen Variablen hin?
+[8]
+```
+
+2\.
+
+```py
+array_1 = [1,2,3,4]
+gen_1 = (x for x in array_1)
+array_1 = [1,2,3,4,5]
+
+array_2 = [1,2,3,4]
+gen_2 = (x for x in array_2)
+array_2[:] = [1,2,3,4,5]
+```
+
+**Ausgabe:**
+```py
+>>> print(list(gen_1))
+[1, 2, 3, 4]
+
+>>> print(list(gen_2))
+[1, 2, 3, 4, 5]
+```
+
+3\.
+
+```py
+array_3 = [1, 2, 3]
+array_4 = [10, 20, 30]
+gen = (i + j for i in array_3 for j in array_4)
+
+array_3 = [4, 5, 6]
+array_4 = [400, 500, 600]
+```
+
+**Ausgabe:**
+```py
+>>> print(list(gen))
+[401, 501, 601, 402, 502, 602, 403, 503, 603]
+```
+
+#### 💡 Erklärung
+
+- In einem [Generator](https://wiki.python.org/moin/Generators)-Ausdruck wird die `in`-Klausel zur Deklarationszeit ausgewertet, während die Bedingungsklausel zur Laufzeit ausgewertet wird.
+- Vor der Laufzeit wird `array` wieder der Liste `[2, 8, 22]` zugewiesen, und da von `1`, `8` und `15`, nur die Anzahl von `8` größer als `0` ist, liefert der Generator nur `8`.
+- Die Unterschiede in der Ausgabe von `g1` und `g2` im zweiten Teil sind auf die Art und Weise zurückzuführen, wie den Variablen `array_1` und `array_2` neue Werte zugewiesen werden.
+- Im ersten Fall wird `array_1` zum neuen Objekt `[1,2,3,4,5]` gebunden und da die `in`-Klausel zur Deklarationszeit ausgewertet wird, bezieht es sich immer noch auf das alte Objekt `[1,2,3,4]` (was nicht zerstört wird).
+- Im zweiten Fall updated die Slice-Anweisung an `array_2` das gleiche alte Objekt `[1,2,3,4]` zu `[1,2,3,4,5]`. Daher verweisen sowohl `g2` als auch `array_2` immer noch auf dasselbe Objekt (welches nun zu `[1,2,3,4,5]` geupdated wird).
+- Okay, wenn wir die Logik anwenden, die wir bis jetzt kennengelernt haben, sollte dann der Wert von `list(gen)` im dritten Schnipsel nicht `[11, 21, 31, 12, 22, 32, 13, 23, 33]` sein? (weil `array_3` und `array_4` sich genauso wie `array_1` verhalten werden). Die Grund, warum (nur) die Werte von `array_4` geändert werden, wird im [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details) erklärt.
+
+ > Nur der äußerste for-Ausdruck wird direkt ausgewertet, die anderen Ausdrücke werden zurückgestellt, bis der Generator ausgeführt wird.
+---
+
+
+### ▶ `is not ...` ist nicht `is (not ...)`
+
+```py
+>>> 'something' is not None
+True
+>>> 'something' is (not None)
+False
+```
+
+#### 💡 Erklärung
+
+- `is not` ist ein einzelner binärer Operator, der anderes Verhalten zeigt, als wenn man `is` und `not` einzeln benutzt.
+- `is not` wird zu `False` ausgewertet wenn die Variablen auf beiden Seiten des Operators auf dasselbe Objekt verweisen, andernfalls zu `True`.
+- Im Beispiel wird `(not None)` zu `True` ausgewertet, denn der Wert `None` ist `False` im booleschen Kontext, also wird der Ausdruck zu `'something' is True` ausgewertet.
+
+---
+
+### ▶ Ein tic-tac-toe wo X im ersten Versuch gewinnt!
+
+
+```py
+# Lass uns eine Zeile initialisieren
+row = [""] * 3 #row i['', '', '']
+# Lass uns ein board bauen
+board = [row] * 3
+```
+
+**Ausgabe:**
+
+```py
+>>> board
+[['', '', ''], ['', '', ''], ['', '', '']]
+>>> board[0]
+['', '', '']
+>>> board[0][0]
+''
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['X', '', ''], ['X', '', '']]
+```
+
+Wir haben nicht dreimal `"X"` zugewiesen, oder?
+
+#### 💡 Erklärung:
+
+Wenn wir die Variable `row` initialisieren, dann erklärt diese Visualisierung, was im Speicher passiert
+
+![image](../images/tic-tac-toe/after_row_initialized.png)
+
+Und wenn das `board` durch Multiplizieren der `row` initialisiert wird, dann passiert das hier innerhalb des Speichers (jedes der Elemente `board[0]`, `board[1]` und `board[2]` ist eine Referenz aud dieselbe Liste, aud die `row` verweist)
+
+![image](../images/tic-tac-toe/after_board_initialized.png)
+
+Wir können dieses Szenario umfahren, indem wir nicht die `row` Variable zum generieren von `board` benutzen. (gefragt [hier](https://github.com/satwikkansal/wtfpython/issues/68)).
+
+```py
+>>> board = [['']*3 for _ in range(3)]
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['', '', ''], ['', '', '']]
+```
+
+---
+
+### ▶ Schrödingers Variable *
+
+
+
+```py
+funcs = []
+results = []
+for x in range(7):
+ def some_func():
+ return x
+ funcs.append(some_func)
+ results.append(some_func()) # Beachte hier den Funktionsaufruf
+
+funcs_results = [func() for func in funcs]
+```
+
+**Ausgabe (Python version):**
+```py
+>>> results
+[0, 1, 2, 3, 4, 5, 6]
+>>> funcs_results
+[6, 6, 6, 6, 6, 6, 6]
+```
+
+The values of `x` were different in every iteration prior to appending `some_func` to `funcs`, but all the functions return 6 when they're evaluated after the loop completes.
+
+2.
+
+```py
+>>> powers_of_x = [lambda x: x**i for i in range(10)]
+>>> [f(2) for f in powers_of_x]
+[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
+```
+
+#### 💡 Erklärung:
+* Wenn wir eine Funktion innerhalb einer Schleife definieren, welche die Schleifenvariable in ihrem Körper benutzt, dann ist der Abschluss der Schleifenfunktion an die *Variable* gebunden, nicht an ihren *Wert*.
+Die Funktion schlägt `x` in dem umgebenden Kontext nach, anstatt den Wert von `x` zum Zeitpunkt der Erstellung der Funktion zu benutzen. Also verwenden alle Funktionen den letzten Wert, der der Variable zugewiesen wurde, für ihre Berechnungen. Wir können beobachten, dass `x` vom umgebenen Kontext verwendet wird (d.h. *keine* lokale Variable) mit:
+
+```py
+>>> import inspect
+>>> inspect.getclosurevars(funcs[0])
+ClosureVars(nonlocals={}, globals={'x': 6}, builtins={}, unbound=set())
+```
+Da `x` ein globaler Wert ist, können wir den Wert, den `funcs` nachschlägt und zurüchgibt, verändern, indem wir `x` updaten:
+
+```py
+>>> x = 42
+>>> [func() for func in funcs]
+[42, 42, 42, 42, 42, 42, 42]
+```
+
+* Um das entsprechende Verhalten zu bekommen, kannst du die Loop-Variable als named-Variable der Funktion übergeben. **Warum funktioniert das?** Weil dies die Variable *innerhalb* des Scopes der Funktion definiert. Sie wird nicht länger im umgebenden (globalen) Scope definiert, sondern es wird eine lokale Variable erzeugt, die den Wert von `x` zu diesem Zeitpunkt speichert.
+
+```py
+funcs = []
+for x in range(7):
+ def some_func(x=x):
+ return x
+ funcs.append(some_func)
+```
+
+**Ausgabe:**
+
+```py
+>>> funcs_results = [func() for func in funcs]
+>>> funcs_results
+[0, 1, 2, 3, 4, 5, 6]
+```
+
+`x` wird nicht länger im globalen Scope verwendet:
+
+```py
+>>> inspect.getclosurevars(funcs[0])
+ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())
+```
+
+---
+
+### ▶ Das Henne-Ei-Problem *
+
+1\.
+```py
+>>> isinstance(3, int)
+True
+>>> isinstance(type, object)
+True
+>>> isinstance(object, type)
+True
+```
+
+Was ist also die ultimative Basisklasse? Die Verwirrung wird noch größer:
+
+2\.
+
+```py
+>>> class A: pass
+>>> isinstance(A, A)
+False
+>>> isinstance(type, type)
+True
+>>> isinstance(object, object)
+True
+```
+
+3\.
+
+```py
+>>> issubclass(int, object)
+True
+>>> issubclass(type, object)
+True
+>>> issubclass(object, type)
+False
+```
+
+
+#### 💡 Erklärung
+
+- `type` ist eine [Metaklasse](https://realpython.com/python-metaclasses/) in Python.
+- **Alles** ist ein `object` in Python, was Klassen und ihre Objekte (Instanzen) einschließt.
+- Die Klasse `type` ist eine Metaklasse der Klasse `object`, und jede Klasse (einschließlich `type`) hat direkt oder indirekt von `object` geerbt.
+- Es gibt keine echte Basisklasse zwischen `object` und `type`. Die Verwirrung im obigen Schnipsel ergibt sich weil wir über diese Beziehungen (`issubclass` und `isinstance`) in Form von Python-Klassen denken. Die Beziehung zwischen `object` und `type` kann nicht in reinem Python reproduziert werden. Um präziser zu sein, die folgenden Beziehungen können nicht in reinem Python reproduziert werden:
+ + Klasse A ist eine Instanz der Klasse B, und Klasse B ist eine Instanz von Klasse A.
+ + Klasse A ist eine Instanz von sich selbst.
+- Diese Beziehungen zwischen `object` und `type` (beide sind Instanzen voneinander und von sich selbst) existieren in Python, weil auf dem Level der Implementierung "geschummelt" wird.
+
+---
+
+### ▶ Beziehungen in Unterklassen
+
+**Ausgabe:**
+```py
+>>> from collections import Hashable
+>>> from collections.abc import Hashable
+>>> issubclass(list, object)
+True
+>>> issubclass(object, Hashable)
+True
+>>> issubclass(list, Hashable)
+False
+```
+
+Die Unterklassenbeziehungen sollten transitiv sein, nicht wahr? (d.h., wenn `A` eine Unterklasse von `B` ist, und `B` eine Unterklasse von `C`, dann _sollte_ `A` eine Unterklasse von `C` sein)
+
+#### 💡 Erklärung:
+
+* Unterklassenbeziehungen in Python sind nicht notwendigerweise transitiv in Python. Jedem ist es erlaubt, seine eigene, beliebige `__subclasscheck__` in einer Metaklasse zu definieren.
+* Wenn `issubclass(cls, Hashable)` aufgerufen wird, sucht es einfach nach nicht-Falsey "`__hash__`" Methoden in `cls` oderallem, von dem es erbt.
+* Da `object` hashable ist, aber `list` nicht-hashable, bricht es die transitive Relation.
+* Eine ausführlichere Erklärung kann [hier](https://www.naftaliharris.com/blog/python-subclass-intransitivity/) gefunden werden.
+
+---
+
+### ▶ Methodengleichheit und -identität
+
+
+1.
+```py
+class SomeClass:
+ def method(self):
+ pass
+
+ @classmethod
+ def classm(cls):
+ pass
+
+ @staticmethod
+ def staticm():
+ pass
+```
+
+**Ausgabe:**
+```py
+>>> print(SomeClass.method is SomeClass.method)
+True
+>>> print(SomeClass.classm is SomeClass.classm)
+False
+>>> print(SomeClass.classm == SomeClass.classm)
+True
+>>> print(SomeClass.staticm is SomeClass.staticm)
+True
+```
+
+Wenn wir zweimal auf `classm` zugreifen, bekommen wir ein gleiches Objekt, aber nicht *dasselbe* oder? Lass uns sehen, was mit Instanzen von `SomeClass` passiert:
+
+2.
+```py
+o1 = SomeClass()
+o2 = SomeClass()
+```
+
+**Ausgabe:**
+```py
+>>> print(o1.method == o2.method)
+False
+>>> print(o1.method == o1.method)
+True
+>>> print(o1.method is o1.method)
+False
+>>> print(o1.classm is o1.classm)
+False
+>>> print(o1.classm == o1.classm == o2.classm == SomeClass.classm)
+True
+>>> print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm)
+True
+```
+
+Der zweifache Zugriff auf `classm` oder `method`, erzeugt gleiche, aber nicht *gleiche* Objekte für dieselbe Instanz von `SomeClass`.
+
+#### 💡 Erklärung
+* Funktionen sind [Deskriptoren](https://docs.python.org/3/howto/descriptor.html). Wann immer auf eine Funktion als Attribut zugegriffen wird, wird der Deskriptor aufgerufen, was ein Methodenobjekt erzeugt, das die Funktion mit dem Objekt "verbindet", welches das Attribut besitzt. Wenn aufgerufen, ruft die Methode die Funktion auf und übergibt implizit das gebundene Objekt als erstes Argument (so erhalten wir `self` als erstes Argument, obwohl es nicht explizit übergeben wird).
+```py
+>>> o1.method
+>
+```
+* Ein mehrfacher Zugriff auf das Attribut erzeugt jedes Mal ein Methodenobjekt! Daher ist `o1.method is o1.method` niemals wahr.
+Der Zugriff auf Klassenattribute (im Gegensatz zu Instanzen) erzeugt jedoch keine Methoden; also ist
+`SomeClass.method is SomeClass.method` wahr.
+```py
+>>> SomeClass.method
+
+```
+* `classmethod` (Klassenmethoden) transformiert Funktionen in Klassenmethoden. Klassenmethoden sind Deskriptoren, die, wenn auf sie zugegriffen wird, ein Methodenobjekt erzeugen, welches die *Klasse* (Typ) des Objektes bindet, anstelle des Objektes selbst.
+```py
+>>> o1.classm
+>
+```
+* Im Gegensatz zu Funktionen erzeugen Klassenmethoden auch dann eine Methode, wenn sie als Klassenattribute aufgerufen werden (in diesem Fall binden sie die Klasse, nicht den Typ der Klasse). Also ist `SomeClass.classm is SomeClass.classm` unwahr.
+```py
+>>> SomeClass.classm
+>
+```
+* Ein Methodenobjekt ist gleich wenn beide Funktionen gleich sind, und die gebundenen Objekte gleich sind. Also ist `o1.method == o1.method` wahr, auch wenn sie nicht das gleiche Objekt im Speicher sind.
+* `staticmethod` transformiert Funktionen in ein "no-op" Deskriptor, welches die Funktion so zurückgibt, wie sie ist. Es werden nie Methodenobjekte erzeugt, also ist der Vergleich mit `is` wahr.
+```py
+>>> o1.staticm
+
+>>> SomeClass.staticm
+
+```
+* Jedes Mal wenn Python Instanzmethoden aufruft, müssen neue "Methoden"-Objekte erstellt und die Argumente geändert werden, um `self` einfügen zu können, was die Leistung negativ beeinflusst.
+CPython 3.7 [löste dies](https://bugs.python.org/issue26110), indem neue Opcodes eingeführt wurden, die den Aufruf von Methoden behandeln, ohne die temporären Methodenobjekte zu erzeugen. Das wird nur genutzt, wenn die Funktion, auf die zugegriffen wird, tatsächlich aufgerufen wird, also sind die Schnipsel hier nicht betroffen. Sie erzeugen also immer noch Methoden :)
+
+### ▶ All-true-ation *
+
+
+
+```py
+>>> all([True, True, True])
+True
+>>> all([True, True, False])
+False
+
+>>> all([])
+True
+>>> all([[]])
+False
+>>> all([[[]]])
+True
+```
+
+Warum ist diese Änderung True/False ?
+
+#### 💡 Erklärung:
+
+- Die Implementierung der `all` Funktion ist äquivalent zu
+
+- ```py
+ def all(iterable):
+ for element in iterable:
+ if not element:
+ return False
+ return True
+ ```
+
+- `all([])` gibt `True` zurück, da das Iterable leer ist.
+- `all([[]])` gibt `False` zurück, weil das übergebene Array ein Element hat, `[]`, und in Python, eine leere Liste `False` ist.
+- `all([[[]]])` und höhere, rekursive Varianten sind immer `True`, weil das einzelne Element des übergebenen Arrays (`[[...]]`) nicht länger leer ist, und Listen mit Werten `True` sind.
+
+---
+
+### ▶ Das überraschende Komma
+
+**Ausgabe (< 3.6):**
+
+```py
+>>> def f(x, y,):
+... print(x, y)
+...
+>>> def g(x=4, y=5,):
+... print(x, y)
+...
+>>> def h(x, **kwargs,):
+ File "", line 1
+ def h(x, **kwargs,):
+ ^
+SyntaxError: invalid syntax
+
+>>> def h(*args,):
+ File "", line 1
+ def h(*args,):
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Erklärung:
+
+- Ein Komma am Ende ist in der Liste der formalen Parameter einer Python-Funktion ist nicht immer zulässig.
+- In Python wird die Liste der Argumente teilweise mit führenden und teilweise mit abschließenden Kommas definiert.
+Dieser Konflikt führt zu Situationen, in denen ein Komma in der Mitte gefangen ist, und keine Regel dies akzeptiert.
+- **Hinweis:** Das abschließende-Komma-Problem wurde [in Python 3.6](https://bugs.python.org/issue9232) gelöst. Die Bemerkungen in [diesem](https://bugs.python.org/issue9232#msg248399) Post diskutieren in Kürze die verschiedenen Verwendungen von abschließenden Kommas in Python.
+
+---
+
+### ▶ Strings und die Backslashes
+
+**Ausgabe:**
+```py
+>>> print("\"")
+"
+
+>>> print(r"\"")
+\"
+
+>>> print(r"\")
+File "", line 1
+ print(r"\")
+ ^
+SyntaxError: EOL while scanning string literal
+
+>>> r'\'' == "\\'"
+True
+```
+
+#### 💡 Erklärung
+
+- In einem normalen Python-String wird der Backslash zum Escapen von Charactern benutzt, die evtl. eine besondere Bedeutung haben (bspw. einfache Anführungszeichen, doppelte Anführungszeichen und der Backslash selbst).
+ ```py
+ >>> "wt\"f"
+ 'wt"f'
+ ```
+- In einem raw-String-Literal (wie durch das Präfix `r` gekennzeichnet), werden die Backslashes so wie sie sind weitergegeben, zusammen mit den Verhalten, dass die folgenden Zeichen escaped werden.
+ ```py
+ >>> r'wt\"f' == 'wt\\"f'
+ True
+ >>> print(repr(r'wt\"f')
+ 'wt\\"f'
+
+ >>> print("\n")
+
+ >>> print(r"\\n")
+ '\\n'
+ ```
+- Das heißt wenn ein Parser auf ein Backslash in einem raw-String trifft, dann erwartet es noch einen Character, der diesem Backslash folgt. Und in unserem Fall (`print(r"\")`) escaped der Backslash das abschließende Anführungszeichen, wodurch der Parser kein terminierendes Anführungszeichen bekommt (daher der `SyntaxError`). Deshalb funktionieren Backslashes am Ende eines raw-Strings nicht.
+
+---
+
+### ▶ not knot!
+
+```py
+x = True
+y = False
+```
+
+**Ausgabe:**
+```py
+>>> not x == y
+True
+>>> x == not y
+ File "", line 1
+ x == not y
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Erklärung:
+
+* Operator-Reihenfolge beeinflusst wie ein Ausdruck ausgewertet wird, und der `==` Operator hat eine höhere Priorität als der `not`-Operator in Python.
+* Also ist `not x == y` äquivalent zu `not (x == y)`, was äquivalent zu `not (True == False)` ist und schließlich zu `True` ausgewertet wird.
+* Aber `x == not y` wirft einen `SyntaxError`, weil man es mit `(x == not) y` gleich setzen könnte und nicht `x == (not y)`, was man zuerst erwarten würde.
+* Der Parser erwartet das `not`-Token als Teil de `not in`-Operators (weil sowohl der `==`- als auch der `not in`-Operator die gleiche Priorität haben), aber nachdem er kein `in`-Token, welches nach einem `not`-Token folgt, gefunden hat, wirft er einen `SyntaxError`.
+
+---
+
+### ▶ Halbe Zeichenketten in dreifachen Anführungszeichen
+
+**Ausgabe:**
+```py
+>>> print('wtfpython''')
+wtfpython
+>>> print("wtfpython""")
+wtfpython
+>>> # Die folgende Anweisung wirft einen `SyntaxError`
+>>> # print('''wtfpython')
+>>> # print("""wtfpython")
+ File "", line 3
+ print("""wtfpython")
+ ^
+SyntaxError: EOF while scanning triple-quoted string literal
+```
+
+#### 💡 Erklärung:
++ Python unterstützt implizite [String-literal-Konkatenation](https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation), Beispiel:
+ ```
+ >>> print("wtf" "python")
+ wtfpython
+ >>> print("wtf" "") # or "wtf"""
+ wtf
+ ```
++ `'''` und `"""` sind auch String-Trennzeichen in Python, was einen SyntaxError hervorruft, weil der Python-Interpreter ein abschließendes dreifaches Anführungszeichen als Trennzeichen erwartet, während er das momentane String-Literal mit dreifachen Anführungszeichen scannt.
+
+---
+
+### ▶ Was ist falsch an booleans?
+
+1\.
+
+```py
+# Ein einfaches Beispiel, um die Anzahl der Booleans und
+# der Integer in einem Iterable mit gemischten Datentypen zu zählen.
+mixed_list = [False, 1.0, "some_string", 3, True, [], False]
+integers_found_so_far = 0
+booleans_found_so_far = 0
+
+for item in mixed_list:
+ if isinstance(item, int):
+ integers_found_so_far += 1
+ elif isinstance(item, bool):
+ booleans_found_so_far += 1
+```
+
+**Ausgabe:**
+```py
+>>> integers_found_so_far
+4
+>>> booleans_found_so_far
+0
+```
+
+
+2\.
+```py
+>>> some_bool = True
+>>> "wtf" * some_bool
+'wtf'
+>>> some_bool = False
+>>> "wtf" * some_bool
+''
+```
+
+3\.
+
+```py
+def tell_truth():
+ True = False
+ if True == False:
+ print("I have lost faith in truth!")
+```
+
+**Ausgabe (< 3.x):**
+
+```py
+>>> tell_truth()
+I have lost faith in truth!
+```
+
+
+
+#### 💡 Erklärung:
+
+* `bool` ist eine Unterklasse von `int` in Python
+
+ ```py
+ >>> issubclass(bool, int)
+ True
+ >>> issubclass(int, bool)
+ False
+ ```
+
+* Zudem sind `True` und `False` Instanzen von `int`
+ ```py
+ >>> isinstance(True, int)
+ True
+ >>> isinstance(False, int)
+ True
+ ```
+
+* Der Integer Wert von `True` ist `1` und der von `False` ist `0`.
+ ```py
+ >>> int(True)
+ 1
+ >>> int(False)
+ 0
+ ```
+
+* Siehe auch diese StackOverflow [Antwort](https://stackoverflow.com/a/8169049/4354153) für die Begründung dahinter.
+* Anfänglich hatte Python keinen `bool` Typ (Leute benutzten 0 für False und nicht-null Werte wie 1 für True). `True`, `False`, und ein `bool` Typ wurden in den 2.x Versionen hinzugefügt, aber, für Rückwärtskompatibilität, konnten `True` und `False` nicht zu Konstanten gemacht werden. Sie waren nur built-in Variablen, was es möglich machte, sie neu zuzuweisen.
+* Python 3 war rückwärtskompatibel, das Problem wurde schließlich gelöst, und daher wird der letzte Schnipsel nicht mit Python 3.x funktionieren!
+
+---
+
+### ▶ Klassen- und Instanzattribute
+
+1\.
+```py
+class A:
+ x = 1
+
+class B(A):
+ pass
+
+class C(A):
+ pass
+```
+
+**Ausgabe:**
+```py
+>>> A.x, B.x, C.x
+(1, 1, 1)
+>>> B.x = 2
+>>> A.x, B.x, C.x
+(1, 2, 1)
+>>> A.x = 3
+>>> A.x, B.x, C.x # C.x wurde verändert, aber B.x nicht
+(3, 2, 3)
+>>> a = A()
+>>> a.x, A.x
+(3, 3)
+>>> a.x += 1
+>>> a.x, A.x
+(4, 3)
+```
+
+2\.
+```py
+class SomeClass:
+ some_var = 15
+ some_list = [5]
+ another_list = [5]
+ def __init__(self, x):
+ self.some_var = x + 1
+ self.some_list = self.some_list + [x]
+ self.another_list += [x]
+```
+
+**Ausgabe:**
+
+```py
+>>> some_obj = SomeClass(420)
+>>> some_obj.some_list
+[5, 420]
+>>> some_obj.another_list
+[5, 420]
+>>> another_obj = SomeClass(111)
+>>> another_obj.some_list
+[5, 111]
+>>> another_obj.another_list
+[5, 420, 111]
+>>> another_obj.another_list is SomeClass.another_list
+True
+>>> another_obj.another_list is some_obj.another_list
+True
+```
+
+#### 💡 Erklärung:
+
+* Klassenvariablen und Variablen in Klasseninstanzen werden intern als Dictionaries von einem Klassenobjekt behandelt. Wenn einen Variablenname nicht im Dictionary der momentanen Klasse gefunden wird, wird die Elternklasse durchsucht.
+* Der `+=` Operator modifiziert das veränderbare Objekt in-place ohne ein neues Objekt zu erzeugen. Also beeinflusst das Ändern eines Attributtes von einer Instanz die anderen Instanzen und die Klassenattribute.
+
+---
+
+### ▶ yielding None
+
+```py
+some_iterable = ('a', 'b')
+
+def some_func(val):
+ return "something"
+```
+
+**Ausgabe (<= 3.7.x):**
+
+```py
+>>> [x for x in some_iterable]
+['a', 'b']
+>>> [(yield x) for x in some_iterable]
+ at 0x7f70b0a4ad58>
+>>> list([(yield x) for x in some_iterable])
+['a', 'b']
+>>> list((yield x) for x in some_iterable)
+['a', None, 'b', None]
+>>> list(some_func((yield x)) for x in some_iterable)
+['a', 'something', 'b', 'something']
+```
+
+#### 💡 Erklärung:
+- Das ist ein Fehler in CPythons Handhabung von `yield` in Generatoren und Comprehensions.
+- Die Quelle und eine Erklärung können hier gefunden werden: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions
+- Der zugehörige Fehlerbericht: https://bugs.python.org/issue10544
+- Python 3.8+ erlaubt kein `yield` in List-Comprehensions und wird einen `SyntaxError` werfen.
+
+---
+
+
+### ▶ Yielding from... return! *
+
+1\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ yield from range(x)
+```
+
+**Ausgabe (> 3.3):**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+Wo ist das `"wtf"` hin? Liegt es an einem besonderen Effekt von `yield from`? Lass uns das bestätigen:
+
+2\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ for i in range(x):
+ yield i
+```
+
+**Ausgabe:**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+Das gleiche Ergebnis; hat also auch nicht funktioniert.
+
+#### 💡 Erklärung:
++ Von Python 3.3 aus wurde es möglich, die `return`-Anweisung mit Werten innerhalb eines Generators zu benutzen (Siehe [PEP380](https://www.python.org/dev/peps/pep-0380/)). Die [offiziellen docs](https://www.python.org/dev/peps/pep-0380/#enhancements-to-stopiteration) sagen:
+
+> "... `return expr` in einem Generator führt zu einem `StopIteration(expr)`, was beim Verlassen des Generators geworfen wird."
+
++ Im Fall von `some_func(3)` wird `StopIteration` am Beginn wegen der `return`-Anweisung geworfen. Die `StopIteration` Exception wird automatisch innerhalb des `list(...)`-Wrapppers und der `for`-Schleife abgefangen. Daher enden die beiden Schnipsel mit einer leeren Liste.
++ Um `["wtf"]` vom Generator `some_func` zu bekommen, müssen wir die `StopIteration` Exception auffangen:
+
+ ```py
+ try:
+ next(some_func(3))
+ except StopIteration as e:
+ some_string = e.value
+ ```
+
+ ```py
+ >>> some_string
+ ["wtf"]
+ ```
+
+---
+
+### ▶ Nan-Reflexivität *
+
+
+
+1\.
+
+```py
+a = float('inf')
+b = float('nan')
+c = float('-iNf') # Diese Strings sind case-insensitiv
+d = float('nan')
+```
+
+**Ausgabe:**
+
+```py
+>>> a
+inf
+>>> b
+nan
+>>> c
+-inf
+>>> float('some_other_string')
+ValueError: could not convert string to float: some_other_string
+>>> a == -c # inf==inf
+True
+>>> None == None # None == None
+True
+>>> b == d # but nan!=nan
+False
+>>> 50 / a
+0.0
+>>> a / a
+nan
+>>> 23 + b
+nan
+```
+
+2\.
+
+```py
+>>> x = float('nan')
+>>> y = x / x
+>>> y is y # Identität funktioniert
+True
+>>> y == y # Gleichheit von y schlägt fehl
+False
+>>> [y] == [y] # aber die Gleichheit für die Liste, die y enthält, gelingt
+True
+```
+
+
+
+#### 💡 Erklärung:
+
+- `'inf'` und `'nan'` sind spezielle Strings (case-insensitiv), die, wenn sie explizit zu `floats` getypcasted werden, benutzt werden, um die mathematische "Unendlichkeit" und "not a number" zu repräsentieren.
+
+- Da nach dem IEEE Standard ` NaN != NaN`, bircht die Befolgung dieser Regel die Reflexivitätsannahme eines Collection-Elements in Python, d.h. wenn `x` Teil einer Collection wie einer `list` ist, dann basieren die Implementierungen, wie zum Beispiel Vergleiche, auf der Annahme, dass `x == x`. Aufgrund dieser Annahme, wird die Identität zuerst verglichen (da dies schneller ist), während die beiden Elemente verglichen werden, und die Werte werden nur verglichen, wenn die Identitäten ungleich sind. Der folgende Schnipsel wird die Dinge klarer erscheinen lassen:
+
+ ```py
+ >>> x = float('nan')
+ >>> x == x, [x] == [x]
+ (False, True)
+ >>> y = float('nan')
+ >>> y == y, [y] == [y]
+ (False, True)
+ >>> x == y, [x] == [y]
+ (False, False)
+ ```
+
+ Da die Identitäten von `x` und `y` unterschiedlich sind, werden die Werte betrachtet, die ebenfalls unterschiedlich sind; deshalb gibt der Vergleich dieses Mal `False` zurück.
+
+- Interessant zu lesen: [Reflexivity, and other pillars of civilization](https://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/)
+
+---
+
+### ▶ Verändern des Unveränderlichen!
+
+
+
+Das sieht vielleicht trivial aus, wenn du weißt wie Referenzen in Python funktionieren.
+
+```py
+some_tuple = ("A", "tuple", "with", "values")
+another_tuple = ([1, 2], [3, 4], [5, 6])
+```
+
+**Ausgabe:**
+```py
+>>> some_tuple[2] = "change this"
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple[2].append(1000) #Das wirft keinen Fehler
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000])
+>>> another_tuple[2] += [99, 999]
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000, 99, 999])
+```
+
+Aber ich dachte Tupel sind unveränderlich...
+
+#### 💡 Erklärung:
+
+* Zitat von https://docs.python.org/3/reference/datamodel.html
+
+ > Unveränderliche Sequenzen
+ Ein Objekt eines unveränderlichen Sequenztypen kann nicht nach der Erzeugung verändert werden. (Wenn das Objekt Referenzen zu anderen Objekten enthält, können die anderen Objekte veränderlich und modifizierbar sein; jedoch kann die Collection von Objekten, die von einem unveränderlichen Objekt referenziert werden, nicht geändert werden.)
+
+* Der `+=` Operator verändert die Liste in-place. Die Element-Zuweisung funktioniert nicht, aber wenn die Exception auftritt, wurde das Element bereits an Ort und Stelle verändert.
+* Es gibt auch eine Erklärung im [offiziellen Python FAQ](https://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works).
+
+---
+
+### ▶ Die verschwindende Variable aus dem äußeren Gültigkeitsbereich
+
+
+```py
+e = 7
+try:
+ raise Exception()
+except Exception as e:
+ pass
+```
+
+**Ausgabe (Python 2.x):**
+```py
+>>> print(e)
+# gibt nichts aus
+```
+
+**Ausgabe (Python 3.x):**
+```py
+>>> print(e)
+NameError: name 'e' is not defined
+```
+
+#### 💡 Erklärung:
+
+* Quelle: https://docs.python.org/3/reference/compound_stmts.html#except
+
+ Wenn eine Exception mit dem `as`-Target zugewiesen wurde, wird sie am Ende der `except`-Klausel gelöscht. Das ist so, als ob
+
+ ```py
+ except E as N:
+ foo
+ ```
+
+ übersetzt wurde in
+
+ ```py
+ except E as N:
+ try:
+ foo
+ finally:
+ del N
+ ```
+
+ Das bedeutet, dass einer Exception ein anderer Name zugewiesen werden muss, wenn man es nach einer except-Klausel benutzen möchte. Exceptions werden gelöscht, weil sie mit dem angehängten Traceback einen Referenzzyklus mit dem SatckFrame bilden, der alle Locals in diesem Frame am Leben erhält, bis die nächste Garbage Collection stattfindet.
+
+* Die Klauseln besitzen keinen Scope in Python. Alles im Beispiel liegt im selben Scope, und die Variable `e` wurde entfernt, da die except-Klausel ausgeführt wurde. Das gleiche gilt nicht für Funktionen, die ihren eigenen inneren Scope haben. Das Beispiel unten erläutert dies:
+
+ ```py
+ def f(x):
+ del(x)
+ print(x)
+
+ x = 5
+ y = [5, 4, 3]
+ ```
+
+ **Ausgabe:**
+ ```py
+ >>> f(x)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> f(y)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> x
+ 5
+ >>> y
+ [5, 4, 3]
+ ```
+
+* In Python 2.x wird der Variable `e` eine `Exception()`-Instanz zugewiesen, wenn du also versuchst, dies auszugeben, wird gar nichts ausgegeben.
+
+ **Ausgabe (Python 2.x):**
+ ```py
+ >>> e
+ Exception()
+ >>> print e
+ # Nichts wird ausgegeben!
+ ```
+
+---
+
+
+### ▶ Die mysteriöse key type Umwandlung
+
+```py
+class SomeClass(str):
+ pass
+
+some_dict = {'s': 42}
+```
+
+**Ausgabe:**
+```py
+>>> type(list(some_dict.keys())[0])
+str
+>>> s = SomeClass('s')
+>>> some_dict[s] = 40
+>>> some_dict # erwartet: zwei verschiedene key-value-Paare
+{'s': 40}
+>>> type(list(some_dict.keys())[0])
+str
+```
+
+#### 💡 Erklärung:
+
+* Das Objekt `s` und der String `"s"` hashen auf denselben Wert, weil `SomeClass` erbt die `__hash__`-Methode von der `str`-Klasse.
+* `SomeClass("s") == "s"` wird zu `True` ausgewertet, weil `SomeClass` auch die `__eq__`-Methode von der `str`-Klasse erbt.
+* Da beide Objekte auf denselben Wert hashen und gleich sind, werden sie durch denselben Key im Dictionary repräsentiert.
+* Für das gewünschte Verhalten, können wir die `__eq__`-Methode in `SomeClass` neu definieren:
+ ```py
+ class SomeClass(str):
+ def __eq__(self, other):
+ return (
+ type(self) is SomeClass
+ and type(other) is SomeClass
+ and super().__eq__(other)
+ )
+
+ # Wenn wir ein benutzerdefiniertes __eq__ definieren, stoppt Python automatisch die Vererbung der
+ # __hash__ Methode, also müssen wir diese auch noch definieren
+ __hash__ = str.__hash__
+
+ some_dict = {'s':42}
+ ```
+
+ **Ausgabe:**
+ ```py
+ >>> s = SomeClass('s')
+ >>> some_dict[s] = 40
+ >>> some_dict
+ {'s': 40, 's': 42}
+ >>> keys = list(some_dict.keys())
+ >>> type(keys[0]), type(keys[1])
+ (__main__.SomeClass, str)
+ ```
+
+---
+
+### ▶ Lass uns sehen, ob du dies errätst?
+
+```py
+a, b = a[b] = {}, 5
+```
+
+**Ausgabe:**
+```py
+>>> a
+{5: ({...}, 5)}
+```
+
+#### 💡 Erklärung:
+
+* Nach der [Python-Sprachreferenz](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements) haben Zuweisungsanweisungen die Form
+ ```
+ (target_list "=")+ (expression_list | yield_expression)
+ ```
+ und
+
+> Eine Zuweisungsanweisung wertet eine Liste von Ausdrücken aus (denk daran, dass dies ein einzelner Ausdruck oder eine durch Komma getrennte Liste sein kann, wobei letzteres ein Tupel ergibt) und weist dem einzelnen resultierenden Objekt jeder der Ziellisten zu, von links nach rechts.
+
+* Das `+` in `(target_list "=")+` meint, dass es **eine oder mehrere** Ziellisten geben kann. In diesem Fall sind die Ziellisten `a, b` und `a[b]` (beachte, dass die Liste von Ausdrücken exakt eine ist, was in unserem Fall `{}, 5` ist).
+
+* Nachdem die Liste der Ausdrücke ausgewertet wurde, wird ihr Wert von **links nach rechts** in die Ziellisten entpackt. Also wird, in unserem Fall, zuerst das Tupel `{}, 5` zu `a, b` entpackt und wir haben nun `a = {}` und `b = 5`.
+
+* `a` wird nun `{}` zugewiesen, was ein veränderliches Objekt ist.
+
+* Die zweite Zielliste ist `a[b]` (vielleicht hättest du erwartet, dass dies einen Fehler wirft, da sowohl `a` als auch `b` nicht in der Anweisung vorher definiert wurden. Aber denk daran, dass wir gerade `a` dem `{}` und `b` der `5` zugewiesen haben).
+
+* Jetzt setzen wir den Schlüssel `5` im Dictionary auf das Tupel `({}, 5)`, was einen Zirkelschluss erzeugt (das `{...}` in der Ausgabe bezieht sich auf dasselbe Objekt, was `a` bereits referenziert). Ein weiteres, einfacheres Beispiel einer zirkulären Referenz:
+ ```py
+ >>> some_list = some_list[0] = [0]
+ >>> some_list
+ [[...]]
+ >>> some_list[0]
+ [[...]]
+ >>> some_list is some_list[0]
+ True
+ >>> some_list[0][0][0][0][0][0] == some_list
+ True
+ ```
+ Ähnlich verhält es sich in unserem Beispiel (`a[b][0]` ist dasselbe Objekt wie `a`)
+
+* Um zusammenzufassen, kannst du das Beispiel wie folgt aufgliedern
+ ```py
+ a, b = {}, 5
+ a[b] = a, b
+ ```
+ Und der zirkuläre Bezug lässt sich durch die Tatsache rechtfertigen, dass `a[b][0]` dasselbe Objekt ist wie `a`
+ ```py
+ >>> a[b][0] is a
+ True
+ ```
+
+
+---
+
+### ▶ Überschreitet den Grenzwert für die Umwandlung von Integer-Strings
+```py
+>>> # Python 3.10.6
+>>> int("2" * 5432)
+
+>>> # Python 3.10.8
+>>> int("2" * 5432)
+```
+
+**Ausgabe:**
+```py
+>>> # Python 3.10.6
+222222222222222222222222222222222222222222222222222222222222222...
+
+>>> # Python 3.10.8
+Traceback (most recent call last):
+ ...
+ValueError: Exceeds the limit (4300) for integer string conversion:
+ value has 5432 digits; use sys.set_int_max_str_digits()
+ to increase the limit.
+```
+
+#### 💡 Erklärung:
+Die Aufforderung `int()` funktioniert gut in Python 3.10.6 und gibt einen ValueError in Python 3.10.8 aus. Beachte, dass Python auch mit großen ganzen Zahlen arbeiten kann. Der Fehler tritt nur auf, wenn du zwischen Integern und Strings konvertiert wird.
+
+Glücklicherweise kannst du den Grenzwert für die zulässige Anzahl von Ziffern erhöhen, wenn du erwartest, dass ein Vorgang diesen Grenzwert überschreitet. Um das zu tun, kannst du folgendes benutzen:
+- Das -X int_max_str_digits command-line flag
+- Die set_int_max_str_digits() Funktion vom sys-modul
+- Die PYTHONINTMAXSTRDIGITS Umgebungsvariable
+
+[Check die Dokumentation](https://docs.python.org/3/library/stdtypes.html#int-max-str-digits) für mehr Details
+über das Verändern des Default-Limits, wenn du erwartest, dass dein Code diesen Wert übersteigt.
+
+---
+
+
+## Kapitel: Slippery Slopes
+
+### ▶ Modifizieren eines Dictionarys während einer Iteration
+
+```py
+x = {0: None}
+
+for i in x:
+ del x[i]
+ x[i+1] = None
+ print(i)
+```
+
+**Ausgabe (Python 2.7- Python 3.5):**
+
+```
+0
+1
+2
+3
+4
+5
+6
+7
+```
+
+Ja, es läuft exakt **acht** mal und stoppt dann.
+
+#### 💡 Erklärung:
+
+* Iteration über ein Dictionary, welches du zur selben Zeit modifizierst, wird nicht unterstützt.
+* Es läuft acht Mal, weil sich die Größe des Dictionary an diesem Punkt ändert, um mehr Schlüssel beherbergen zu können (wir haben acht Löscheinträge, daher ist eine Größenänderung nötig). Das ist tatsächlich ein Implementierungsdetail.
+* Wie gelöschte Schlüssel gehandhabt werden und wann eine Größenänderung erfolgt, kann sich je nach Python-Implementierung unterscheiden.
+* Daher mag sich die Anzahl für andere Python-Versionen, als für 2.7 - 3.5, von 8 unterscheiden (aber wie die Anzahl auch sein mag, sie wird bei jedem Programmdurchlauf gleich bleiben). Du kannst ein paar Diskussionen rund um das Thema [hier](https://github.com/satwikkansal/wtfpython/issues/53) oder in [diesem](https://stackoverflow.com/questions/44763802/bug-in-python-dict) StackOverflow thread finden.
+* Ab Python 3.7.6 wirst du eine `RuntimeError: dictionary keys changed during iteration` Exception sehen, wenn du so etwas versuchst.
+
+---
+
+### ▶ Hartnäckige `del` Operation
+
+
+
+```py
+class SomeClass:
+ def __del__(self):
+ print("Deleted!")
+```
+
+**Ausgabe:**
+1\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x # das sollte "Deleted!" ausgeben
+>>> del y
+Deleted!
+```
+
+Endlich wird deleted ausgegeben. Vielleicht erahnst du schon schon, warum `__del__` nicht schon bei unserem ersten Versuch, `x` zu löschen, aufgerufen wurde. Ergänzen wir das Beispiel um weitere Aspekte
+
+2\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x
+>>> y # check, ob y existiert
+<__main__.SomeClass instance at 0x7f98a1a67fc8>
+>>> del y # Wie vorher sollte das "Deleted!" ausgeben
+>>> globals() # oh, das hat es nicht. Lass uns alle globalen Variablen checken und das bestätigen
+Deleted!
+{'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
+```
+
+Okay, jetzt ist es gelöscht :confused:
+
+#### 💡 Erklärung:
++ `del x` ruft nicht direkt `x.__del__()` auf.
++ Wenn auf `del x` gestoßen wird, dann löscht Python den Namen `x` vom momentanen Scope und dekrementiert den Referenz-Counter des Objektes, welches `x` referenziert, um 1. `__del__()` wird nur aufgerufen, wenn der Referenz-Counter des Objektes 0 erreicht.
++ Im zweiten Ausgabe-Schnipsel wurde `__del__()` nicht aufgerufen, weil die vorherige Anweisung (`>>> y`) im interaktiven Interpreter eine neue Referenz zum selben Objekt erzeugt hat (spezifisch die magische Variable `_`, welche das Ergebnis des letzten, nicht-`None` Ausdrucks der REPL referenziert), und daher den Referenz-Counter davon abhält, die 0 zu erreichen, wenn `del y` gelesen wurde.
++ Mit dem Aufruf von `globals` (oder irgendetwas, dass kein Ergebnis hat, dass `None` ist) wurde `_` angewiesen, dass neue Ergebnis zu referenzieren, wodurch die bestehende Referenz fallen gelassen wurde. Nun hat der Referenz-Counter 0 erreicht und wir können sehen, dass "Deleted!" ausgegeben wurde (Endlich!)
+
+---
+
+### ▶ Die Variable aus dem äußeren Geltungsbereich
+
+
+1\.
+```py
+a = 1
+def some_func():
+ return a
+
+def another_func():
+ a += 1
+ return a
+```
+
+2\.
+```py
+def some_closure_func():
+ a = 1
+ def some_inner_func():
+ return a
+ return some_inner_func()
+
+def another_closure_func():
+ a = 1
+ def another_inner_func():
+ a += 1
+ return a
+ return another_inner_func()
+```
+
+**Ausgabe:**
+```py
+>>> some_func()
+1
+>>> another_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+
+>>> some_closure_func()
+1
+>>> another_closure_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+```
+
+#### 💡 Erklärung:
+* Wenn du eine einer Variablen in einem Scope etwas zuweist, dann wird das für diesen Scope lokal. Also wird `a` lokal für den Scope von `another_func`, aber es wurde vorher nicht im selben Scope initialisiert, was einen Fehler wirft.
+* Um die Outer-Scope-Variable `a` in `another_func` zu modifizieren, müssen wir das `global` Schlüsselwort verwenden.
+ ```py
+ def another_func()
+ global a
+ a += 1
+ return a
+ ```
+
+ **Ausgabe:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+* In `another_closure_func` wird `a` für den Scope von `another_inner_func` lokal, aber es wurde vorher nicht im selben Scope initialisiert, was der Grund für den Fehler ist.
+* Um die Outer-Scope-Variable `a` in `another_inner_func` zu modifizieren, brauchen wir das `nonlocal` Schlüsselwort. Die nonlocal-Anweisung bezieht sich auf Variablen, die im nächstgelegenen äußeren Scope (globaler exkludiert) definiert wurden.
+ ```py
+ def another_func():
+ a = 1
+ def another_inner_func():
+ nonlocal a
+ a += 1
+ return a
+ return another_inner_func()
+ ```
+
+ **Ausgabe:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+* Die Schlüsselwörter `global` und `nonlocal` teilen dem Python Interpreter mit, keine neuen Variablen zu deklarieren und diese im entsprechenden äußeren Scope nachzuschlagen.
+* Lies [diesen](https://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html) kurzen, aber tollen Artikel durch, der ein super Leitfaden ist, um mehr über Namespaces und Scope-Auflösung in Python zu lernen.
+
+---
+
+### ▶ Löschen eines Listenelements während einer Iteration
+
+```py
+list_1 = [1, 2, 3, 4]
+list_2 = [1, 2, 3, 4]
+list_3 = [1, 2, 3, 4]
+list_4 = [1, 2, 3, 4]
+
+for idx, item in enumerate(list_1):
+ del item
+
+for idx, item in enumerate(list_2):
+ list_2.remove(item)
+
+for idx, item in enumerate(list_3[:]):
+ list_3.remove(item)
+
+for idx, item in enumerate(list_4):
+ list_4.pop(idx)
+```
+
+**Ausgabe:**
+```py
+>>> list_1
+[1, 2, 3, 4]
+>>> list_2
+[2, 4]
+>>> list_3
+[]
+>>> list_4
+[2, 4]
+```
+
+Kannst du erklären, warum die Ausgabe `[2, 4]` ist?
+
+#### 💡 Erklärung:
+
+* Es ist nie eine gute Idee ein Objekt zu verändern, worüber du gerade iterierst. Korrekt wäre es stattdessen über eine Kopie des Objektes zu iterieren, und `list_3[:]` tut genau das.
+
+ ```py
+ >>> some_list = [1, 2, 3, 4]
+ >>> id(some_list)
+ 139798789457608
+ >>> id(some_list[:]) # Beachte, dass Python ein neues Objekt für geslicte Listen baut.
+ 139798779601192
+ ```
+
+**Unterschied zwischen `del`, `remove`, und `pop`:**
+* `del var_name` entfernt nur die Bindung von `var_name` vom lokalen oder globalen Namespace (Deshalb ist `list_1` davon nicht betroffen).
+* `remove` entfernt den ersten Wert, der übereinstimmt, keinen spezifischen Index, und wirft einen `ValueError`, wenn der Wert nicht gefunden werden konnte.
+* `pop` entfernt ein Element am angegebenen Index und gibt dieses zurück und wirft einen `IndexError`, wenn ein falscher Index angegeben wurde.
+
+**Warum ist die Ausgabe `[2, 4]`?**
+- Die Listeniteration wird über den Index ausgeführt und wenn wir `1` von `list_2` oder `list_4` entfernen, ist der Inhalt der Listen nun `[2, 3, 4]`. Die verbliebenen Elemente werden heruntergeschoben, d.h.., `2` ist am Index 0, und `3` ist am Index 1. Da die nächste Iteration an Index 1 nachsehen wird (wo `3` steht), wird die `2` komplett übersprungen. Ähnliches wird anderen Element in einer Listensequenz passieren.
+
+* Ich verweise auf diesen StackOverflow [thread](https://stackoverflow.com/questions/45946228/what-happens-when-you-try-to-delete-a-list-element-while-iterating-over-it), welches das Beispiel erklärt
+* Siehe auch diesen schönen StackOverflow [thread](https://stackoverflow.com/questions/45877614/how-to-change-all-the-dictionary-keys-in-a-for-loop-with-d-items) für ein ähnliches Beispiel im Bezug auf Dictionaries in Python.
+
+---
+
+
+### ▶ Lossy Zips von Iteratoren *
+
+
+```py
+>>> numbers = list(range(7))
+>>> numbers
+[0, 1, 2, 3, 4, 5, 6]
+>>> first_three, remaining = numbers[:3], numbers[3:]
+>>> first_three, remaining
+([0, 1, 2], [3, 4, 5, 6])
+>>> numbers_iter = iter(numbers)
+>>> list(zip(numbers_iter, first_three))
+[(0, 0), (1, 1), (2, 2)]
+# so weit, so gut, lass uns den Rest zippen
+>>> list(zip(numbers_iter, remaining))
+[(4, 3), (5, 4), (6, 5)]
+```
+Wo ist das Element `3` von der Liste `numbers` hin?
+
+#### 💡 Erklärung:
+
+- Von der Python-[Dokumentation](https://docs.python.org/3.3/library/functions.html#zip), hier ist eine ungefähre Implementierung der zip-Funktion:
+ ```py
+ def zip(*iterables):
+ sentinel = object()
+ iterators = [iter(it) for it in iterables]
+ while iterators:
+ result = []
+ for it in iterators:
+ elem = next(it, sentinel)
+ if elem is sentinel: return
+ result.append(elem)
+ yield tuple(result)
+ ```
+- Die Funktion nimmt also beliebige Zahlen von Iterable-Objekten, fügt jedes ihrer Elemente der Liste `result` hinzu, indem es die Funktion `next` auf ihnen aufruft, und stoppt immer dann, wenn einer der Iterables aufgebraucht ist.
+- Die Einschränkung liegt hier darin, dass wenn ein Iterable aufgebraucht ist, die verbleibenden Elemente in der `result` Liste verworfen werden. Das ist mit der `3` in `numbers_iter` passiert.
+- Die korrekte Vorgehensweise mit `zip` für das obige sähe wie folgt aus:
+ ```py
+ >>> numbers = list(range(7))
+ >>> numbers_iter = iter(numbers)
+ >>> list(zip(first_three, numbers_iter))
+ [(0, 0), (1, 1), (2, 2)]
+ >>> list(zip(remaining, numbers_iter))
+ [(3, 3), (4, 4), (5, 5), (6, 6)]
+ ```
+ Das erste Argument von zip sollte das mit der geringsten Anzahl an Elementen sein.
+
+---
+
+### ▶ Schleifenvariablen, die auslaufen!
+
+1\.
+```py
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Ausgabe:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+Aber `x` wurde nie außerhalb des Scopes der for-Schleife definiert...
+
+2\.
+```py
+# Las uns dieses Mal x zuerst initialisieren
+x = -1
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Ausgabe:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+3\.
+
+**Ausgabe (Python 2.x):**
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+4
+```
+
+**Ausgabe (Python 3.x):**
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+1
+```
+
+#### 💡 Erklärung:
+
+- In Python benutzen for-Schleifen den Scope, in dem sie existieren und lassen ihre definierte Schleifenvariable zurück. Das passiert auch, wenn wir diese Variable vorher im globalen Namespace definieren. In diesem Fall wird die bestehende Variable neu gebunden.
+
+- Die Unterschiede in der Ausgabe des Python 2.x und Python 3.x Interpreters für List-Comprehensions können durch die folgende Änderung erklärt werden, welche im [What’s New In Python 3.0](https://docs.python.org/3/whatsnew/3.0.html) Changelog dokumentiert wurde:
+
+ > "List-Comprehensions unterstützen nicht länger die syntaktische Form `[... for var in item1, item2, ...]`. Benutze `[... for var in (item1, item2, ...)]` stattdessen. Beachte zudem, dass List-Comprehensions eine andere Semantik haben: sie sind eher syntaktischer Zucker für einen Generator-Ausdruck innerhalb eines `list()`-Konstruktors und insbesondere werden die Steuervariablen der Schleife nicht mehr in den umgebenden Scope geleakt."
+
+---
+
+### ▶ Vorsicht vor standardmäßig veränderbaren Argumenten!
+
+
+```py
+def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+```
+
+**Ausgabe:**
+```py
+>>> some_func()
+['some_string']
+>>> some_func()
+['some_string', 'some_string']
+>>> some_func([])
+['some_string']
+>>> some_func()
+['some_string', 'some_string', 'some_string']
+```
+
+#### 💡 Erklärung:
+
+- Die vorgegebenen veränderbaren Argumente von Funktionen in Python werden nicht wirklich jedes Mal, wenn du die Funktion aufrufst, initialisiert. Stattdessen wird der zuletzt zugewiesene Wert als default genommen. Als wir explizit `[]` an `some_func` als Argument übergeben haben, dann wurde der default-Wert der `default_arg`-Variable nicht benutzt, also hat die Funktion das zurückgegeben, was wir erwartet hatten.
+
+ ```py
+ def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+ **Ausgabe:**
+ ```py
+ >>> some_func.__defaults__ #Das zeigt die default-Werte der Argumente für die Funktion
+ ([],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string'],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ >>> some_func([])
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ ```
+
+- Eine typische Vorgehensweise um Fehler, ausgelöst durch veränderbare Argumente, zu verhindern ist es, `None` dem default-Wert zuzuweisen und später zu überprüfen, ob irgendein Wert an die Funktion übergeben wird, der diesem Argument entspricht. Beispiel:
+
+ ```py
+ def some_func(default_arg=None):
+ if default_arg is None:
+ default_arg = []
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+---
+
+### ▶ Fangen der Exceptions
+
+```py
+some_list = [1, 2, 3]
+try:
+ # Das sollte einen ``IndexError`` werfen
+ print(some_list[4])
+except IndexError, ValueError:
+ print("Caught!")
+
+try:
+ # Das sollte einen ``ValueError`` werfen
+ some_list.remove(4)
+except IndexError, ValueError:
+ print("Caught again!")
+```
+
+**Ausgabe (Python 2.x):**
+```py
+Caught!
+
+ValueError: list.remove(x): x not in list
+```
+
+**Ausgabe (Python 3.x):**
+```py
+ File "", line 3
+ except IndexError, ValueError:
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Erklärung
+
+* Um mehrere Exceptions der except-Klausel hinzuzufügen, musst du diese als Tupel mit Klammern und als erstes Argument übergeben. Das zweite Argument ist ein optionaler Name, der, wenn angegeben, die Exception-Instanz bindet, die geworfen wurde. Beispiel:
+
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ # Das sollte einen ``ValueError`` werfen
+ some_list.remove(4)
+ except (IndexError, ValueError), e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Ausgabe (Python 2.x):**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+ **Ausgabe (Python 3.x):**
+ ```py
+ File "", line 4
+ except (IndexError, ValueError), e:
+ ^
+ IndentationError: unindent does not match any outer indentation level
+ ```
+
+* Die Exceptions und die Variable mit einem Komma zu trennen ist veraltet und funktioniert in Python 3 nicht mehr; der korrekte Weg wäre `as` zu benutzen. Beispiel:
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ some_list.remove(4)
+
+ except (IndexError, ValueError) as e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Ausgabe:**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+
+---
+
+### ▶ Gleiche Operanden, unterschiedliche Story!
+
+1\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a = a + [5, 6, 7, 8]
+```
+
+**Ausgabe:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4]
+```
+
+2\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a += [5, 6, 7, 8]
+```
+
+**Ausgabe:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4, 5, 6, 7, 8]
+```
+
+#### 💡 Erklärung:
+
+* `a += b` verhält sich nicht immer wie `a = a + b`. Klassen *können* die *`op=`* Operatoren unterschiedlich implementieren und Listen tun das.
+
+* Der Ausdruck `a = a + [5,6,7,8]` generiert eine neue Liste und setzt `a`s Referenz auf diese neue Liste, wodurch `b` unverändert bleibt.
+
+* Der Ausdruck `a += [5,6,7,8]` wird tatsächlich zu einer "extend"-Funktion gemapped wird, die auf der Liste arbeitet, sodass `a` und `b` immer noch auf dieselbe Liste zeigen, die in-place modifiziert wurde.
+
+---
+
+### ▶ Namensauflösung ohne Berücksichtigung des Geltungsbereichs der Klasse
+
+1\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = (x for i in range(10))
+```
+
+**Ausgabe:**
+```py
+>>> list(SomeClass.y)[0]
+5
+```
+
+2\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = [x for i in range(10)]
+```
+
+**Ausgabe (Python 2.x):**
+```py
+>>> SomeClass.y[0]
+17
+```
+
+**Ausgabe (Python 3.x):**
+```py
+>>> SomeClass.y[0]
+5
+```
+
+#### 💡 Erklärung
+
+- Innerhalb der Klassendefinition verschachtelte Scopes ignorieren die auf Klassenebene gebundenen Namen.
+- Ein Generator-Ausdruck hat seinen eigenen Scope.
+- Ausgehend von Python 3.X haben List Comprehensions auch ihren eigenen Scope.
+
+---
+
+### ▶ Runden wie ein Bankier *
+
+Lass uns eine naive Funktion implementieren, um das mittlere Element einer Liste zu bekommen:
+```py
+def get_middle(some_list):
+ mid_index = round(len(some_list) / 2)
+ return some_list[mid_index - 1]
+```
+
+**Python 3.x:**
+```py
+>>> get_middle([1]) # sieht gut aus
+1
+>>> get_middle([1,2,3]) # sieht gut aus
+2
+>>> get_middle([1,2,3,4,5]) # huh?
+2
+>>> len([1,2,3,4,5]) / 2 # gut
+2.5
+>>> round(len([1,2,3,4,5]) / 2) # Warum?
+2
+```
+Sieht so aus, als ob Python 2.5 zu 2 rundet.
+
+#### 💡 Erklärung:
+
+- Das ist kein Gleitkommafehler, denn faktisch ist dieses Verhalten Absicht. Seit Python 3.0 benutzt `round()` [banker's rounding](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even), wodurch 0,5er-Brüche auf die nächste **gerade** Zahl gerundet werden:
+
+```py
+>>> round(0.5)
+0
+>>> round(1.5)
+2
+>>> round(2.5)
+2
+>>> import numpy # numpy tut dasselbe
+>>> numpy.round(0.5)
+0.0
+>>> numpy.round(1.5)
+2.0
+>>> numpy.round(2.5)
+2.0
+```
+
+- Das ist der empfohlene Weg um 0,5er-Brüche zu runden, wie es auch in [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules) beschrieben wurde. Allerdings wird in der Schule meist die andere Methode (von Null abrunden) gelehrt, so dass banker's rounding wahrscheinlich nicht so bekannt ist. Außerdem benutzen manche der populärsten Programmiersprachen (zum Beispiel: JavaScript, Java, C/C++, Ruby, Rust) nicht das banker's rounding. Daher ist das ziemlich besonders für Python und kann zu Verwirrung beim runden von Brüchen führen.
+- Sieh dir die [round() docs](https://docs.python.org/3/library/functions.html#round) oder [diesen stackoverflow thread](https://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior), um mehr Informationen zu bekommen.
+- Beachte, dass `get_middle([1])` nur 1 zurückgegeben hat, weil der Index `round(0.5) - 1 = 0 - 1 = -1` war, wodurch das letzte Element in der Liste zurückgibt.
+
+---
+
+### ▶ Nadeln im Heuhaufen *
+
+
+
+Ich habe bis heute keinen einzigen erfahrenen Pythonisten getroffen, der nicht auf eines oder mehrere der folgenden Szenarien gestoßen ist:
+
+1\.
+
+```py
+x, y = (0, 1) if True else None, None
+```
+
+**Ausgabe:**
+
+```py
+>>> x, y # erwartet: (0, 1)
+((0, 1), None)
+```
+
+2\.
+
+```py
+t = ('one', 'two')
+for i in t:
+ print(i)
+
+t = ('one')
+for i in t:
+ print(i)
+
+t = ()
+print(t)
+```
+
+**Ausgabe:**
+
+```py
+one
+two
+o
+n
+e
+tuple()
+```
+
+3\.
+
+```
+ten_words_list = [
+ "some",
+ "very",
+ "big",
+ "list",
+ "that"
+ "consists",
+ "of",
+ "exactly",
+ "ten",
+ "words"
+]
+```
+
+**Ausgabe**
+
+```py
+>>> len(ten_words_list)
+9
+```
+
+4\. Not asserting strongly enough
+
+```py
+a = "python"
+b = "javascript"
+```
+
+**Ausgabe:**
+
+```py
+# Eine assert-Anweisung mit einer assertion-Fehlermeldung.
+>>> assert(a == b, "Both languages are different")
+# Kein AssertionError wurde geworfen
+```
+
+5\.
+
+```py
+some_list = [1, 2, 3]
+some_dict = {
+ "key_1": 1,
+ "key_2": 2,
+ "key_3": 3
+}
+
+some_list = some_list.append(4)
+some_dict = some_dict.update({"key_4": 4})
+```
+
+**Ausgabe:**
+
+```py
+>>> print(some_list)
+None
+>>> print(some_dict)
+None
+```
+
+6\.
+
+```py
+def some_recursive_func(a):
+ if a[0] == 0:
+ return
+ a[0] -= 1
+ some_recursive_func(a)
+ return a
+
+def similar_recursive_func(a):
+ if a == 0:
+ return a
+ a -= 1
+ similar_recursive_func(a)
+ return a
+```
+
+**Ausgabe:**
+
+```py
+>>> some_recursive_func([5, 0])
+[0, 0]
+>>> similar_recursive_func(5)
+4
+```
+
+#### 💡 Erklärung:
+
+* Für 1 wäre die korrekte Anweisung für das erwartete Verhalten `x, y = (0, 1) if True else (None, None)`.
+
+* Für 2 wäre die korrekte Anweisung für das erwartete Verhalten `t = ('one',)` oder `t = 'one',` (fehlendes Komma) andernfalls wird der Interpreter `t` als ein `str` betrachten und iteriert Character für Character über es.
+
+* `()` ist ein spezielles Token und bezeichnet ein leeres `tuple`.
+
+* In 3, wie du vielleicht schon mitbekommen hast, fehlt ein Komma nach dem fünften Element (`"that"`) in der Liste. Also bei implizierter String-Literal-Konkatenation:
+
+ ```py
+ >>> ten_words_list
+ ['some', 'very', 'big', 'list', 'thatconsists', 'of', 'exactly', 'ten', 'words']
+ ```
+
+* Es wurde kein `AssertionError` im 4.ten Schnipsel geworfen, weil wir statt den individuellen Ausdruck `a == b` zu asserten, das ganze Tupel asserten. Der folgende Schnipsel wird die Dinge klarer werden lassen:
+
+ ```py
+ >>> a = "python"
+ >>> b = "javascript"
+ >>> assert a == b
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError
+
+ >>> assert (a == b, "Values are not equal")
+ :1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
+
+ >>> assert a == b, "Values are not equal"
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError: Values are not equal
+ ```
+
+* Was den fünften Schnipsel betrifft, so modifizieren die meisten Methoden, die die Elemente von Sequenzen/Mapping-Objekten modifizieren, wie `list.append`, `dict.update`, `list.sort`, die Objekte in-place und geben `None` zurück. Der Grund dafür ist eine Leistungsverbesserung, indem die Erstellung einer Kopie vermieden wird, wenn die Operation in-place ausgeführt werden kann (Verweis nach [hier](https://docs.python.org/3/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list)).
+
+* Das letzte sollte ziemlich offensichtlich sein. Ein veränderliches Objekt (wie `list`) kann in der Funktion geändert werden und die Neuzuweisung eines unveränderlichen Wertes (`a -= 1`) ist keine Änderung des WErtes
+
+* Wenn du dir dieser Kleinigkeiten bewusst bist, dann kannst du dir auf lange Sicht stundenlanges Debugging sparen.
+
+---
+
+
+### ▶ Splitsies *
+
+```py
+>>> 'a'.split()
+['a']
+
+# ist dasselbe wie
+>>> 'a'.split(' ')
+['a']
+
+# aber
+>>> len(''.split())
+0
+
+# ist nicht dasselbe wie
+>>> len(''.split(' '))
+1
+```
+
+#### 💡 Erklärung:
+
+- Auf den ersten Blick sieht es so aus, als wäre das Standardtrennzeichen für split ein einzelnes Leerzeichen `' '`, aber der [Dokumentation](https://docs.python.org/3/library/stdtypes.html#str.split) zufolge
+ > Wenn ein Separator nicht angegeben oder `None` ist, dann wird ein anderer Splitting-Algorithmus angewendet: aufeinanderfolgende Leerzeichen werden als ein einziges Trennzeichen betrachtet, und das Ergebnis enthält keine leeren Strings am Anfang oder Ende, wenn der String führende oder nachfolgende Leerzeichen enthält. Folglich gibt das Splitten eines leeren Strings oder eines Strings, der nur ein Leerzeichen enthält, mit einen None-Separator `[]` zurück.
+ > Wenn ein Separator gegeben ist,Aufeinanderfolgende Begrenzungszeichen werden nicht zusammengefasst und gelten als Begrenzung leerer Strings. (zum Beispiel, `'1,,2'.split(',')` gibt `['1', '', '2']` zurück). Einen leeren String mit einem angegebenen Separator zu splitten gibt `['']` zurück.
+- Wenn du dir ansiehst, wie die führenden und nachfolgenden Leerzeichen im folgenden Schnipsel gehandhabt werden, wird die Sache klarer:
+ ```py
+ >>> ' a '.split(' ')
+ ['', 'a', '']
+ >>> ' a '.split()
+ ['a']
+ >>> ''.split(' ')
+ ['']
+ ```
+
+---
+
+### ▶ Wilde Imports *
+
+
+
+```py
+# Datei: module.py
+
+def some_weird_name_func_():
+ print("works!")
+
+def _another_weird_name_func():
+ print("works!")
+
+```
+
+**Ausgabe**
+
+```py
+>>> from module import *
+>>> some_weird_name_func_()
+"works!"
+>>> _another_weird_name_func()
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name '_another_weird_name_func' is not defined
+```
+
+#### 💡 Erklärung:
+
+- Es ist oft ratsam, keine Wildcard-Importe zu verwenden. Der erste offensichtliche Grund dafür ist, dass bei Wildcard-Importen die Namen mit einem führenden Unterstrich nicht importiert werden. Das kann zu Fehlern während der Laufzeit führen.
+- Hätten wir diese `from ... import a, b, c` Syntax benutzt, wäre der obige `NameError` nicht aufgetreten.
+ ```py
+ >>> from module import some_weird_name_func_, _another_weird_name_func
+ >>> _another_weird_name_func()
+ works!
+ ```
+- Wenn du wirklich Wildcard-Importe verwenden willst, musst du in deinem Modul die Liste `__all__` definieren, die eine Liste der öffentlichen Objekte enthält, die bei Wildcard-Importen zur Verfügung stehen werden.
+ ```py
+ __all__ = ['_another_weird_name_func']
+
+ def some_weird_name_func_():
+ print("works!")
+
+ def _another_weird_name_func():
+ print("works!")
+ ```
+ **Ausgabe**
+
+ ```py
+ >>> _another_weird_name_func()
+ "works!"
+ >>> some_weird_name_func_()
+ Traceback (most recent call last):
+ File "", line 1, in
+ NameError: name 'some_weird_name_func_' is not defined
+ ```
+
+---
+
+### ▶ Alles sortieren ? *
+
+
+
+```py
+>>> x = 7, 8, 9
+>>> sorted(x) == x
+False
+>>> sorted(x) == sorted(x)
+True
+
+>>> y = reversed(x)
+>>> sorted(y) == sorted(y)
+False
+```
+
+#### 💡 Erklärung:
+
+- Die `sorted` Methode gibt immer eine Liste zurück, und das Vergleichen von Listen und Tupeln gibt in Python immer `False` zurück.
+
+- ```py
+ >>> [] == tuple()
+ False
+ >>> x = 7, 8, 9
+ >>> type(x), type(sorted(x))
+ (tuple, list)
+ ```
+
+- Im Gegensatz zu `sorted`, gibt die `reversed` Methode einen Iterator zurück. Warum? Weil Sortieren vorraussetzt, dass der Iterator entweder in-place modifiziert wird oder einen extra Container (eine Liste) benutzt, während die Umkehrung einfach durch Iteration vom letzten Index zum ersten funktionieren kann.
+
+- Also während des Vergleichs `sorted(y) == sorted(y)`, wird der erste Aufruf von `sorted()` den Iterator `y` konsumieren, und der nächste Aufruf wird nur eine leere Liste zurückgeben.
+
+ ```py
+ >>> x = 7, 8, 9
+ >>> y = reversed(x)
+ >>> sorted(y), sorted(y)
+ ([7, 8, 9], [])
+ ```
+
+---
+
+### ▶ Mitternachtszeit gibt es nicht ?
+
+```py
+from datetime import datetime
+
+midnight = datetime(2018, 1, 1, 0, 0)
+midnight_time = midnight.time()
+
+noon = datetime(2018, 1, 1, 12, 0)
+noon_time = noon.time()
+
+if midnight_time:
+ print("Time at midnight is", midnight_time)
+
+if noon_time:
+ print("Time at noon is", noon_time)
+```
+
+**Ausgabe (< 3.5):**
+
+```py
+('Time at noon is', datetime.time(12, 0))
+```
+The midnight time is not printed.
+
+#### 💡 Erklärung:
+
+Vor Python 3.5, wurde der boolesche Wert für das `datetime.time`-Objekt als `False` betrachtet, wenn wenn es Mitternacht in UTC dargestellt hätte. Es ist fehleranfällig, wenn die `if obj:`-Syntax verwendet wird, um zu prüfen, ob `obj` null oder ein Äquivalent von "leer" ist.
+
+---
+---
+
+
+
+## Kapitel: Die verborgenen Schätze!
+
+Dieser Abschnitt enthält ein paar weniger bekannte und interessante Dinge über Python, die den meisten Anfängern wie mir nicht bekannt sind (nun aber schon).
+
+### ▶ Okay Python, kannst du mich fliegen lassen?
+
+Nun hier wären wir:
+
+```py
+import antigravity
+```
+
+**Ausgabe:**
+Sshh... It's a super-secret.
+
+#### 💡 Erklärung:
++ Das `antigravity` Modul ist eines der wenigen easter eggs, die von Python Entwicklern veröffentlicht werden.
++ `import antigravity` öffnet deinen Webbrowser und zeigt zum [klassischen XKCD Comic](https://xkcd.com/353/).
++ Nun, es gibt noch mehr. Es gibt noch ein **easter egg im easter egg**. Wenn du dir den [code](https://github.com/python/cpython/blob/master/Lib/antigravity.py#L7-L17) anschaust, dann findest du eine Funktion, die die Ausführung des [XKCDs Geohashing Algorithmus](https://xkcd.com/426/) beabsichtigt.
+
+---
+
+### ▶ `goto`, aber wieso?
+
+
+```py
+from goto import goto, label
+for i in range(9):
+ for j in range(9):
+ for k in range(9):
+ print("I am trapped, please rescue!")
+ if k == 2:
+ goto .breakout # Ausbrechen aus einer tief verschachtelten Schleife
+label .breakout
+print("Freedom!")
+```
+
+**Ausgabe (Python 2.3):**
+```py
+I am trapped, please rescue!
+I am trapped, please rescue!
+Freedom!
+```
+
+#### 💡 Erklärung:
+- Eine funktionierende Version von `goto` in Python wurde als Aprilscherz am 1. April 2004 [angekündigt](https://mail.python.org/pipermail/python-announce-list/2004-April/002982.html).
+- Aktuelle Versionen von Python haben dieses Modul nicht.
+- Auch wenn es funktioniert, benutze es bitte nicht. Hier ist die [Antwort](https://docs.python.org/3/faq/design.html#why-is-there-no-goto), warum `goto` in Python nicht verwendet wird.
+
+---
+
+### ▶ Halte dich fest!
+
+Wenn du zu den Leuten gehörst, die keine Leerzeichen in Python verwenden wollen, um Bereiche zu kennzeichnen, kannst du den C-Stil {} verwenden, indem du folgendes importierst:
+
+```py
+from __future__ import braces
+```
+
+**Ausgabe:**
+```py
+ File "some_file.py", line 1
+ from __future__ import braces
+SyntaxError: not a chance
+```
+
+Klammern? Niemals! Wenn du enttäuscht bist, nutze Java. Okay, eine weitere überraschende Sache, kannst du herausfinden, wo `SyntaxError` im `__future__` Mmodul geworfen wird [code](https://github.com/python/cpython/blob/master/Lib/__future__.py)?
+
+#### 💡 Erklärung:
++ Das `__future__` Modul wird normalerweise benutzt, um Features von zukünftigen Python Versionen bereitzustellen. Das "future" is in diesem spezifischen Kontext ironisch gemeint.
++ Das ist ein easter egg die die Gefühle der Community in dieser Frage wiederspiegeln.
++ Der Code ist tatsächlich [hier](https://github.com/python/cpython/blob/025eb98dc0c1dc27404df6c544fc2944e0fa9f3a/Python/future.c#L49) in der Datei `future.c` verfügbar.
++ Wenn der CPython Compiler auf ein [future Statement](https://docs.python.org/3.3/reference/simple_stmts.html#future-statements) trifft, wird zunächst der entsprechende Code in `future.c` ausgeführt, bevor es als normale Importanweisung behandelt wird.
+
+---
+
+### ▶ Let's meet Friendly Language Uncle For Life
+
+**Ausgabe (Python 3.x)**
+```py
+>>> from __future__ import barry_as_FLUFL
+>>> "Ruby" != "Python" # Hier bestehen keine Zweifel
+ File "some_file.py", line 1
+ "Ruby" != "Python"
+ ^
+SyntaxError: invalid syntax
+
+>>> "Ruby" <> "Python"
+True
+```
+
+Das wars schon.
+
+#### 💡 Erklärung:
+-Das ist relevant für [PEP-401](https://www.python.org/dev/peps/pep-0401/), was am 1. April 2009 veröffentlicht wurde (nun weißt du, was das heißt).
+- Zitat vom PEP-401
+
+ > Die FLUFL hat erkannt, dass der Ungleichheitsoperator != in Python 3.0 ein schrecklicher, fingerschmerzverursachender Fehler war, und führt den <> Diamantoperator als einzige Schreibweise wieder ein.
+- Es gab mehrere Dinge, die Onkel Barry im PEP teilte; du kannst das [hier](https://www.python.org/dev/peps/pep-0401/) nachlesen.
+- Es funktioniert gut in einem Interactive Environment, aber es wirft einen `SyntaxError`, wenn du es in einer Python-Datei ausführst (siehe auch dieses [issue](https://github.com/satwikkansal/wtfpython/issues/94)). Du kannst jedoch die Anweisung innerhalb einer `eval` oder `compile` verpacken, um es zum Laufen zu bekommen:
+ ```py
+ from __future__ import barry_as_FLUFL
+ print(eval('"Ruby" <> "Python"'))
+ ```
+
+---
+
+### ▶ Selbst Python versteht, dass Liebe kompliziert ist
+
+```py
+import this
+```
+
+Warte, was ist **this**? `this` ist love :heart:
+
+**Ausgabe:**
+```
+Der Zen von Python, von Tim Peters
+
+Beautiful is better than ugly.
+Explicit is better than implicit.
+Simple is better than complex.
+Complex is better than complicated.
+Flat is better than nested.
+Sparse is better than dense.
+Readability counts.
+Special cases aren't special enough to break the rules.
+Although practicality beats purity.
+Errors should never pass silently.
+Unless explicitly silenced.
+In the face of ambiguity, refuse the temptation to guess.
+There should be one-- and preferably only one --obvious way to do it.
+Although that way may not be obvious at first unless you're Dutch.
+Now is better than never.
+Although never is often better than *right* now.
+If the implementation is hard to explain, it's a bad idea.
+If the implementation is easy to explain, it may be a good idea.
+Namespaces are one honking great idea -- let's do more of those!
+```
+
+Das ist der Zen von Python!
+
+```py
+>>> love = this
+>>> this is love
+True
+>>> love is True
+False
+>>> love is False
+False
+>>> love is not True or False
+True
+>>> love is not True or False; love is love # Liebe ist kompliziert
+True
+```
+
+#### 💡 Erklärung:
+
+* Das `this` Modul in Python ist ein Easter Egg für den Zen von Python ([PEP 20](https://www.python.org/dev/peps/pep-0020)).
+* Und wenn du denkst, dass das schon interessant genug ist, sieh dir die Implementierung von [this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py) an. Interessanterweise **verstößt der Code für das Zen gegen sich selbst** (und das ist wahrscheinlich der einzige Ort, an dem dies geschieht).
+* Die Aussage `love is not True or False; love is love` ist ironisch, aber selbsterklärend (wenn nicht, sieh dir die Beispiele zu den `is` und `is not` Operatoren an).
+
+---
+
+### ▶ Ja, es existiert!
+
+**The `else` clause for loops.** Ein typisches Beispiel wäre:
+
+```py
+ def does_exists_num(l, to_find):
+ for num in l:
+ if num == to_find:
+ print("Exists!")
+ break
+ else:
+ print("Does not exist")
+```
+
+**Ausgabe:**
+```py
+>>> some_list = [1, 2, 3, 4, 5]
+>>> does_exists_num(some_list, 4)
+Exists!
+>>> does_exists_num(some_list, -1)
+Does not exist
+```
+
+**Die `else`-Klausel in der Behandlung von Exceptions.** Ein Beispiel:
+
+```py
+try:
+ pass
+except:
+ print("Exception occurred!!!")
+else:
+ print("Try block executed successfully...")
+```
+
+**Ausgabe:**
+```py
+Try block executed successfully...
+```
+
+#### 💡 Erklärung:
+- Die `else`-Klausel nach einer Schleife wird nur dann ausgeführt, wenn es kein explizites `break` nach allen Iterationen gibt. Du kannst es dir als eine "nobreak"-Klausel vorstellen.
+- Eine `else`-Klausel nach einem try-Block wird auch "completion clause" genannt, da wenn die `else`-Klausel in einer `try`-Anweisung erreicht wird, dies bedeutet, dass der try-Block tatsächlich komplett erfolgreich durchlief.
+
+---
+### ▶ Ellipsen *
+
+```py
+def some_func():
+ Ellipsis
+```
+
+**Ausgabe**
+```py
+>>> some_func()
+# Keine Ausgabe, Kein Fehler
+
+>>> SomeRandomString
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'SomeRandomString' is not defined
+
+>>> Ellipsis
+Ellipsis
+```
+
+#### 💡 Erklärung
+- In Python ist `Ellipsis` ein global verfügbares built-in Objekt, was äquivalent zu `...` ist.
+ ```py
+ >>> ...
+ Ellipsis
+ ```
+- Ellipsen können für verschiedene Dinge eingesetzt werden,
+ + Als ein Platzhalter für Code, der noch nicht geschrieben wurde (wie auch die `pass`-Anweisung)
+ + In Slicing-Syntax, um die vollen Slices in der übrigen Richtung zu repräsentieren
+ ```py
+ >>> import numpy as np
+ >>> three_dimensional_array = np.arange(8).reshape(2, 2, 2)
+ array([
+ [
+ [0, 1],
+ [2, 3]
+ ],
+
+ [
+ [4, 5],
+ [6, 7]
+ ]
+ ])
+ ```
+ Also ist unser `three_dimensional_array` ein Array von Arrays von Arrays. Angenommen, wir wollen das zweite Element (index `1`) aller innersten Arrays ausgeben, dann können wir Ellipsis verwenden, um alle vorhergehenden Dimensionen zu umgehen
+ ```py
+ >>> three_dimensional_array[:,:,1]
+ array([[1, 3],
+ [5, 7]])
+ >>> three_dimensional_array[..., 1] # Benutzen der Ellipsis.
+ array([[1, 3],
+ [5, 7]])
+ ```
+ Beachte: dies funktioniert für eine beliebige Anzahl von Dimensionen. Auf diese Weise kannst du sogar die erste und letzte Dimension auswählen und die mittleren ignorieren. (`n_dimensional_array[firs_dim_slice, ..., last_dim_slice]`)
+ + In [type hinting](https://docs.python.org/3/library/typing.html) um nur einen Teil des Typs anzugeben (wie `(Callable[..., int]` oder `Tuple[str, ...]`))
+ + Du kannst Ellipsis auch als default-Argument einer Funktion verwenden (in den Fällen, wo du zwischen den Szenarien "no argument passed" und "None value passed" unterscheiden willst).
+
+---
+
+### ▶ Einbindung
+
+Die Schreibweise ist beabsichtigt. Bitte schicke keinen Patch hierfür ab.
+
+**Ausgabe (Python 3.x):**
+```py
+>>> infinity = float('infinity')
+>>> hash(infinity)
+314159
+>>> hash(float('-inf'))
+-314159
+```
+
+#### 💡 Erklärung:
+- Der Hash von infinity ist 10⁵ x π.
+- Interessanterweise ist der Hash von `float('-inf')` "-10⁵ x π" in Python 3, während er in Python 2 "-10⁵ x e" ist.
+
+---
+
+### ▶ Lass uns demolieren
+
+1\.
+```py
+class Yo(object):
+ def __init__(self):
+ self.__honey = True
+ self.bro = True
+```
+
+**Ausgabe:**
+```py
+>>> Yo().bro
+True
+>>> Yo().__honey
+AttributeError: 'Yo' object has no attribute '__honey'
+>>> Yo()._Yo__honey
+True
+```
+
+2\.
+```py
+class Yo(object):
+ def __init__(self):
+ # Versuchen wir es dieses Mal mit etwas Symmetrischem
+ self.__honey__ = True
+ self.bro = True
+```
+
+**Ausgabe:**
+```py
+>>> Yo().bro
+True
+
+>>> Yo()._Yo__honey__
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'Yo' object has no attribute '_Yo__honey__'
+```
+
+Warum hat `Yo()._Yo__honey` funktioniert?
+
+3\.
+
+```py
+_A__variable = "Some value"
+
+class A(object):
+ def some_func(self):
+ return __variable # noch nirgends initialisiert
+```
+
+**Ausgabe:**
+```py
+>>> A().__variable
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'A' object has no attribute '__variable'
+
+>>> A().some_func()
+'Some value'
+```
+
+
+#### 💡 Erklärung:
+
+* [Name Mangling](https://en.wikipedia.org/wiki/Name_mangling) wird verwendet, um Namenskollisionen zwischen verschiedenen Namespaces zu vermeiden.
+* In Python modifiziert (lies: demoliert) der Interpreter die Namen der Klassenattribute, die mit `__` (doppelter Unterstrich a.k.a "dunder") anfangen und nicht mit mehr als einem Unterstrich am Ende enden, indem er `_NameOfTheClass` voranstellt.
+* Um also auf das `__honey`-Attribut im ersten Schnipsel zugreifen zu können, müssten wir `_Yo` vorne anhängen, was Konflikte mit demselben Attributsnamen, das in einer anderen Klasse definiert ist, verhindern würde.
+* Aber warum hat es dann im zweiten Schnipsel nicht geklappt? Weil Name Mangling die Namen ausschließt, die mit doppelten Unterstrichen enden.
+* Der dritte Schnipsel war auch eine Konsequenz des Name Manglings. Der Name `__variable` in der Anweisung `return __variable` wurde zu `_A__variable`, was zufällig auch der Name der Variablen ist, die wir im äußeren Scope deklariert haben.
+* Wenn der demolierte Name länger als 255 Zeichen ist, wird er außerdem abgeschnitten.
+
+---
+---
+
+## Kapitel: Der Schein trügt!
+
+### ▶ Zeilen überspringen?
+
+**Ausgabe:**
+```py
+>>> value = 11
+>>> valuе = 32
+>>> value
+11
+```
+
+Was?
+
+**Beachte:** Um dies zu reproduzieren, kopiere einfach die Anweisungen aus dem obigen Ausschnitt und füge sie in deine Datei/Shell ein.
+
+#### 💡 Erklärung
+
+Einige nicht-westliche Schriftzeichen sehen genauso aus wie die Buchstaben des englischen Alphabets, werden aber von Interpreter als unterschiedlich angesehen.
+
+```py
+>>> ord('е') # kyrillisches 'e' (Ye)
+1077
+>>> ord('e') # lateinisches 'e', wie es in Englisch benutzt wird und auch auf einer Standard-Tastatur vorkommt
+101
+>>> 'е' == 'e'
+False
+
+>>> value = 42 # lateinisches e
+>>> valuе = 23 # kyrillisches 'e', der Python 2.x Interpreter würde hier einen `SyntaxError` werfen
+>>> value
+42
+```
+
+Die built-in `ord()`-Funktion gibt den Unicode eines Characters zurück [code point](https://en.wikipedia.org/wiki/Code_point), und unterschiedliche Codepositionen des kyrillischen "e" und des lateinischen "e" begründen das Verhalten des obigen Beispiels.
+
+---
+
+### ▶ Teleportation
+
+
+
+```py
+# `pip install numpy` vorher.
+import numpy as np
+
+def energy_send(x):
+ # Initialisiere ein numpy array
+ np.array([float(x)])
+
+def energy_receive():
+ # Gib ein leeres numpy array zurück
+ return np.empty((), dtype=np.float).tolist()
+```
+
+**Ausgabe:**
+```py
+>>> energy_send(123.456)
+>>> energy_receive()
+123.456
+```
+
+Wo ist der Nobelpreis?
+
+#### 💡 Erklärung:
+
+* Beachte, dass das numpy array, was in der Funktion `energy_send` erzeugt wurde, nicht zurückgegeben wird, so dass Speicherplatz für die Neuzuweisung frei ist.
+* `numpy.empty()` gibt den nächsten freien Speicherplatz zurück, ohne ihn neu zu initialisieren. Dieser Speicherplatz ist zufällig der gleiche, der gerade freigegeben wurde (normalerweise, aber nicht immer).
+
+---
+
+### ▶ Da ist wohl irgendwas faul...
+
+```py
+def square(x):
+ """
+ Eine einfahce Funktion, um das Quadrat einer Zahl durch Addition zu bestimmen
+ """
+ sum_so_far = 0
+ for counter in range(x):
+ sum_so_far = sum_so_far + x
+ return sum_so_far
+```
+
+**Ausgabe (Python 2.x):**
+
+```py
+>>> square(10)
+10
+```
+
+Sollte das nicht 100 sein?
+
+**Note:** Wenn du das Ergebnis nicht reproduzieren kannst, versuch die Datei [mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py) via shell auszuführen.
+
+#### 💡 Erklärung
+
+* **Vermische keine Leerzeichen und Tabs!** Das Zeichen unmittelbar vor dem Return ist ein "Tab", und der Code ist an anderer Stelle im Beispiel um ein Vielfaches von "4 Leerzeichen" eingerückt.
+* So geht Python mit Tabs um:
+
+ > Als erstes werden Tabs (von links nach rechts) durch ein bis acht Leerzeichen ersetzt, so dass die Gesamtzahl der Zeichen bis einschließlich der Ersetzung ein Vielfaches von acht ist <...>
+* So wird "tab" in der letzten Zeile der Funktion `square` durch acht Leerzeichen ersetzt und gelangt in die Schleife.
+* Python 3 ist so freundlich, in solchen Fällen automatisch einen Fehler zu melden.
+
+ **Ausgabe (Python 3.x):**
+ ```py
+ TabError: inconsistent use of tabs and spaces in indentation
+ ```
+
+---
+---
+
+## Kapitel: Sonstiges
+
+
+### ▶ `+=` ist schneller
+
+
+```py
+# Benutzen von "+" mit drei Strings:
+>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.25748300552368164
+# Benutzen von "+=" mit drei Strings:
+>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.012188911437988281
+```
+
+#### 💡 Erklärung:
++ `+=` ist schneller als `+`, um mehr als zwei String zu konkatenieren, weil der erste String (Beispiel: `s1` für `s1 += s2 + s3`), während der Berechnung des gesamten Strings, nicht zerstört wird.
+
+---
+
+### ▶ Lass uns einen gigantischen String machen!
+
+```py
+def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s += "xyz"
+ assert len(s) == 3*iters
+
+def add_bytes_with_plus(iters):
+ s = b""
+ for i in range(iters):
+ s += b"xyz"
+ assert len(s) == 3*iters
+
+def add_string_with_format(iters):
+ fs = "{}"*iters
+ s = fs.format(*(["xyz"]*iters))
+ assert len(s) == 3*iters
+
+def add_string_with_join(iters):
+ l = []
+ for i in range(iters):
+ l.append("xyz")
+ s = "".join(l)
+ assert len(s) == 3*iters
+
+def convert_list_to_string(l, iters):
+ s = "".join(l)
+ assert len(s) == 3*iters
+```
+
+**Ausgabe:**
+
+```py
+# Ausgeführt in der ipython-Shell unter Verwendung von %timeit für eine bessere Lesbarkeit der Ergebnisse.
+# Du kannst das timeit-Modul auch in der normalen Python-Shell/scriptm= verwenden, Beispielverwendung unten
+# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
+
+>>> NUM_ITERS = 1000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS)
+124 µs ± 4.73 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS)
+211 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS)
+61 µs ± 2.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS)
+117 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS)
+10.1 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+Erhöhen wir die Anzahl der Iterationen um den Faktor 10.
+
+```py
+>>> NUM_ITERS = 10000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linearer Anstieg der Ausführungszeit
+1.26 ms ± 76.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS) # Quadratische Anstieg
+6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS) # Linearer Anstieg
+645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS) # Linearer Anstieg
+1.17 ms ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS) # Linearer Anstieg
+86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+#### 💡 Erklärung
+- Du kannst mehr über timeit hier [timeit](https://docs.python.org/3/library/timeit.html) oder hier [%timeit](https://ipython.org/ipython-doc/dev/interactive/magics.html#magic-timeit) lesen. Sie werden verwendet, um die Ausführungszeit von Codestücken zu messen.
+- Benutze `+` nicht, um lange Strings zu generieren — In Python ist `str` unveränderlich, daher müssen der linke und der rechte String für jedes Paar von Verkettungen in den neuen String kopiert werden. Wenn du 4 String mit Länge 10 konkatenierst, dann kopierst du (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90 Characters anstatt nur 40 Characters. Die Situation verschlechtert sich quadratisch mit zunehmender Anzahl und Größe der Zeichenketten (mit den Ausführungszeiten der Funktion `add_bytes_with_plus` begründet)
+- Daher ist es ratsam, die Syntax `.format.` oder `%` zu verwenden. (sie sind jedoch bei sehr kurzen Zeichenfolgen etwas langsamer als `x`).
+- Oder besser, wenn du bereits Inhalte in Form eines iterierbaren Objekts zur Verfügung hast, dann verwende `''.join(iterable_object)`, was viel schneller ist.
+- Im Gegensatz zu `add_bytes_with_plus` zeigte `add_string_with_plus` aufgrund der im vorherigen Beispiel besprochenen `+=`-Optimierungen keinen quadratischen Anstieg der Ausführungszeit. Wäre die Anweisung `s = s + "x" + "y" + "z"` statt `s += "xyz"` gewesen, wäre der Anstieg quadratisch gewesen.
+ ```py
+ def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s = s + "x" + "y" + "z"
+ assert len(s) == 3*iters
+
+ >>> %timeit -n100 add_string_with_plus(1000)
+ 388 µs ± 22.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+ >>> %timeit -n100 add_string_with_plus(10000) # Quadratischer Anstieg der Ausführungszeit
+ 9 ms ± 298 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+ ```
+- So viele Möglichkeiten einen gigantischen String zu erzeugen und zu formatieren stehen irgendwie in Kontrast zum [Zen von Python](https://www.python.org/dev/peps/pep-0020/), nachdem gilt:
+
+ > Es sollte einen - und vorzugsweise nur einen - offensichtlichen Weg geben, dies zu tun.
+
+---
+
+### ▶ Verlangsamen von `dict` Lookups *
+
+```py
+some_dict = {str(i): 1 for i in range(1_000_000)}
+another_dict = {str(i): 1 for i in range(1_000_000)}
+```
+
+**Ausgabe:**
+```py
+>>> %timeit some_dict['5']
+28.6 ns ± 0.115 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> some_dict[1] = 1
+>>> %timeit some_dict['5']
+37.2 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+
+>>> %timeit another_dict['5']
+28.5 ns ± 0.142 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> another_dict[1] # Versuch, auf einen Schlüssel zuzugreifen, der nicht existiert
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 1
+>>> %timeit another_dict['5']
+38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+```
+Wieso werden dieselben lookups immer langsamer?
+
+#### 💡 Erklärung:
++ CPython hat eine generische Funktion zum Nachschlagen in Dictionaries, die alle Arten von Schlüsseln (`str`, `int`, beliebige Objekte ...) behandelt, und eine spezialisierte Funktion für den häufigen Fall von Dictionaries, die nur aus `str`-Schlüsseln bestehen.
++ Die spezialisierte Funktion (wird `lookdict_unicode` in CPythons [code](https://github.com/python/cpython/blob/522691c46e2ae51faaad5bbbce7d959dd61770df/Objects/dictobject.c#L841) genannt) weiß, dass alle vorhandenen Schlüssel (einschließlich des nachgeschlagenen Schlüssels) Strings sind, und verwendet den schnelleren und einfacheren Stringvergleich, um Schlüssel zu vergleichen, anstatt die Methode `__eq__` aufzurufen.
++ Das erste Mal, wenn auf eine `dict`-Instanz mit einem Nicht-`str`-Schlüssel zugegriffen wird, wird sie so geändert, dass zukünftige Suchvorgänge die generische Funktion verwenden.
++ Dieser Prozess ist für die jeweilige `dict`-Instanz nicht umkehrbar, und der Schlüssel muss nicht einmal im Dictionary vorhanden sein. Deshalb hat der Versuch eines fehlgeschlagenen Nachschlagevorgangs denselben Effekt.
+
+### ▶ Blähende Instanz `dict`s *
+
+```py
+import sys
+
+class SomeClass:
+ def __init__(self):
+ self.some_attr1 = 1
+ self.some_attr2 = 2
+ self.some_attr3 = 3
+ self.some_attr4 = 4
+
+
+def dict_size(o):
+ return sys.getsizeof(o.__dict__)
+
+```
+
+**Ausgabe:** (Python 3.8, oder Python 3 Versionen können ein bisschen variieren)
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104
+>>> dict_size(o2)
+104
+>>> del o1.some_attr1
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+>>> dict_size(o1)
+232
+```
+
+Versuchen wir es noch einmal... in einem neuen Interpreter:
+
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104 # wie erwartet
+>>> o1.some_attr5 = 5
+>>> o1.some_attr6 = 6
+>>> dict_size(o1)
+360
+>>> dict_size(o2)
+272
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+```
+
+Was führt dazu, dass diese Dictionaries aufgebläht werden? Und warum werden neu erstellte Objekte ebenfalls aufgebläht?
+
+#### 💡 Erklärung:
++ CPython ist in der Lage, das gleiche "keys"-Objekt in mehreren Dictionaries wiederzuverwenden. Dies wurde in [PEP 412](https://www.python.org/dev/peps/pep-0412/) mit der Motivation hinzugefügt, die Speichernutzung zu reduzieren, insbesondere in Dictionaries von Instanzen - wo Schlüssel (Instanzattribute) tendenziell für alle Instanzen gleich sind.
++ Diese Optimierung ist für Instanz-Dictionaries völlig nahtlos, wird aber deaktiviert, wenn bestimmte Annahmen nicht eingehalten werden.
++ Key-Sharing-Dictionaries unterstützen keine Löschung; wird ein Instanzattribut gelöscht, ist das Wörterbuch "unshared" und Key-Sharing ist für alle zukünftigen Instanzen derselben Klasse deaktiviert.
++ Wenn die Größe der Dictionary-Schlüssel geändert wurde (weil neue Schlüssel eingefügt wurden), werden sie außerdem *nur* dann gemeinsam genutzt, wenn sie von genau einem einzigen Dictionary verwendet werden (dies ermöglicht das Hinzufügen vieler Attribute in der `__init__` der allerersten erstellten Instanz, ohne ein "unshare" zu verursachen). Wenn mehrere Instanzen existieren, wenn eine Größenänderung stattfindet, wird das Key-Sharing für alle zukünftigen Instanzen der gleichen Klasse deaktiviert: CPython kann nicht mehr feststellen, ob die Instanzen den gleichen Satz von Attributen verwenden und entscheidet sich, den Versuch, ihre Schlüssel zu teilen, abzubrechen.
++ Ein kleiner Tipp, wenn du den Speicherbedarf deines Programms verringern willst: lösche keine Instanzattribute und stelle sicher, dass alle Attribute in `__init__` initialisiert werden!
+
+### ▶ Kleinigkeiten *
+
+* `join()` ist eine String-Operation anstatt einer List-Operation. (auf den ersten Blick nicht einfach zu verstehen)
+
+ **💡 Erklärung:** Wenn `join()` eine Methode auf einem String ist, dann kann es auf irgendeinem Iterable operieren (Liste, Tuple, Iteratoren). Wäre es eine Methode auf einer Liste, müsste sie von jedem Typ separat implementiert werden. Außerdem macht es nicht viel Sinn, eine String-spezifische Methode auf eine generische API eines `listen`-Objektes anzuwenden
+
+* Ein paar komisch aussehende aber semantisch korrekte Statements:
+ + `[] = ()` ist ein semantisch korrektes Statement (entpacken eines leeren `Tuples` in eine leere `Liste`)
+ + `'a'[0][0][0][0][0]` ist auch semantisch korrect, weil Python keinen Char-Datentyp wie viele andere Sprachen, die auf C basieren, hat. Also ergibt das Auswählen eines einzelnen Characters aus einem String einen Single-character String.
+ + `3 --0-- 5 == 8` und `--5 == 5` sind beides semantisch korrekte Statements und wird zu `True` ausgewertet.
+
+* Sei `a` eine Zahl, `++a` und `--a` sind beides valide Python Statements aber sie verhaten sich nicht so wie vergleichbare Statements in Sprachen wie C, C++, oder Java.
+ ```py
+ >>> a = 5
+ >>> a
+ 5
+ >>> ++a
+ 5
+ >>> --a
+ 5
+ ```
+
+ **💡 Erklärung:**
+ + Es gibt keinen `++` Operator in der Python Grammatik. Es handelt sich eigentlich um zwei `+` Operatoren.
+ + `++a` parst eigentlich `+(+a)` was übersetzt wird zu `a`. In ähnlicher Weise kann die Ausgabe des Statement `--a` begründet werden.
+ + Dieser StackOverflow [Thread](https://stackoverflow.com/questions/3654830/why-are-there-no-and-operators-in-python) erörtert die Gründe für das Fehlen von Inkrement- und Dekrementoperatoren in Python.
+
+* Der Walrus Operator in Python sollte dir bekannt sein. Aber hast du schon mal von dem *space-invader Operator* gehört?
+ ```py
+ >>> a = 42
+ >>> a -=- 1
+ >>> a
+ 43
+ ```
+ Er wird als alternativer Inkrementierungsoperator verwendet, zusammen mit einem anderen Operator
+ ```py
+ >>> a +=+ 1
+ >>> a
+ >>> 44
+ ```
+ **💡 Erklärung:** Dieser Streich kommt von [Raymond Hettingers Tweet](https://twitter.com/raymondh/status/1131103570856632321?lang=en). Der Space Invader Operator ist tatsächlich nur ein schlecht formatiertes `a -= (-1)`, was äquivalent zu `a = a - (- 1)` ist. Ähnliches gilt für `a += (+ 1)`.
+
+* Python hat einen undokumentierten [umgekehrten Implikations](https://en.wikipedia.org/wiki/Converse_implication) Operator.
+
+ ```py
+ >>> False ** False == True
+ True
+ >>> False ** True == False
+ True
+ >>> True ** False == True
+ True
+ >>> True ** True == True
+ True
+ ```
+
+ **💡 Erklärung:** Wenn du `False` und `True` durch 0 und 1 ersetzt und dann rechnest, dann ist die Wahrheitstabelle äquivalent zu einem Operator der umgekehrten Implikation. ([Quelle](https://github.com/cosmologicon/pywat/blob/master/Erklärung.md#the-undocumented-converse-implication-operator))
+
+* Weil wir über Operatoren sprechen, erwähnen wir auch den `@` Ooperator, der für Matrixmultiplikation benutzt wird (Keine Sorge, diese mal ist es ernst).
+
+ ```py
+ >>> import numpy as np
+ >>> np.array([2, 2, 2]) @ np.array([7, 8, 8])
+ 46
+ ```
+
+ **💡 Erklärung:** Der `@` Operator wurde, mit der wissenschaftlichen Community im Hinterkopf, in Python 3.5 eingeführt. Jedes Objekt kann die magische `__matmul__` Method überladen, um ein bestimmtes Verhalten für diesen Operator zu definieren.
+
+* Ab Python 3.8 kannst du eine typische f-String-Syntax wie `f'{some_var=}` für schnelles Debugging verwenden. Zum Beispiel:
+ ```py
+ >>> some_string = "wtfpython"
+ >>> f'{some_string=}'
+ "some_string='wtfpython'"
+ ```
+
+* Python nutzt 2 Bytes die Speicherung lokaler Variablen in Funktionen. Theoretisch heißt das, dass nur 65536 Variablen in einer Funktion gespeichert werden können. Allerdings hat Python eine praktische, eingebaute Lösung, die benutzt werden kann, um mehr als 2^16 Variablennamen zu speichern. Der folgende Code demonstriert, was im Stack passiert, wenn mehr als 65536 lokale Variablen definiert werden (Warnung: Dieser Code gibt ungefähr 2^18 Zeilen Text aus, also sei darauf vorbereitet!):
+
+ ```py
+ import dis
+ exec("""
+ def f():
+ """ + """
+ """.join(["X" + str(x) + "=" + str(x) for x in range(65539)]))
+
+ f()
+
+ print(dis.dis(f))
+ ```
+
+* Mehrere Python Threads werden deinen *Python code* nicht gleichzeitig laufen lassen (Ja, du hast richtig gehört!).
+Es mag intuitiv erscheinen, mehrere Threads zu erzeugen, die dann deinen Python code gleichzeitig ausführen, aber wegen dem [Global Interpreter Lock](https://wiki.python.org/moin/GlobalInterpreterLock) in Python, sorgst du nur dafür, dass die Threads abwechselnd auf demselben Kern ausgeführt werden.
+Python Threads sind gut für IO-gebundene Aufgaben, aber um tatsächliche Parallelisierung für CPU-gebundene Aufgaben in Python zu erreichen, solltest du lieber das Python [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) Modul benutzen.
+
+* Manchmal gibt die `print` Methode die Werte nicht sofort aus. Zum Beispiel:
+
+ ```py
+ # Datei some_file.py
+ import time
+
+ print("wtfpython", end="_")
+ time.sleep(3)
+ ```
+
+ Das wird `wtfpython` nach 3 Sekunden, aufgrund des `end` Argumentes ausgeben, wei der Ausgabe-Puffer, entweder nach `\n` oder wenn das Programm die Ausführung beendet, geflusht wird. Mit dem Argument `flush=True` können wir den Puffer zum Flushen zwingen.
+
+* List slicing mit Indices, die nicht innerhalb der Grenzen liegen, wirft keinen Fehler
+ ```py
+ >>> some_list = [1, 2, 3, 4, 5]
+ >>> some_list[111:]
+ []
+ ```
+
+* Slicing eines Iterables erzeugt nicht immer ein neues Objekt. Zum Beispiel:
+ ```py
+ >>> some_str = "wtfpython"
+ >>> some_list = ['w', 't', 'f', 'p', 'y', 't', 'h', 'o', 'n']
+ >>> some_list is some_list[:] # False erwartet, weil ein neues Objekt erzeugt wird.
+ False
+ >>> some_str is some_str[:] # True, weil Strings immutable sind, daher ist die Erstellung eines neuen Objektes sinnlos.
+ True
+ ```
+
+* `int('١٢٣٤٥٦٧٨٩')` gibt `123456789` in Python 3 zurück. In Python umfassen Dezimalzeichen auch Ziffernzeichen, und alle Zeichen, die zum Erstellen von Dezimal-Radix-Nummern benutzt werden können, z.B. U+0660, ARABIC-INDIC DIGIT ZERO. Hier ist eine [interessante Geschichte](https://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/) im Zusammenhang mit diesem Verhalten in Python.
+
+* Du kannst numerische Literale mit Unterstrichen trennen, um die Lesbarkeit zu erhöhen (Python 3 spezifisch).
+
+ ```py
+ >>> sechs_millionen = 6_000_000
+ >>> sechs_millionen
+ 6000000
+ >>> sechs_millionen = 0xF00D_CAFE
+ >>> sechs_millionen
+ 4027435774
+ ```
+
+* `'abc'.count('') == 4`. Hier ist eine ungefähre Implementierung der Methode `count`, die die Dinge klarer machen würde
+ ```py
+ def count(s, sub):
+ result = 0
+ for i in range(len(s) + 1 - len(sub)):
+ result += (s[i:i + len(sub)] == sub)
+ return result
+ ```
+ Das Verhalten ist darauf zurückzuführen, dass leere Teilstrings (`''`) mit Slices der Länge 0 in der ursprünglichen Zeichenkette übereinstimmen
+---
+---
+
+# Contributing
+
+Ein paar Wege, wie du zu wtfpython beitragen kannst:
+
+- neue Beispiele vorschlagen
+- bei der Übersetzung helfen (Siehe [issues sind mit 'translation' markiert](https://github.com/satwikkansal/wtfpython/issues?q=is%3Aissue+is%3Aopen+label%3Atranslation))
+- Verbesserungen vorschlagen, z.B. Aufzeigen von veralteten Schnipseln, Typen, Formatfehlern, etc.
+- Lücken identifizieren (z.B. unzureichende Erklärungen, überflüssige Beispiele, etc.)
+- Irgendwelche kreativen Vorschläge unterbreiten, um dieses Projekt spaßiger und nützlicher zu machen
+
+Für mehr Details, wirf bitte einen Blick auf [CONTRIBUTING.md](/CONTRIBUTING.md). Erstelle ruhig ein neues [Issue](https://github.com/satwikkansal/wtfpython/issues/new), um zu diskutieren.
+
+PS: Bitte keine Anfragen mit Links erstellen. Es werden keine Links hinzugefügt, es sein denn sie sind für das Projekt relevant.
+
+# Anerkennung
+
+Die Idee und das Design für diese Sammlung wurden durch Denys Dovhans tolles Projekt inspiriert [wtfjs](https://github.com/denysdovhan/wtfjs). Der überragende Support von Pythonisten hat es zu dem gemacht, was es heute ist.
+
+#### Ein paar nützliche Links!
+* https://www.youtube.com/watch?v=sH4XF6pKKmk
+* https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python
+* https://sopython.com/wiki/Common_Gotchas_In_Python
+* https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines
+* https://stackoverflow.com/questions/1011431/common-pitfalls-in-python
+* https://www.python.org/doc/humor/
+* https://github.com/cosmologicon/pywat#the-undocumented-converse-implication-operator
+* https://www.codementor.io/satwikkansal/python-practices-for-efficient-code-performance-memory-and-usability-aze6oiq65
+* https://github.com/wemake-services/wemake-python-styleguide/search?q=wtfpython&type=Issues
+* WFTPython discussion threads on [Hacker News](https://news.ycombinator.com/item?id=21862073) and [Reddit](https://www.reddit.com/r/programming/comments/edsh3q/what_the_fck_python_30_exploring_and/).
+
+# 🎓 License
+
+[![WTFPL 2.0][license-image]][license-url]
+
+© [Satwik Kansal](https://satwikkansal.xyz)
+
+[license-url]: http://www.wtfpl.net
+[license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square
+
+## Überrasche auch deine Freunde!
+
+Wenn du wtfpython cool findest, kannst du diese Quick-Links nutzen, um es mit deinen Freunden zu teilen:
+
+[Twitter](https://twitter.com/intent/tweet?url=https://github.com/satwikkansal/wtfpython&text=If%20you%20really%20think%20you%20know%20Python,%20think%20once%20more!%20Check%20out%20wtfpython&hashtags=python,wtfpython) | [Linkedin](https://www.linkedin.com/shareArticle?url=https://github.com/satwikkansal&title=What%20the%20f*ck%20Python!&summary=If%20you%20really%20thing%20you%20know%20Python,%20think%20once%20more!) | [Facebook](https://www.facebook.com/dialog/share?app_id=536779657179021&display=page&href=https%3A%2F%2Fgithub.com%2Fsatwikkansal%2Fwtfpython"e=If%20you%20really%20think%20you%20know%20Python%2C%20think%20once%20more!)
+
+## Brauchst du eine pdf version?
+
+Ich habe ein paar Anfragen für eine pdf (und epub) Version von wtfpython erhalten. Du kannst [hier](https://form.jotform.com/221593245656057) deine Daten angeben, um sie schnellstmöglich zu bekommen.
+
+**Das ist alles, Freunde!** Um neue Updates zu erhalten, kannst du deine email [hier](https://form.jotform.com/221593598380062) hinzufügen.
diff --git a/docs/KO.md b/docs/KO.md
new file mode 100644
index 0000000..5263a5a
--- /dev/null
+++ b/docs/KO.md
@@ -0,0 +1,3992 @@
+---
+hide:
+ - toc
+---
+
+> > ## 번역
+>
+> 번역에 참여하고 싶으시면 [Github Discussion](https://github.com/buttercrab/wtfpython-ko/discussions)을 방문하세요!
+
+
+What the f*ck Python! 😱
+놀라운 예제들을 통해서 파이썬 탐험하고 이해하기
+
+영어 English(원문) | 중국어 中文
+
+다른 읽는 방법: [인터랙티브](https://colab.research.google.com/github/satwikkansal/wtfpython/blob/master/irrelevant/wtf.ipynb) | [CLI](https://pypi.python.org/pypi/wtfpython)
+
+설계가 잘된 고급 인터프리터 언어인 파이썬에는 프로그래머를 위한 편의 기능이 많습니다. 하지만 간혹 어떤 파이썬 코드들은 실행 결과가 이상해보일 때도 있습니다.
+
+이 문서는 직관적이지 않거나 덜 알려진 기능을 사용하는 예제들에 대해 그 이면의 동작을 정확하게 설명합니다.
+
+아래의 예제들이 WTF 까지는 아닐 수도 있지만, 파이썬의 잘 몰랐던 부분을 보여드릴 수는 있을 것입니다. 이런 식으로 공부하는 것이 프로그래밍 언어의 내부를 알게되는데 효과적이라고 생각합니다. 여러분도 동의하게 되실거라 믿습니다!
+
+만약 파이썬의 고인물이라면 첫눈에 예제의 의미를 파악해보세요. 아마 이미 경험한 적이 있는 코드일 수도 있겠습니다. 그렇다면 옛 추억을 떠으로게 해드린 셈이 되겠네요! :sweat_smile:
+
+추신: 예전에 읽었는데 다시 오신 분이라면 [여기서](https://github.com/satwikkansal/wtfpython/releases/) 바뀐 부분을 확인할 수 있습니다.
+
+추신 2: [옮긴이의 말](https://github.com/buttercrab/wtfpython-ko/blob/3.0/translator.md)을 읽는 것을 추천합니다.
+
+그럼, 시작합니다!
+
+# 목차
+
+
+
+
+
+- [예제의 구성](#예제의-구성)
+ - [▶ 빛나는 제목](#-빛나는-제목)
+- [사용방법](#사용방법)
+- [👀 예제](#-예제)
+ - ["머리가 아플수도 있어요!" 단원](#머리가-아플수도-있어요-단원)
+ - [▶ 먼저 처음 것들부터 \*](#-먼저-처음-것들부터-)
+ - [▶ 문자열은 가끔 헷갈려요](#-문자열은-가끔-헷갈려요)
+ - [▶ 연결된 연산들을 조심하세요](#-연결된-연산들을-조심하세요)
+ - [▶ `is` 연산자를 안 쓰는 방법](#-is-연산자를-안-쓰는-방법)
+ - [▶ 해시 브라우니](#-해시-브라우니)
+ - [▶ 깊이 들어가면 우리는 다 똑같아.](#-깊이-들어가면-우리는-다-똑같아)
+ - [▶ 질서 속의 무질서 \*](#-질서-속의-무질서-)
+ - [▶ 계속 시도해 보세요... \*](#-계속-시도해-보세요-)
+ - [▶ 무엇을 위해서(for)?](#-무엇을-위해서for)
+ - [▶ 실행되는 시간의 차이](#-실행되는-시간의-차이)
+ - [▶ `is not ...` 은 `is (not ...)`이 아니다](#-is-not--은-is-not-이-아니다)
+ - [▶ X가 첫 번째 시도에서 승리하는 틱택토!](#-x가-첫-번째-시도에서-승리하는-틱택토)
+ - [▶ 달라붙는 출력 함수](#-달라붙는-출력-함수)
+ - [▶ 닭이 먼저일까, 달걀이 먼저일까 \*](#-닭이-먼저일까-달걀이-먼저일까-)
+ - [▶ 서브 클래스의 관계](#-서브-클래스의-관계)
+ - [▶ 메소드의 같음과 동일함](#-메소드의-같음과-동일함)
+ - [▶ 참 거짓의 반복 \*](#-참-거짓의-반복-)
+ - [▶ 놀라운 콤마](#-놀라운-콤마)
+ - [▶ 문자열과 백슬래시](#-문자열과-백슬래시)
+ - [▶ 매듭이 아니야!](#-매듭이-아니야)
+ - [▶ 반쪽 3중 따옴표 문자열](#-반쪽-3중-따옴표-문자열)
+ - [▶ 불린의 문제점이 뭐야?](#-불린의-문제점이-뭐야-)
+ - [▶ 클래스 속성과 인스턴스 속성](#-클래스-속성과-인스턴스-속성)
+ - [▶ yielding None](#-yielding-none)
+ - [▶ Yielding from... return! \*](#-yielding-from-return-)
+ - [▶ Nan-재귀성 \*](#-nan-재귀성-)
+ - [▶ 불변을 변형하기!](#-불변을-변형하기)
+ - [▶ 외부 범위에서 사라지는 변수](#-외부-범위에서-사라지는-변수)
+ - [▶ 미스테리한 키 타입 형 변환](#-미스테리한-키-타입-형-변환)
+ - [▶ 여러분이 맞출 수 있는지 한번 볼까요?](#-여러분이-맞출-수-있는지-한번-볼까요)
+ - ["미끄러운 비탈길" 단원](#미끄러운-비탈길-단원)
+ - [▶ 딕셔너리가 반복 중일 때 수정하기](#-딕셔너리가-반복-중일-때-수정하기)
+ - [▶ 완강한 `del` 연산자](#-완강한-del-연산자)
+ - [▶ 범위를 벗어난 변수](#-범위를-벗어난-변수)
+ - [▶ 반복하는 동안 리스트의 아이템을 삭제하기](#-반복하는-동안-리스트의-아이템을-삭제하기)
+ - [▶ 반복자의 손실되는 zip \*](#-반복자의-손실되는-zip-)
+ - [▶ 루프 변수가 유출되고 있습니다!](#-루프-변수가-유출되고-있습니다)
+ - [▶ 기본 가변인수를 조심하세요!](#-기본-가변인수를-조심하세요)
+ - [▶ 여러 예외들을 잡기](#-여러-예외들을-잡기)
+ - [▶ 같은 피연산자, 다른 이야기!](#-같은-피연산자-다른-이야기)
+ - [▶ 이름 확인은 클래스 범위를 무시합니다](#-이름-확인은-클래스-범위를-무시합니다)
+ - [▶ 모래밭에서 바늘찾기 \*](#-모래밭에서-바늘찾기-)
+ - [▶ 나눠봅시다 \*](#-나눠봅시다-)
+ - [▶ 제멋대로 가져오기 \*](#-제멋대로-가져오기-)
+ - [▶ 다 정렬되었나요? \*](#-다-정렬되었나요-)
+ - [▶ 자정은 존재하지 않나요?](#-자정은-존재하지-않나요)
+ - ["숨겨진 보물들!" 단원](#숨겨진-보물들-단원)
+ - [▶ 파이썬, 날 날게해줄 수 있니?](#-파이썬-날-날게해줄-수-있니)
+ - [▶ `goto`, 하지만 왜?](#-goto-하지만-왜)
+ - [▶ 마음 단단히 먹으세요!](#-마음-단단히-먹으세요)
+ - [▶ 평생 친근한 아저씨 같은 언어를 만나봅시다](#-평생-친근한-아저씨-같은-언어를-만나봅시다)
+ - [▶ 파이썬 조차 사랑이 복잡하다는 것을 이해합니다](#-파이썬-조차-사랑이-복잡하다는-것을-이해합니다)
+ - [▶ 네, 존재합니다!](#-네-존재합니다)
+ - [▶ Ellipsis \*](#-ellipsis-)
+ - [▶ Inpinity](#-inpinity)
+ - [▶ 망쳐봅시다](#-망쳐봅시다)
+ - ["겉모습은 기만적입니다!" 단원](#겉모습은-기만적입니다-단원)
+ - [▶ 줄 건너뛰기?](#-줄-건너뛰기)
+ - [▶ 순간이동](#-순간이동)
+ - [▶ 음, 뭔가 수상한데...](#-음-뭔가-수상한데)
+ - ["기타 등등" 단원](#기타-등등-단원)
+ - [▶ `+=` 가 더 빨라요](#--가-더-빨라요)
+ - [▶ 거대한 문자열을 만들어봐요!](#-거대한-문자열을-만들어봐요)
+ - [▶ dict 검색 속도 느려지게 하기 \*](#-dict-검색-속도-느려지게-하기-)
+ - [▶ `dict` 인스턴스 부풀리기 \*](#-dict-인스턴스-부풀리기-)
+ - [▶ 사소한 것들 \*](#-사소한-것들-)
+- [기여하기](#기여하기)
+- [감사의 말](#감사의-말)
+- [🎓 License](#-license)
+ - [친구들을 놀라게 해보세요!](#친구들을-놀라게-해보세요)
+ - [Need a pdf version?](#Need-a-pdf-version-)
+
+
+
+# 예제의 구성
+
+모든 예제는 아래와 같은 구조로 이루어져 있습니다.
+
+> ### ▶ 빛나는 제목
+>
+> ```py
+> # 예제 세팅
+> # 마법 같은 일을 기대하세요...
+> ```
+>
+> **출력 결과 (유효한 파이썬 버전들):**
+>
+> ```py
+> >>> 입력
+> 놀라운 결과
+> ```
+>
+> 놀라운 결과에 대한 한 줄 설명이 있을 수도 있습니다.
+>
+> #### 💡 설명:
+>
+> - 무엇이 일어나고 있는지와 왜 일어나는지에 대한 간략한 설명
+>
+> ```py
+> # 설명을 도울 예제
+> ```
+>
+> **출력 결과 (유효한 파이썬 버전들):**
+>
+> ```py
+> >>> 입력 # 놀라운 결과의 이해를 돕기 위한 예제
+> # 이해 가능한 결과
+> ```
+
+**참고:** 여기에 있는 모든 예제는 파이썬 3.5.2 인터렉티브 인터프리터에서 테스트 되었고 추가적으로 명시되어 있지 않은 이상 모든 버전에서 작동할 것입니다.
+
+# 사용방법
+
+예제들을 순서대로 읽어내려가는 것을 권장하고 예제마다:
+
+- 예제의 코드를 잘 읽어보세요. 만약 파이썬 고인물이라면 대부분 결과가 어떻게 될지 미리 알고 있을 것입니다.
+- 결과를 읽고,
+ - 예상한 결과와 실제 결과가 맞는지 확인해 보세요.
+ - 결과와 그 작동원리에 대한 정확한 원리를 알고 있나요?
+ * 만약 아니라면 (상관없어요), 큰 숨을 한 번 들이마시고, 설명을 읽어보세요 (그래도 이해하지 못했다면, [여기](https://github.com/satwikkansal/wtfpython/issues/new)에 이슈를 작성해주세요).
+ * 알고 있다면, 자신을 한번 토닥여주고 다음 예제로 넘어가세요.
+
+추신: [pypi 패키지](https://pypi.python.org/pypi/wtfpython)를 사용하면 command line에서도 이 문서를 읽을 수 있습니다.
+
+```sh
+$ pip install wtfpython -U
+$ wtfpython
+```
+
+---
+
+# 👀 예제
+
+## "머리가 아플수도 있어요!" 단원
+
+### ▶ 먼저 처음 것들부터 \*
+
+
+
+
+어떤 이유에서인지, 파이썬 3.8의 Walrus 연산자 (`:=`) 가 꽤 알려지게 되었습니다. 확인해봅시다.
+
+1\.
+
+```py
+# 파이썬 3.8+
+
+>>> a = "wtf_walrus"
+'wtf_walrus'
+>>> a
+'wtf_walrus'
+
+>>> a := "wtf_walrus"
+File "", line 1
+ a := "wtf_walrus"
+ ^
+SyntaxError: invalid syntax
+
+>>> (a := "wtf_walrus") # 이건 잘 작동하네요
+>>> a
+'wtf_walrus'
+```
+
+2 \.
+
+```py
+# 파이썬 3.8+
+
+>>> a = 6, 9
+>>> a
+(6, 9)
+
+>>> (a := 6, 9)
+>>> a
+6
+
+>>> a, b = 6, 9 # 전형적인 언패킹
+>>> a, b
+(6, 9)
+>>> (a, b = 16, 19) # 이런
+ File "", line 1
+ (a, b = 6, 9)
+ ^
+SyntaxError: invalid syntax
+
+>>> (a, b := 16, 19) # 이것은 이상한 3-튜플을 출력합니다.
+(6, 16, 19)
+
+>>> a # a가 아직도 안 바뀌었네요?
+6
+
+>>> b
+16
+```
+
+#### 💡 설명
+
+**간단한 walrus 연산자 설명**
+
+walrus 연산자 (`:=`) 는 파이썬 3.8에서 소개되었으며, 변수에 할당하면서 연산을 하고 싶을 때 유용하게 쓰일 수 있습니다.
+
+```py
+def some_func():
+ # 많은 계산을 하는 함수라고 가정합시다.
+ # time.sleep(1000)
+ return 5
+
+# 그래서
+if some_func():
+ print(some_func()) # 같은 계산이 두 번 이루어지므로 안 좋은 방법입니다.
+
+# 또는
+a = some_func()
+if a:
+ print(a)
+
+# 대신에 이렇게 간단하게 쓸 수 있습니다.
+if a := some_func():
+ print(a)
+```
+
+**출력 결과 (> 3.8):**
+
+```py
+5
+5
+5
+```
+
+이 연산자는 한 줄의 코드를 아끼고 `some_func`를 두 번 호출하는 것을 방지할 수 있습니다.
+
+- (walrus 연산자를 사용한) 괄호로 묶이지 않은 "할당문(assignment expression)"은 컴파일러의 상위 단계에서 제한되므로 첫 번째 줄 `a := "wtf_walrus"`에서 `SyntaxError`가 발생합니다. 괄호로 묶게 되면 예상했던 대로 작동하고 `a`에 값을 할당하게 됩니다.
+
+- 정상적으로, `=` 연산자를 포함한 표현식에서는 괄호로 둘러싸는 것이 허용되지 않기 때문에, `(a, b = 6, 9)`에서 syntax error가 발생합니다.
+
+- walrus 연산자는 `Name`이 유효한 식별자(identifier)이고 `expr`이 유효한 표현식 일 때, `Name := expr`로 사용됩니다. 따라서 패킹과 언패킹은 지원되지 않습니다. 그러므로,
+
+ - `(a := 6, 9)`는 `((a := 6), 9)`와 같고 결국 `(a, 9)` (`a`의 값이 6 일때) 와 같게 됩니다.
+
+ ```py
+ >>> (a := 6, 9) == ((a := 6), 9)
+ True
+ >>> x = (a := 696, 9)
+ >>> x
+ (696, 9)
+ >>> x[0] is a # 둘 다 메모리의 같은 위치를 가리키고 있습니다.
+ True
+ ```
+
+ - 비슷하게, `(a, b := 16, 19)`는 `(a, (b := 16), 19)`와 같게 되고 이는 단순한 3-튜플과 같습니다.
+
+---
+
+### ▶ 문자열은 가끔 헷갈려요
+
+
+
+1\.
+
+```py
+>>> a = "some_string"
+>>> id(a)
+140420665652016
+>>> id("some" + "_" + "string") # 두 id가 같네요
+140420665652016
+```
+
+2\.
+
+```py
+>>> a = "wtf"
+>>> b = "wtf"
+>>> a is b
+True
+
+>>> a = "wtf!"
+>>> b = "wtf!"
+>>> a is b
+False
+
+```
+
+3\.
+
+```py
+>>> a, b = "wtf!", "wtf!"
+>>> a is b # 3.7.x 버전을 제외하고 모든 버전에서 이렇게 작동합니다.
+True
+
+>>> a = "wtf!"; b = "wtf!"
+>>> a is b # 어디서 실행시키는지에 따라 True 혹은 False가 출력될 것입니다. (파이썬 쉘 / ipython / 파이썬 스크립트)
+False
+```
+
+```py
+# 이번에는 some_file.py 파일에서 실행시켜봅시다.
+a = "wtf!"
+b = "wtf!"
+print(a is b)
+
+# 모듈을 실행시키면 True를 출력하네요.
+```
+
+4\.
+
+**출력 결과 (< 3.7 )**
+
+```py
+>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
+True
+>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
+False
+```
+
+말이 되는 거 같죠?
+
+#### 💡 설명:
+
+- 첫 번째와 두 번째 코드에서의 결과는 새로운 객체를 항상 만드는 것보다 이미 존재하고 바뀌지 않는 객체를 사용하려고 하는 CPython 최적화 때문에 그렇습니다. (문자열 interning이라고 부릅니다)
+- interning이 되고 난 다음, 많은 변수는 같은 메모리에 있는 문자열을 가리키고 있을 겁니다. (메모리를 줄이게 됩니다)
+- 위의 코드들을 보면, 문자열은 알아서 interning이 됩니다. 구현 방식에 따라서 알아서 interning이 될 것인지 결정됩니다. 알아서 interning이 될 것인지 예측해볼 몇 가지 규칙이 있습니다:
+ - 길이가 0과 1인 모든 문자열은 interning이 됩니다.
+ - 문자열은 컴파일 시간에 interning이 됩니다. (`'wtf'`은 interning이 되지만 `''.join(['w', 't', 'f'])`은 interning이 되지 않습니다)
+ - 아스키 문자, 숫자, 언더바 이외의 문자로 이루어져 있으면 interning이 되지 않습니다. 그래서 `'wtf!'`이 `!` 때문에 interning이 되지 않았습니다. CPython에서의 구현은 [여기](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)서 확인할 수 있습니다.
+ ![image](../images/string-intern/string_intern.png)
+- `a`와 `b`가 같은 줄에서 `"wtf!"`의 값으로 할당된다면, 파이썬 인터프리터가 새로운 객체를 만들고 두 번째 변수도 가리키게 만듭니다. 그런데 만약 이 작업을 다른 줄에서 한다면, 파이썬 인터프리터는 이미 `"wtf!"`가 객체로 존재한다는 사실을 모릅니다 (왜냐하면 `"wtf!"`는 interning이 되지 않았기 때문입니다). interning은 컴파일 시간에 작동하는 최적화입니다. 이 최적화는 CPython 3.7.x 버전들에는 적용되지 않았습니다. (더 많은 정보는 이 [이슈](https://github.com/satwikkansal/wtfpython/issues/100)를 확인하세요).
+- IPython과 같은 인터랙티브 환경에서는 하나의 컴파일 유닛(unit)이 하나의 표현식이고 모듈일 때는 모듈 전체일 때도 있습니다. `a, b = "wtf!", "wtf!"`은 하나의 표현식이지만 `a = "wtf!"; b = "wtf!"`은 한 줄에 있는 두 개의 표현식입니다. 그러면 위 예제들의 결과를 설명할 수 있습니다.
+- 네 번째 출력 결과의 갑작스러운 변화는 [핍홀 최적화](https://en.wikipedia.org/wiki/Peephole_optimization)에 의한 것입니다. 즉 `'a'*20`은 실행 시간에 클록수를 줄이기 위해 컴파일 시간에 `aaaaaaaaaaaaaaaaaaaa`로바뀝니다. 핍홀 최적화는 문자열의 길이가 20 이하일 때만 일어납니다 (`'a'*10**10`의 결과로 `.pyc`파일의 크기를 생각해보세요). [여기](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288)에 그에 대한 구현이 있습니다.
+- 참고: 파이썬 3.7에서는 새로운 AST 최적화 새로운 로직으로 핍홀 최적화가 빠졌습니다. 그래서 세 번째 코드가 파이썬 3.7에서는 작동하지 않았습니다. [여기](https://bugs.python.org/issue11549)에서 더 자세히 알아보세요.
+
+---
+
+### ▶ 연결된 연산들을 조심하세요
+
+
+
+```py
+>>> (False == False) in [False] # 말이 되네요
+False
+>>> False == (False in [False]) # 이것도 말이 됩니다
+False
+>>> False == False in [False] # 이건 뭐죠?
+True
+
+>>> True is False == False
+False
+>>> False is False is False
+True
+
+>>> 1 > 0 < 1
+True
+>>> (1 > 0) < 1
+False
+>>> 1 > (0 < 1)
+False
+```
+
+#### 💡 설명:
+
+https://docs.python.org/2/reference/expressions.html#not-in 에 따라서
+
+> 형식적으로, a, b, c, ..., y, z가 표현식이고 op1, op2, ..., opN이 비교 연산자라면, 각 식이 한번에 평가된다는 점을 제외하고 a op1 b op2 c ... y opN z는 a op1 b and b op2 c and ... y opN z에 해당합니다.
+
+위의 예시와 같은 결과들은 이상해 보일지도 모르지만, `a == b == c`나 `0 <= x <= 100`와 같은 표현들은 환상적입니다.
+
+- `False is False is False`는 `(False is False) and (False is False)`와 같습니다.
+- `True is False == False`는 `True is False and False == False`와 같으며 구문의 첫 부분 (`True is False`)가 `False`로 평가되기 때문에 전체 표현식의 결과는 `False`가 됩니다.
+- `1 > 0 < 1`은 `1 > 0 and 0 < 1`과 같아 `True`가 계산됩니다.
+- 표현식 `(1 > 0) < 1`은 `True < 1`과 같으며
+ ```py
+ >>> int(True)
+ 1
+ >>> True + 1 #예제와는 관련이 없지만, 재미를 위해서입니다.
+ 2
+ ```
+ 즉, `1 < 1`의 결과는 `False`입니다.
+
+---
+
+### ▶ `is` 연산자를 안 쓰는 방법
+
+
+
+아래 예제는 인터넷에서 매우 유명한 예제로 퍼져있습니다.
+
+1\.
+
+```py
+>>> a = 256
+>>> b = 256
+>>> a is b
+True
+
+>>> a = 257
+>>> b = 257
+>>> a is b
+False
+```
+
+2\.
+
+```py
+>>> a = []
+>>> b = []
+>>> a is b
+False
+
+>>> a = tuple()
+>>> b = tuple()
+>>> a is b
+True
+```
+
+3\.
+**출력 결과**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+True
+```
+
+**출력 결과 (파이썬 3.7.x)**
+
+```py
+>>> a, b = 257, 257
+>> a is b
+False
+```
+
+#### 💡 설명:
+
+**`is`와 `==`의 차이점**
+
+- `is` 연산자는 연산자 양쪽이 같은 객체를 참조하고 있는지를 확인합니다. (즉, 둘이 진짜로 같은지를 확인합니다).
+- `==` 연산자는 양쪽의 값을 비교하여 이 둘이 같은지를 확인합니다.
+- 그래서 `is`는 참조의 동등을, `==`는 값의 동등을 확인합니다. 다음 예제로 정리해보면,
+ ```py
+ >>> class A: pass
+ >>> A() is A() # 이 둘은 메모리상에 다른 곳에 있는 두 빈 객체입니다.
+ False
+ ```
+
+**`256`은 존재하는 객체이지만 `257`은 아닙니다**
+
+파이썬을 시작하게 되면, `-5`부터 `256`까지의 수들은 할당됩니다. 이 수들은 많이 사용되기 때문에 미리 준비하는 것입니다.
+
+https://docs.python.org/3/c-api/long.html 에서 인용한 글입니다.
+
+> 현 구현은 -5부터 256까지의 정수들을 담는 배열을 만듭니다. 만약 이 범위 안에 있는 정수를 만들게 되면 이미 존재하는 객체의 참조를 반환합니다. 그래서 1의 값을 바꾸는 것이 가능할 것입니다. 아마도 이 경우는 파이썬의 행동은 정의되지 않을 것입니다. :-)
+
+```py
+>>> id(256)
+10922528
+>>> a = 256
+>>> b = 256
+>>> id(a)
+10922528
+>>> id(b)
+10922528
+>>> id(257)
+140084850247312
+>>> x = 257
+>>> y = 257
+>>> id(x)
+140084850247440
+>>> id(y)
+140084850247344
+```
+
+여기서 인터프리터는 `y = 257`을 실행할 때 위에서 벌써 `257`을 가지는 정수를 만들었다는 것을 알 정도로 똑똑하지 않아서 메모리에 새로운 객체를 만들게 됩니다.
+
+빈 튜플과 같이 다른 **변하지 않는** 객체에 대해서도 비슷한 최적화가 적용됩니다. 배열은 변할 수 있어서, `[] is []`는 항상 `False`를 반환하고 `() is ()`는 항상 `True`를 반환합니다. 이는 두 번째 예제를 성명합니다. 이제 세 번쨰로 넘어가볼까요?
+
+**같은 줄에서 같은 값으로 초기화할 때 `a`와 `b` 둘 다 같은 객체를 참조합니다.**
+
+**출력 결과**
+
+```py
+>>> a, b = 257, 257
+>>> id(a)
+140640774013296
+>>> id(b)
+140640774013296
+>>> a = 257
+>>> b = 257
+>>> id(a)
+140640774013392
+>>> id(b)
+140640774013488
+```
+
+- a와 b가 같은 줄에서 `257`로 할당될 때, 파이썬 인터프리터는 새로운 객체를 만듦과 동시에 두 번째 변수가 참조하게 됩니다. 이것을 다른 줄에서 한다면, 인터프리터는 `257`이 이미 있는지 알지 못합니다.
+
+- 이 현상은 컴파일러 최적화이고 특별히 인터랙티브 환경에서만 적용됩니다. 인터프리터에 두 줄을 입력하게 되면, 각각 컴파일되며, 각각 최적화됩니다. 만약 이를 `.py`파일에서 시도한다면, 한 번에 컴파일되기 때문에 이 현상을 보지 못합니다. 이 최적화는 정수에만 국한된 것이 아니라 문자열("문자열은 가끔 헷갈려요"를 확인해보세요.)과 실수와 같이 변하지 않는 자료 구조에도 적용됩니다.
+
+ ```py
+ >>> a, b = 257.0, 257.0
+ >>> a is b
+ True
+ ```
+
+- 왜 파이썬 3.7에서는 작동되지 않나요? 간단히 말하자면 컴파일러 최적화는 구현하기 나름이기 때문입니다. (즉, 버전이나 운영체제 등에 따라 바뀔 수 있어요) 아직 이 문제를 일으킨 정확한 이유를 찾지 못했지만, 이 [이슈](https://github.com/satwikkansal/wtfpython/issues/100)를 확인해보세요.
+
+---
+
+### ▶ 해시 브라우니
+
+
+
+1\.
+
+```py
+some_dict = {}
+some_dict[5.5] = "JavaScript"
+some_dict[5.0] = "Ruby"
+some_dict[5] = "Python"
+```
+
+**출력 결과:**
+
+```py
+>>> some_dict[5.5]
+"JavaScript"
+>>> some_dict[5.0] # "Python"이 "Ruby"를 사라지게 했네요.
+"Python"
+>>> some_dict[5]
+"Python"
+
+>>> complex_five = 5 + 0j
+>>> type(complex_five)
+complex
+>>> some_dict[complex_five]
+"Python"
+```
+
+그래서, 왜 파이썬이 여기저기서 발견되나요?
+
+#### 💡 설명
+
+- 파이썬 딕셔너리 키의 유일성은 키가 서로 동일한지가 아니라, 동등한 값으로 결정됩니다. 그래서 `5`, `5.0`, 그리고 `5 + 0j`가 서로 다른 타입이더라도 같은 값을 가지기 때문에 같은 딕셔너리나 집합안에 있을 수 없습니다. 그 중 하나를 삽입하게 되면 (`KeyError`을 발생시키는 것이 아닌) 동등한 원소를 발견하는 것을 성공하게 됩니다.
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> 5 is not 5.0 is not 5 + 0j
+ True
+ >>> some_dict = {}
+ >>> some_dict[5.0] = "Ruby"
+ >>> 5.0 in some_dict
+ True
+ >>> (5 in some_dict) and (5 + 0j in some_dict)
+ True
+ ```
+- 이는 한 원소의 값을 지정할 때도 적용됩니다. 그래서 `some_dict[5] = "Python"`을 하게 되면, 파이썬은 동등한 키인 `5.0 -> "Ruby"`를 찾게 되고, 그 값을 덮어쓰게 됩니다.
+ ```py
+ >>> some_dict
+ {5.0: 'Ruby'}
+ >>> some_dict[5] = "Python"
+ >>> some_dict
+ {5.0: 'Python'}
+ ```
+- 그러면 우리는 어떻게 (`5.0` 대신) `5`인 키를 업데이트 할 수 있을까요? 이는 값을 업데이트 하는 것으로는 할 수 없지만, 먼저 키를 지우고 (`del some_dict[5.0]`), 다시 실수 `5.0` 대신 정수 `5`설정하는 (`some_dict[5]`) 것을 통해 할 수 있습니다. 이런 경우는 드물긴 하겠네요.
+
+- 파이썬은 `5.0`을 포함하고 있는 딕셔너리에서 어떻게 `5`를 찾았을까요? 파이썬은 모든 원소를 보지 않고 해시 함수를 이용해 상수 시간에 해결합니다. 파이썬이 `foo`라는 키를 딕셔너리에서 찾을 때, 먼저 `hash(foo)`를 계산합니다. (이는 상수시간에 계산됩니다). 파이썬에서는 동등한 값은 같은 해시값을 가지므로 ([관련 문서](https://docs.python.org/ko/3/reference/datamodel.html#object.__hash__)), `5`, `5.0`, 그리고 `5 + 0j`는 같은 해시 값을 가지게 됩니다.
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> hash(5) == hash(5.0) == hash(5 + 0j)
+ True
+ ```
+ **관련 정보:** 반대는 참이 아닐 수가 있습니다: 해시 값이 같은 두 객체는 동등하지 않을 수 있습니다. (이는 [해시 충돌](https://ko.wikipedia.org/wiki/%ED%95%B4%EC%8B%9C_%EC%B6%A9%EB%8F%8C)이라고 알려져 있고 해싱의 상수 시간의 성능을 저하시킬 수 있습니다. )
+
+---
+
+### ▶ 깊이 들어가면 우리는 다 똑같아.
+
+
+
+```py
+class WTF:
+ pass
+```
+
+**출력 결과:**
+
+```py
+>>> WTF() == WTF() # 두 인스턴스는 같을 수 없습니다
+False
+>>> WTF() is WTF() # 메모리 위에서도 다르네요
+False
+>>> hash(WTF()) == hash(WTF()) # 해시는 _당연히_ 같아야 합니다
+True
+>>> id(WTF()) == id(WTF())
+True
+```
+
+#### 💡 설명:
+
+- `id`가 호출되었을 때, 파이썬에서 `WTF` 객체를 만들고 `id` 함수로 넘겨줍니다. `id` 함수는 그 객체의 아이디(`id`, 메모리상의 위치)를 가져오고 객체를 버립니다. 객체는 파괴됩니다.
+- 만약 이것을 두 번 한다면, 파이썬은 두 번째 객체도 같은 메모리에 할당하게 됩니다. (CPython에서는) `id`가 메모리의 위치를 객체의 아이디로 쓰기 때문에 두 아이디는 같게 됩니다.
+- 그래서 객체의 아이디는 객체가 파괴되지 않는 한 고유합니다. 객체가 파괴된 후 또는 생성되기 이전에는 다른 것이 같은 아이디를 가질 수도 있습니다.
+- 그러면 왜 `is` 연산자는 `False`라고 출력했을까요? 이 코드를 보세요.
+
+ ```py
+ class WTF(object):
+ def __init__(self): print("I")
+ def __del__(self): print("D")
+ ```
+
+ **출력 결과:**
+
+ ```py
+ >>> WTF() is WTF()
+ I
+ I
+ D
+ D
+ False
+ >>> id(WTF()) == id(WTF())
+ I
+ D
+ I
+ D
+ True
+ ```
+
+ 여러분도 보셨다시피 객체들이 만들어지고 파괴되는 순서가 다르게 됩니다.
+
+---
+
+### ▶ 질서 속의 무질서 \*
+
+
+
+```py
+from collections import OrderedDict
+
+dictionary = dict()
+dictionary[1] = 'a'; dictionary[2] = 'b';
+
+ordered_dict = OrderedDict()
+ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+
+another_ordered_dict = OrderedDict()
+another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+
+class DictWithHash(dict):
+ """
+ __hash__ 마법을 구현하는 dict
+ """
+ __hash__ = lambda self: 0
+
+class OrderedDictWithHash(OrderedDict):
+ """
+ __hash__ 마법을 구현하는 OrderedDict
+ """
+ __hash__ = lambda self: 0
+```
+
+**출력 결과**
+
+```py
+>>> dictionary == ordered_dict # 만약 a == b 이고,
+True
+>>> dictionary == another_ordered_dict # b == c 이면
+True
+>>> ordered_dict == another_ordered_dict # 왜 c == a 가 아닐까요?
+False
+
+# 집합(set)은 유일한 원소들만 가지고 있으므로,
+# 위의 딕셔너리로 집합을 만들고 어떤 일이 일어나는지 알아봅시다.
+
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unhashable type: 'dict'
+
+# 딕셔너리는 __hash__가 구현되어있지 않으므로 그런것 같네요.
+# 그러면 위에서 만든 래퍼(wrapper) 클래스를 써봅시다.
+>>> dictionary = DictWithHash()
+>>> dictionary[1] = 'a'; dictionary[2] = 'b';
+>>> ordered_dict = OrderedDictWithHash()
+>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+>>> another_ordered_dict = OrderedDictWithHash()
+>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+1
+>>> len({ordered_dict, another_ordered_dict, dictionary}) # 순서를 바꿔봅시다.
+2
+```
+
+무슨 일이 벌어지고 있는거죠?
+
+#### 💡 설명:
+
+- `dictionary` 그리고 `ordered_dict`, `another_ordered_dict`가 자동적으로 같지 않은 이유는 `OrderedDict` 클래스에서 `__eq__` 메소드가 구현된 방식 때문입니다. [도큐먼트](https://docs.python.org/3/library/collections.html#ordereddict-objects)에서
+ > OrderedDict 오브젝트이 같음을 확인하는 방법은 순서와 관련이 있고 `list(od1.items())==list(od2.items())`로 구현되어 있습니다. `OrderedDict` 오프젝트와 다른 매핑 오프젝트들의 같음을 확인하는 방법은 순서와 상관있습니다.
+- 위와 같이 동작하는 이유는 `OrderedDict` 오브젝트가 바로 보통의 딕셔너리가 사용되는 곳에 사용될 수 있게 하기 위해서 입니다.
+- 그러면 왜 `set` 오브젝트에서 순서를 바꾼것이 왜 길이에 영향을 미친 것일까요? 같음을 확인하는 함수가 잘 구현되어 있지 않기 때문입니다. 집합(set)은 유일한 원소들의 순서를 고려하지 않은 자료구조이므로, 각 원소를 삽입하는 순서는 상관이 없어야 합니다. 하지만 이 경우에는 상관이 있네요. 한번 깊이 들어가 봅시다.
+
+ ```py
+ >>> some_set = set()
+ >>> some_set.add(dictionary) # 이것들은 위의 코드에서의 매핑 오브젝트들입니다
+ >>> ordered_dict in some_set
+ True
+ >>> some_set.add(ordered_dict)
+ >>> len(some_set)
+ 1
+ >>> another_ordered_dict in some_set
+ True
+ >>> some_set.add(another_ordered_dict)
+ >>> len(some_set)
+ 1
+
+ >>> another_set = set()
+ >>> another_set.add(ordered_dict)
+ >>> another_ordered_dict in another_set
+ False
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ >>> dictionary in another_set
+ True
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ ```
+
+ 그래서 `ordered_dict == another_ordered_dict`이 `False`이고 `ordered_dict`이 `another_set`안에 들어있었으므로 `another_ordered_dict in another_set`이 `False`인 모순으로 인해서 생긴일 입니다.
+
+---
+
+### ▶ 계속 시도해 보세요... \*
+
+
+
+```py
+def some_func():
+ try:
+ return 'from_try'
+ finally:
+ return 'from_finally'
+
+def another_func():
+ for _ in range(3):
+ try:
+ continue
+ finally:
+ print("Finally!")
+
+def one_more_func(): # 알았다!
+ try:
+ for i in range(3):
+ try:
+ 1 / i
+ except ZeroDivisionError:
+ # 여기서 에러를 발생시키고 반복문 밖에서 다뤄보도록 하죠
+ raise ZeroDivisionError("A trivial divide by zero error")
+ finally:
+ print("Iteration", i)
+ break
+ except ZeroDivisionError as e:
+ print("Zero division error ocurred", e)
+```
+
+**출력 결과:**
+
+```py
+>>> some_func()
+'from_finally'
+
+>>> another_func()
+Finally!
+Finally!
+Finally!
+
+>>> 1 / 0
+Traceback (most recent call last):
+ File "", line 1, in
+ZeroDivisionError: division by zero
+
+>>> one_more_func()
+Iteration 0
+
+```
+
+#### 💡 설명:
+
+- `return` 또는 `break`, `continue`가 "try-finally" 에서의 `try`문 안에서 실행된다면, `finally` 구문도 끝나기 전에 실행됩니다.
+- 함수의 리턴값은 마지막 리턴문에 의해 결정됩니다. `finally` 구문이 항상 마지막에 실행되므로, `finally` 안에 있는 리턴문이 실행됩니다.
+- 여기서 주의할 점은 만약 `finally` 구문 안에서 `return`이나 `break`이 있을 때 임시로 저장된 예외가 없어집니다.
+
+---
+
+### ▶ 무엇을 위해서(for)?
+
+
+
+```py
+some_string = "wtf"
+some_dict = {}
+for i, some_dict[i] in enumerate(some_string):
+ i = 10
+```
+
+**출력결과:**
+
+```py
+>>> some_dict # 딕셔너리가 나타나네요
+{0: 'w', 1: 't', 2: 'f'}
+```
+
+#### 💡 설명:
+
+- `for` 문은 [파이썬 문법](https://docs.python.org/3/reference/grammar.html)에서 정의되어 있습니다:
+
+ ```
+ for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
+ ```
+
+ `exprlist`는 할당되는 부분입니다. 이는 `{exprlist} = {next_value}`와 같다는 말이고 **각각의 원소에 대해 실행됩니다.**
+ 아래에 재미있는 예제가 있습니다.
+
+ ```py
+ for i in range(4):
+ print(i)
+ i = 10
+ ```
+
+ **출력 결과:**
+
+ ```
+ 0
+ 1
+ 2
+ 3
+ ```
+
+ 반복문이 한 번만 돌기를 기대했나요?
+
+ **💡 설명:**
+
+ - 반복문이 파이썬에서 특별하게 작동하기 때문에 할당문 `i = 10`은 절대로 반복문에 영향을 끼치지 않습니다. 매번 반복하기 전에 반복자(iterator)에 의해 제공된 값(위 경우는 `range(4)`)들이 변수(위 경우는 `i`)에 할당됩니다.
+
+- `enumerate(some_string)` 함수는 반복마다 새로운 값 `i` (하나씩 올라가는 카운터)와 `some_string`에 있는 문자 하나씩을 yield 합니다. 그리고 딕셔너리 `some_dict`에 키가 `i`인 값을 그 문자로 지정합니다. 반복문을 풀어보면 아래처럼 나올 수 있습니다:
+ ```py
+ >>> i, some_dict[i] = (0, 'w')
+ >>> i, some_dict[i] = (1, 't')
+ >>> i, some_dict[i] = (2, 'f')
+ >>> some_dict
+ ```
+
+---
+
+### ▶ 실행되는 시간의 차이
+
+
+
+1\.
+
+```py
+array = [1, 8, 15]
+# 전형적인 제너레이터(generator) 예제입니다
+gen = (x for x in array if array.count(x) > 0)
+array = [2, 8, 22]
+```
+
+**출력 결과:**
+
+```py
+>>> print(list(gen)) # 다른 값들은 어디 갔나요?
+[8]
+```
+
+2\.
+
+```py
+array_1 = [1,2,3,4]
+gen_1 = (x for x in array_1)
+array_1 = [1,2,3,4,5]
+
+array_2 = [1,2,3,4]
+gen_2 = (x for x in array_2)
+array_2[:] = [1,2,3,4,5]
+```
+
+**출력 결과:**
+
+```py
+>>> print(list(gen_1))
+[1, 2, 3, 4]
+
+>>> print(list(gen_2))
+[1, 2, 3, 4, 5]
+```
+
+3\.
+
+```py
+array_3 = [1, 2, 3]
+array_4 = [10, 20, 30]
+gen = (i + j for i in array_3 for j in array_4)
+
+array_3 = [4, 5, 6]
+array_4 = [400, 500, 600]
+```
+
+**출력 결과:**
+
+```py
+>>> print(list(gen))
+[401, 501, 601, 402, 502, 602, 403, 503, 603]
+```
+
+#### 💡 설명
+
+- [제너레이터](https://wiki.python.org/moin/Generators)에서는 `in` 부분은 선언할 때 실행되지만 조건문은 런타임에 실행됩니다.
+- 그래서 런타임 이전에, `array`가 `[2, 8, 22]`로 재할당 되고 `1`, `8`, `15`중에 `8`이 개수가 `0`보다 크므로 제너레이터는 오직 `8`만 yield 합니다.
+- 두 번쨰 예제의 `gen_1`과 `gen_2`의 출력 결과가 다른 이유는 `array_1`과 `array_2`가 재할당되는 방법이 다르기 때문입니다.
+- 첫 번째 경우에는, `array_1`이 새로운 객체인 `[1, 2, 3, 4, 5]`가 할당되고 `in` 부분은 선언할 때 계산되기 때문에 계속 이전 객체인 `[1, 2, 3, 4]`를 가지고 있습니다.
+- 두 번째 경우에는, `array_2`에 슬라이스 객체가 할당될 때 이전의 객체인 `[1, 2, 3, 4]`를 `[1, 2, 3, 4, 5]`로 변화시킵니다. 따라서 `g2`와 `array_2` 모두 같은 (새롭게 `[1, 2, 3, 4, 5]`로 업데이트된) 객체를 가리키고 있습니다.
+- 좋아요, 그런데 지금까지의 로직을 살펴보면, 세 번째 예제의 `list(g)`의 값이 `[11, 21, 31, 12, 22, 32, 13, 23, 33]` 가 되어야 하는 것이 아닌가요? (왜냐하면 `array_3`과 `array_4`가 `array_1`처럼 행동할 테니까요). (오직) `array_4`의 값만이 업데이트되는 이유는 [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details)에서 설명되어 있습니다
+
+ > for 구문의 가장 바깥쪽 부분만 바로 계산되고, 다른 구문들은 제너레이터가 실행될때까지 참조되는 것이 없습니다.
+
+---
+
+### ▶ `is not ...` 은 `is (not ...)`이 아니다
+
+
+
+```py
+>>> 'something' is not None
+True
+>>> 'something' is (not None)
+False
+```
+
+#### 💡 설명
+
+- `is not` 은 단일 이진 연산자이며, 이는 `is`와 `not`을 따로 사용하는 것과는 다른 기능을 합니다.
+- `is not` 연산자는 양쪽의 변수가 동일한 객체를 가리키면 `False`를, 아니면 `True`로 나타납니다.
+- In the example, `(not None)` evaluates to `True` since the value `None` is `False` in a boolean context, so the expression becomes `'something' is True`.
+
+---
+
+### ▶ X가 첫 번째 시도에서 승리하는 틱택토!
+
+
+
+```py
+# row를 초기화합니다
+row = [""] * 3 #row i['', '', '']
+# board를 만듭니다
+board = [row] * 3
+```
+
+**출력 결과:**
+
+```py
+>>> board
+[['', '', ''], ['', '', ''], ['', '', '']]
+>>> board[0]
+['', '', '']
+>>> board[0][0]
+''
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['X', '', ''], ['X', '', '']]
+```
+
+우리는 세 개의 `"X"`를 할당하지 않았습니다. 그랬나요?
+
+#### 💡 설명:
+
+다음 시각화 자료는 `row` 변수를 초기화할 때 메모리에서 어떠한 일이 일어나는지 보여줍니다.
+
+![image](../images/tic-tac-toe/after_row_initialized.png)
+
+그리고 다음은 `row`를 곱하여 `board`를 초기화할 때, 메모리에서 일어나는 일입니다. (각각의 원소 `board[0]`, `board[1]` 그리고 `board[2]`는 `row`가 참조한 동일한 리스트의 참조자입니다.)
+
+![image](../images/tic-tac-toe/after_board_initialized.png)
+
+위와 같은 현상은 `row`를 사용하지 않고 `board`를 생성하여 해결할 수 있습니다. (이 [이슈](https://github.com/satwikkansal/wtfpython/issues/68)에서 질문되었습니다).
+
+```py
+>>> board = [['']*3 for _ in range(3)]
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['', '', ''], ['', '', '']]
+```
+
+---
+
+### ▶ 달라붙는 출력 함수
+
+
+
+1\.
+
+```py
+funcs = []
+results = []
+for x in range(7):
+ def some_func():
+ return x
+ funcs.append(some_func)
+ results.append(some_func()) # 함수를 호출하고 있다는 것을 놓치지 마세요.
+
+funcs_results = [func() for func in funcs]
+```
+
+**출력 결과:**
+
+```py
+>>> results
+[0, 1, 2, 3, 4, 5, 6]
+>>> funcs_results
+[6, 6, 6, 6, 6, 6, 6]
+```
+
+`funcs`에 `some_func`를 추가하기 전의 `x`값은 항상 달랐는데도, 모든 함수가 6을 리턴합니다.
+
+2\.
+
+```py
+>>> powers_of_x = [lambda x: x**i for i in range(10)]
+>>> [f(2) for f in powers_of_x]
+[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
+```
+
+#### 💡 설명
+
+- 반복문 내에서 반복문의 변수를 사용하는 함수를 정의하면, 함수의 클로저는 변수의 값이 아니라 변수 자체에 바인딩됩니다. 따라서 모든 함수가 그 변수에 마지막으로 할당된 값을 사용하게 되죠.
+
+- 원하는 결과를 얻고 싶다면, 반복문의 변수를 함수의 인자로서 넘겨주면 됩니다. **이게 왜 되는 걸까요?** 이렇게 하면 변수가 함수의 스코프 내에서 다시 정의되기 때문입니다.
+
+ ```py
+ funcs = []
+ for x in range(7):
+ def some_func(x=x):
+ return x
+ funcs.append(some_func)
+ ```
+
+ **출력 결과:**
+
+ ```py
+ >>> funcs_results = [func() for func in funcs]
+ >>> funcs_results
+ [0, 1, 2, 3, 4, 5, 6]
+ ```
+
+---
+
+### ▶ 닭이 먼저일까, 달걀이 먼저일까 \*
+
+
+
+1\.
+
+```py
+>>> isinstance(3, int)
+True
+>>> isinstance(type, object)
+True
+>>> isinstance(object, type)
+True
+```
+
+그래서, "궁극적인" 기본 클래스는 뭘까요? 혼란스러운 점은 이게 끝이 아닙니다.
+
+2\.
+
+```py
+>>> class A: pass
+>>> isinstance(A, A)
+False
+>>> isinstance(type, type)
+True
+>>> isinstance(object, object)
+True
+```
+
+3\.
+
+```py
+>>> issubclass(int, object)
+True
+>>> issubclass(type, object)
+True
+>>> issubclass(object, type)
+False
+```
+
+#### 💡 설명
+
+- `type`은 파이썬의 [메타클래스](https://realpython.com/python-metaclasses/)입니다.
+- 파이썬에서 **모든 것은** `object`입니다. 이는 클래스와 인스턴스 모두에게 해당됩니다.
+- `type` 클래스는 `object` 클래스의 메타클래스이고, (`type`을 포함하는) 모든 클래스는 직접적으로든 간접적으로든 `object`를 상속합니다.
+- `object`와 `type` 중에서 진짜 기본 클래스는 존재하지 않습니다. 위의 코드들이 야기하는 혼란은 우리가 이런 관계들(`issubclass`와 `isinstance`)을 파이썬 클래스의 관점에서 생각하고 있기 때문에 발생합니다. `object`와 `type`의 관계는 순수 파이썬만으로는 재현할 수 없습니다. 정확히 말하자면, 아래의 관계는 순수 파이썬만으로 재현하는 것이 불가능합니다.
+ - 클래스 A는 클래스 B의 인스턴스이고, 클래스 B는 클래스 A의 인스턴스입니다.
+ - 클래스 A는 자기 자신의 인스턴스입니다.
+- `object`와 `type`의 이러한 관계(서로가 서로와 자기 스스로의 인스턴스인 것)를 가질 수 있는 건 구현 수준에서의 "편법"이 사용되었기 때문입니다.
+
+---
+
+### ▶ 서브 클래스의 관계
+
+
+
+**출력 결과:**
+
+```py
+>>> from collections import Hashable
+>>> issubclass(list, object)
+True
+>>> issubclass(object, Hashable)
+True
+>>> issubclass(list, Hashable)
+False
+```
+
+서브 클래스의 관계는 삼단논법을 따라야 하지 않나요? (즉 `A`가 `B`의 서브 클래스이고 `B`가 `C`의 서브 클래스이면 `A`는 `C`의 서브 클래스 _이여야만_ 합니다)
+
+#### 💡 설명:
+
+- 파이썬에서는 서브 클래스의 관계가 삼단논법을 따를 필요가 없습니다. 아무나 자유롭게 메타클래스에 자신만의 `__subclasscheck__`를 만들 수 있습니다.
+- `issubclass(cls, Hashable)`이 호출되면, `cls`나 이의 조상 클래스에서 거짓이 아닌 "`__hash__`" 메소드를 찾습니다.
+- `object`가 해싱할 수 있고 `list`는 해싱할 수 없기 때문에, 상속되었다고 보기 힘듭니다.
+- 더 자세한 정보는 [여기](https://www.naftaliharris.com/blog/python-subclass-intransitivity/)에서 찾아볼 수 있습니다.
+
+---
+
+### ▶ 메소드의 같음과 동일함
+
+
+
+1.
+
+```py
+class SomeClass:
+ def method(self):
+ pass
+
+ @classmethod
+ def classm(cls):
+ pass
+
+ @staticmethod
+ def staticm():
+ pass
+```
+
+**출력 결과:*
+
+```py
+>>> print(SomeClass.method is SomeClass.method)
+True
+>>> print(SomeClass.classm is SomeClass.classm)
+False
+>>> print(SomeClass.classm == SomeClass.classm)
+True
+>>> print(SomeClass.staticm is SomeClass.staticm)
+True
+```
+
+`classm`을 두 번 접근했을 때, 동등한 객체이지만 _같지는_ 않은 객체가 되네요?
+`SomeClass`의 인스턴스는 어떻게 되는지 한 번 볼까요?:
+
+> 여기서 동등하다는 것은 메모리 상 같은 위치에 있다는 것이고, 같다는 것은 단순하게 값이 같다는 의미입니다.
+
+2.
+
+```py
+o1 = SomeClass()
+o2 = SomeClass()
+```
+
+**출력 결과:**
+
+```py
+>>> print(o1.method == o2.method)
+False
+>>> print(o1.method == o1.method)
+True
+>>> print(o1.method is o1.method)
+False
+>>> print(o1.classm is o1.classm)
+False
+>>> print(o1.classm == o1.classm == o2.classm == SomeClass.classm)
+True
+>>> print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm)
+True
+```
+
+`SomeClass`의 같은 인스턴스는 `classm`와 `method`를 두 번 접근했을 때, 동등하지만 _같지는_ 않은 객체를 생성합니다.
+
+#### 💡 설명
+
+- 함수는 [디스크립터](https://docs.python.org/ko/3/howto/descriptor.html) 입니다.
+함수가 속성으로 접근되었을 때, 디스크립터가 호출되고, 객체를 가지고 속성으로 가지고 있는 함수를 "바인딩" 하는 메소드 객체를 생성합니다.
+만약 호출되면 메소드는 그 함수를 호출하고, 내부적으로 바인드된 객체를 첫 번째 인자로 넘겨줍니다.
+(이 원리로 인해 직접적으로 넘기지 않고 `self`를 첫 번째 인자로 받을 수 있는 것입니다.)
+
+```py
+>>> o1.method
+>
+```
+
+- 속성에 여러번 접근할 때마다 매번 메소드 객체를 만들게 됩니다!
+그래서 `o1.method is o2.method`는 참일 수가 없는 것이죠.
+(인스턴스와 반대되게) 함수를 클래스 속성으로 접근하게 되면 메소드를 생성하지 않게 됩니다.
+그래서 `SomeClass.method is SomeClass.method`는 항상 참입니다.
+
+```py
+>>> SomeClass.method
+
+```
+
+- `classmethod`는 함수를 클래스 메소드로 변환시킵니다. 클래스 메소드는 접근할때 객체 대신에 객체의 _class_ (타입)을 바인드한 메소드 객체를 생성하는 디스크럽터 입니다.
+
+```py
+>>> o1.classm
+>
+```
+
+- 함수와는 달리, `classmethods`는 클래스 속성으로 접근할 때 (타입이 아닌 클래스를 바인드 합니다) 새로운 메소드를 생성합니다.
+그래서 `SomeClass.classm is SomeClass.classm`는 거짓입니다.
+
+```py
+>>> SomeClass.classm
+>
+```
+
+- 메소드 객체가 같다는 것은 두 함수가 같고 바인드 되어 있는 객체가 같음을 의미합니다.
+그래서 메모리상 같은 객체가 아님에도 `o1.method == o1.method`는 같습니다.
+- `staticmethod`는 함수를 그대로 리턴하는 "no-op" (아무 일도 하지 않는) 디스크립터를 통해 함수를 변형시킵니다.
+어떠한 객체도 생성되지 않으며, `is`로 비교하는 것은 참이 됩니다.
+
+```py
+>>> o1.staticm
+
+>>> SomeClass.staticm
+
+```
+
+- 파이썬에서 인스턴스 메소드를 호출할 때마다 인자들을 매번 변경하고 맨 앞에 `self`를 넣어주면서 새로운 "메소드" 객체를 만드는 것은 속도에 나쁘게 영향을 끼치게 됩니다.
+CPython 3.7은 임시 메소드 객체를 만들지 않고 해결하는 새로운 명령코드를 만들어서 이 문제를 [해결했습니다.](https://bugs.python.org/issue26110).
+이 방법은 함수가 실제로 호출 될 때 사용되므로, 여기 있는 코드들에 영향을 끼치지 않고 여전히 메소드를 생성합니다 :)
+
+---
+
+### ▶ 참 거짓의 반복 \*
+
+
+
+```py
+>>> all([True, True, True])
+True
+>>> all([True, True, False])
+False
+
+>>> all([])
+True
+>>> all([[]])
+False
+>>> all([[[]]])
+True
+```
+
+왜 이런 참 거짓의 반복이 일어날까요?
+
+#### 💡 설명:
+
+- `all`함수의 구현은 다음과 같습니다
+
+- ```py
+ def all(iterable):
+ for element in iterable:
+ if not element:
+ return False
+ return True
+ ```
+
+- `all([])`은 비어있으므로 `True`를 반환합니다.
+- `all([[]])`은 `not[]`는 True이며 이는 `not False`와 같고, `iterable` 안에 있는 리스트가 비어 있기 때문에 `False`를 반환합니다.
+- `all([[[]]])`와 더 많이 겹친 경우는 `not [[]]`, `not [[[]]]`... 이 `not True`와 동일 하므로 모두 `True`를 반환합니다.
+
+---
+
+### ▶ 놀라운 콤마
+
+
+
+**출력 결과 (< 3.6):**
+
+```py
+>>> def f(x, y,):
+... print(x, y)
+...
+>>> def g(x=4, y=5,):
+... print(x, y)
+...
+>>> def h(x, **kwargs,):
+ File "", line 1
+ def h(x, **kwargs,):
+ ^
+SyntaxError: invalid syntax
+
+>>> def h(*args,):
+ File "", line 1
+ def h(*args,):
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 설명:
+
+- 파이썬 함수의 정규 파라미터 리스트에 콤마를 남기는 것이 항상 허용되는 것은 아닙니다.
+- 파이썬에서 전달인자 리스트는 선행 콤마(leading commas)들과 후행 콤마(trailing commas)들로 부분적으로 정의되어 있습니다. 이러한 충돌이 콤마가 가운데에 끼게되는 현상을 만들게 되고 결국 아무 규칙에도 맞지 않게 됩니다.
+- **참고:** 후행 콤마 문제는 [파이썬 3.6에서 고쳐졌습니다](https://bugs.python.org/issue9232). 이 [포스트](https://bugs.python.org/issue9232#msg248399)에서는 파이썬에서의 후행 콤마들의 다양한 사용법들이 간결하게 논의하고 있습니다.
+
+---
+
+### ▶ 문자열과 백슬래시
+
+
+
+**출력 결과:**
+
+```py
+>>> print("\"")
+"
+
+>>> print(r"\"")
+\"
+
+>>> print(r"\")
+File "", line 1
+ print(r"\")
+ ^
+SyntaxError: EOL while scanning string literal
+
+>>> r'\'' == "\\'"
+True
+```
+
+#### 💡 설명
+
+- 보통 파이썬 문자열에서 백슬래시는 특별한 의미의 문자(작은 따옴표나 큰 따옴표, 그리고 백슬래시 그 자체)를 이스케이프하는데 사용됩니다.
+ ```py
+ >>> "wt\"f"
+ 'wt"f'
+ ```
+- 원시 문자열 리터럴(raw string literal, 접두사 `r`로 나타난다)에서는 백슬래시들이 그대로 출력되지만 그 특성도 그대로 적용됩니다.
+
+ ```py
+ >>> r'wt\"f' == 'wt\\"f'
+ True
+ >>> print(repr(r'wt\"f')
+ 'wt\\"f'
+
+ >>> print("\n")
+
+ >>> print(r"\\n")
+ '\\n'
+ ```
+
+- 즉, 이는 파서가 원시 문자열에서 백슬래시와 만나면 그 뒤에 문자가 나오기를 예상한다는 것입니다. 그리고 이러한 경우(`print(r"\")`)에서는 백슬래시가 뒤의 따옴표에서 이스케이프하여 파서는 끝나는 따옴표를 찾지 못합니다(따라서 `SyntaxError`이 발생합니다). 이 이유로 원시 문자열의 끝에서 백슬래시를 사용할 수 없습니다.
+
+---
+
+### ▶ 매듭이 아니야!
+
+
+
+```py
+x = True
+y = False
+```
+
+**출력 결과:**
+
+```py
+>>> not x == y
+True
+>>> x == not y
+ File "", line 1
+ x == not y
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 설명:
+
+- 연산자 우선순위는 표현식이 실행되는 것에 영향을 주고 파이썬에서는 `==` 연산자는 `not` 연산자보다 우선순위가 높습니다.
+- 그래서 `not x == y`는 `not (x == y)`와 같고 이는 `not (True == False)`와 같게 되므로 최종적으로 `True`가 됩니다.
+- 하지만 `x == not y`는 `SyntaxError`를 발생시키게 됩니다. 왜냐하면 이는 보통 생각하는 `x == (not y)`가 아니라 `(x == not) y`로 해석되기 때문입니다.
+- 파서는 `not` 토큰이 `not in` 연산자의 일부라고 예상하지만 (왜냐하면 `==`와 `not in` 연산자가 같은 연산자 우선순위를 가지고 있기 때문입니다) `not` 뒤에 `in` 토큰을 찾을 수 없기 때문에 `SyntaxError`를 발생시킵니다.
+
+---
+
+### ▶ 반쪽 3중 따옴표 문자열
+
+
+
+**출력 결과:**
+
+```py
+>>> print('wtfpython''')
+wtfpython
+>>> print("wtfpython""")
+wtfpython
+>>> # 다음 표현식은 `SyntaxError`를 발생시킵니다.
+>>> # print('''wtfpython')
+>>> # print("""wtfpython")
+ File "", line 3
+ print("""wtfpython")
+ ^
+SyntaxError: EOF while scanning triple-quoted string literal
+```
+
+#### 💡 설명:
+
+- 파이썬은 암묵적 [문자열 리터럴 병합 연산](https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation) 연산을 지원합니다, 예시로,
+ ```
+ >>> print("wtf" "python")
+ wtfpython
+ >>> print("wtf" "") # 또는 "wtf"""
+ wtf
+ ```
+- `'''` 와 `"""` 는 파이썬에서 문자열 구분 기호로 파이썬은 후에 문자열 끝맺음 3중 따옴표를 찾지만 찾지 못해 SyntaxError를 발생시킵니다.
+
+---
+
+### ▶ 불린의 문제점이 뭐야?
+
+
+
+1\.
+
+```py
+# 다양한 데이터 타입 속 불린의 개수와 정수의 개수를 세는 간단한 예제입니다.
+mixed_list = [False, 1.0, "some_string", 3, True, [], False]
+integers_found_so_far = 0
+booleans_found_so_far = 0
+
+for item in mixed_list:
+ if isinstance(item, int):
+ integers_found_so_far += 1
+ elif isinstance(item, bool):
+ booleans_found_so_far += 1
+```
+
+**출력 결과:**
+
+```py
+>>> integers_found_so_far
+4
+>>> booleans_found_so_far
+0
+```
+
+2\.
+
+```py
+>>> some_bool = True
+>>> "wtf" * some_bool
+'wtf'
+>>> some_bool = False
+>>> "wtf" * some_bool
+''
+```
+
+3\.
+
+```py
+def tell_truth():
+ True = False
+ if True == False:
+ print("I have lost faith in truth!")
+```
+
+**출력 (< 3.x):**
+
+```py
+>>> tell_truth()
+I have lost faith in truth!
+```
+
+#### 💡 설명:
+
+- 파이썬에서 `bool`은 `int`의 서브클래스입니다.
+ ```py
+ >>> issubclass(bool, int)
+ True
+ >>> issubclass(int, bool)
+ False
+ ```
+- 추가로, `True`와 `False`는 `int`의 인스턴스입니다.
+
+ ```py
+ >>> isinstance(True, int)
+ True
+ >>> isinstance(False, int)
+ True
+ ```
+
+- `True`의 정수 값은 `1`이고 `False`의 정수 값은 `0`입니다.
+
+ ```py
+ >>> int(True)
+ 1
+ >>> int(False)
+ 0
+ ```
+
+- StackOverflow [답변](https://stackoverflow.com/a/8169049/4354153)을 통해 이러한 현상이 발생하는 이유에 대해서 알아보세요.
+
+- 초기에, 파이썬은 `bool` 타입이 없었습니다. (사람들은 0을 거짓으로 쓰고 1과 같은 0이 아닌 수를 참으로 사용했습니다) `True` 그리고 `False`, `bool` 타입은 2.x 버전에서 추가되었지만 호환성을 위해 `True`와 `False`는 상수로 만들어질 수 없었습니다. 이들은 내장 변수이고 다시 할당하는 것이 가능했습니다.
+
+- 파이썬 3은 호환이 안되기 때문에, 문제가 해결되었고 마지막 코드는 파이썬 3.x 에서는 작동하지 않습니다!
+
+---
+
+### ▶ 클래스 속성과 인스턴스 속성
+
+
+
+1\.
+
+```py
+class A:
+ x = 1
+
+class B(A):
+ pass
+
+class C(A):
+ pass
+```
+
+**Output:**
+
+```py
+>>> A.x, B.x, C.x
+(1, 1, 1)
+>>> B.x = 2
+>>> A.x, B.x, C.x
+(1, 2, 1)
+>>> A.x = 3
+>>> A.x, B.x, C.x # C.x 는 바뀌었지만, B.x 는 바뀌지 않았습니다.
+(3, 2, 3)
+>>> a = A()
+>>> a.x, A.x
+(3, 3)
+>>> a.x += 1
+>>> a.x, A.x
+(4, 3)
+```
+
+2\.
+
+```py
+class SomeClass:
+ some_var = 15
+ some_list = [5]
+ another_list = [5]
+ def __init__(self, x):
+ self.some_var = x + 1
+ self.some_list = self.some_list + [x]
+ self.another_list += [x]
+```
+
+**Output:**
+
+```py
+>>> some_obj = SomeClass(420)
+>>> some_obj.some_list
+[5, 420]
+>>> some_obj.another_list
+[5, 420]
+>>> another_obj = SomeClass(111)
+>>> another_obj.some_list
+[5, 111]
+>>> another_obj.another_list
+[5, 420, 111]
+>>> another_obj.another_list is SomeClass.another_list
+True
+>>> another_obj.another_list is some_obj.another_list
+True
+```
+
+#### 💡 설명:
+
+- 클래스 변수들과 클래스 인스턴스 안에 있는 변수들은 내부에서 클래스 객체의 딕셔너리들로 처리됩니다. 현재 클래스의 딕셔너리에 변수 이름이 없는 경우, 부모 클래스에서 해당 이름을 검색합니다.
+- '+=' 연산자는 새로운 객체를 생성하지 않고 그 자리에서 가변 객체를 수정합니다. 따라서, 한 인스턴스의 속성을 바꾸는 것은 다른 인스턴스들과 클래스 속성에도 영향을 미칩니다.
+
+---
+
+### ▶ yielding None
+
+
+
+```py
+some_iterable = ('a', 'b')
+
+def some_func(val):
+ return "something"
+```
+
+**출력 결과 (<= 3.7.x):**
+
+```py
+>>> [x for x in some_iterable]
+['a', 'b']
+>>> [(yield x) for x in some_iterable]
+ at 0x7f70b0a4ad58>
+>>> list([(yield x) for x in some_iterable])
+['a', 'b']
+>>> list((yield x) for x in some_iterable)
+['a', None, 'b', None]
+>>> list(some_func((yield x)) for x in some_iterable)
+['a', 'something', 'b', 'something']
+```
+
+#### 💡 설명:
+
+- 이것은 CPython에서 제너레이터와 컴프리헨션에서 `yield`를 처리할 때 생기는 버그입니다.
+- 소스와 설명은 여기서 찾아볼 수 있습니다: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions
+- 관련된 버그 리포트: https://bugs.python.org/issue10544
+- 파이썬 3.8 이상의 버전에서는 리스트 컴프리헨션의 내부에 `yield`를 허용하지 않고 `SyntaxError`를 발생시킵니다.
+
+---
+
+### ▶ Yielding from... return! \*
+
+
+
+1\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ yield from range(x)
+```
+
+**출력 결과 (> 3.3):**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+`"wtf"`이 어디로 갓나요? `yield from`의 특수한 효과 때문일까요? 확인해봅시다.
+
+2\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ for i in range(x):
+ yield i
+```
+
+**출력 결과:**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+같은 결과입니다. 이것도 효과가 없습니다.
+
+#### 💡 설명:
+
+- 파이썬 3.3 이후의 버전부터, 내부의 제너레이터와 함께 `return`문을 사용할 수 있게 되었습니다 (이것을 참고하세요 [PEP380](https://www.python.org/dev/peps/pep-0380/)). [공식 문서](https://www.python.org/dev/peps/pep-0380/#enhancements-to-stopiteration)에서도 말하고 있습니다.
+
+> "... 제너레이터 내부의 `return expr`는 제너레이터가 종료될 때 `StopIteration(expr)`을 발생시킵니다."
+
+- `some_func(3)`의 경우, `return`문으로 인해 처음부터 `StopIteration`이 발생합니다. `StopIteration` 예외는 자동으로 `list(...)` 래퍼와 `for` 루프의 내부에 잡히게 됩니다. 따라서 위의 두 코드의 결과는 빈 리스트가 됩니다.
+
+- 제너레이터의 `some_func`에서 `["wtf"]`을 얻으려면 `StopIteration` 예외를 잡아야 합니다.
+
+ ```py
+ try:
+ next(some_func(3))
+ except StopIteration as e:
+ some_string = e.value
+ ```
+
+ ```py
+ >>> some_string
+ ["wtf"]
+ ```
+
+---
+
+### ▶ Nan-재귀성 \*
+
+
+
+1\.
+
+```py
+a = float('inf')
+b = float('nan')
+c = float('-iNf') # 이 문자열은 대소문자를 구분하지 않습니다
+d = float('nan')
+```
+
+**출력 결과:**
+
+```py
+>>> a
+inf
+>>> b
+nan
+>>> c
+-inf
+>>> float('some_other_string')
+ValueError: could not convert string to float: some_other_string
+>>> a == -c # inf==inf
+True
+>>> None == None # None == None
+True
+>>> b == d # 하지만 nan!=nan 입니다
+False
+>>> 50 / a
+0.0
+>>> a / a
+nan
+>>> 23 + b
+nan
+```
+
+2\.
+
+```py
+>>> x = float('nan')
+>>> y = x / x
+>>> y is y # 정체성은 유지됩니다
+True
+>>> y == y # y와 같은 값은 아닙니다
+False
+>>> [y] == [y] # 하지만 y를 리스트로 감싸면 같은 값이 됩니다
+True
+```
+
+#### 💡 설명:
+
+- `'inf'`와 `'nan'`은 대소문자를 구분하지 않는 특수한 문자열로, 명시적으로 `float` 타입으로 형 변환할 때 수학적 "무한대"와 "숫자가 아님"을 표현할 때 사용합니다.
+
+- IEEE 표준 `NaN != NaN`을 따르면 파이썬에서 컬렉션 요소들의 재귀성 가정이 깨지게 됩니다. 만약 `x`가 `list`와 같은 컬렉션의 일부면 비교와 같은 구현들은 `x == x`라는 가정에 기반합니다. 이런 가정 때문에 두 요소를 비교할 때 정체성을 먼저 비교하고 (속도가 더 빠르기 때문입니다) 정체성이 일치하지 않을 때만 값을 비교합니다. 다음의 코드가 이것들을 더 확실하게 만들어 줄 겁니다,
+
+ ```py
+ >>> x = float('nan')
+ >>> x == x, [x] == [x]
+ (False, True)
+ >>> y = float('nan')
+ >>> y == y, [y] == [y]
+ (False, True)
+ >>> x == y, [x] == [y]
+ (False, False)
+ ```
+
+ `x`와 `y`의 정체성이 다르기 때문에 값이 고려되고, 비교 결과 `False`가 반환됩니다.
+
+- 흥미로운 읽을거리: [Reflexivity, and other pillars of civilization](https://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/)
+
+---
+
+### ▶ 불변을 변형하기!
+
+
+
+여러분이 파이썬에서 참조가 어떻게 작동하는지 안다면 이건 당연해 보일 수 있습니다.
+
+```py
+some_tuple = ("A", "tuple", "with", "values")
+another_tuple = ([1, 2], [3, 4], [5, 6])
+```
+
+**출력 결과:**
+
+```py
+>>> some_tuple[2] = "change this"
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple[2].append(1000) #이건 에러를 만들지 않습니다
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000])
+>>> another_tuple[2] += [99, 999]
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000, 99, 999])
+```
+
+하지만 저는 튜플이 변경 불가능하다 생각했습니다...
+
+#### 💡 설명:
+
+- https://docs.python.org/ko/3/reference/datamodel.html 을 인용하면
+
+ > 불변 시퀸스
+ >
+ > 불변 시퀀스 형의 객체는 일단 만들어진 후에는 변경될 수 없다. (만약 다른 객체로의 참조를 포함하면, 그 객체는 가변일 수 있고, 변경될 수 있다; 하지만, 불변 객체로부터 참조되는 객체의 집합 자체는 변경될 수 없다.)
+
+- `+=` 연산자는 리스트를 그 자리에서 변경합니다. 그 항목 할당이 동작하지 않지만, 예외 발생 시 그 항목은 이미 그 자리에서 변경되었습니다.
+
+---
+
+### ▶ 외부 범위에서 사라지는 변수
+
+
+
+```py
+e = 7
+try:
+ raise Exception()
+except Exception as e:
+ pass
+```
+
+**출력 결과 (Python 2.x):**
+
+```py
+>>> print(e)
+# 아무것도 출력하지 않습니다
+```
+
+**출력 결과 (Python 3.x):**
+
+```py
+>>> print(e)
+NameError: name 'e' is not defined
+```
+
+#### 💡 설명:
+
+- 출처: https://docs.python.org/ko/3/reference/compound_stmts.html#except
+
+ 예외가 `as target`을 사용해서 대입될 때, `except`절 끝에서 삭제됩니다. 이것은 마치
+
+ ```py
+ except E as N:
+ foo
+ ```
+
+ 가 이렇게 변환되는 것과 같습니다
+
+ ```py
+ except E as N:
+ try:
+ foo
+ finally:
+ del N
+ ```
+
+ 이것은 except 절 후에 참조하려면 예외를 다른 이름에 대입해야 한다는 뜻입니다. 예외를 제거하는 이유는, 그것에 첨부된 트레이스백으로 인해, 스택 프레임과 참조 순환을 형성해서 다음 가비지 수거가 일어나기 전까지 그 프레임의 모든 지역 변수들을 잡아두기 때문입니다.
+
+- 해당 절들은 파이썬에서 범위가 정해지지 않았습니다. 예시의 모든 것이 동일한 범위에 존재하며 `except` 절의 실행으로 `e` 변수가 삭제되었습니다. 별도의 내부 범위를 가지고 있는 함수들도 마찬가지인 데 다음의 예시가 보여줍니다:
+
+ ```py
+ def f(x):
+ del(x)
+ print(x)
+
+ x = 5
+ y = [5, 4, 3]
+ ```
+
+ **출력 결과:**
+
+ ```py
+ >>>f(x)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>>f(y)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> x
+ 5
+ >>> y
+ [5, 4, 3]
+ ```
+
+- 파이썬 2.x에서는 `e` 변수가 `Exception()` 인스턴스에 할당되므로 출력하려 할 때 아무것도 출력하지 않습니다.
+
+ **출력 결과 (Python 2.x):**
+
+ ```py
+ >>> e
+ Exception()
+ >>> print e
+ # 아무것도 출력되지 않았습니다!
+ ```
+
+---
+
+### ▶ 미스테리한 키 타입 형 변환
+
+
+
+```py
+class SomeClass(str):
+ pass
+
+some_dict = {'s': 42}
+```
+
+**출력 결과:**
+
+```py
+>>> type(list(some_dict.keys())[0])
+str
+>>> s = SomeClass('s')
+>>> some_dict[s] = 40
+>>> some_dict # 예상: 두개의 다른 key-value 쌍
+{'s': 40}
+>>> type(list(some_dict.keys())[0])
+str
+```
+
+#### 💡 설명:
+
+- `SomeClass`는 `str` 클래스의 `__hash__` 메소드를 상속받기 때문에 객체 `s`와 문자열 `"s"`는 같은 값을 갖게 됩니다.
+- `SomeClass`은 `str` 클래스의 `__eq__` 메소드도 상속받기 때문에 `SomeClass("s") == "s"`의 결과는 `True`가 됩니다.
+- 두 객체는 같은 값으로 해싱되어 동일하기 때문에 딕셔너리에서 같은 키로 표현됩니다.
+- 원하는 동작을 위해 `SomeClass`의 `__eq__` 메소드를 다시 정의할 수 있습니다.
+
+ ```py
+ class SomeClass(str):
+ def __eq__(self, other):
+ return (
+ type(self) is SomeClass
+ and type(other) is SomeClass
+ and super().__eq__(other)
+ )
+
+ # 수정된 __eq__를 정의할 때 파이썬은 자동으로 __hash__ 메소드를
+ # 상속받는 것을 중단합니다. 그래서 정의해줄 필요가 있습니다.
+ __hash__ = str.__hash__
+
+ some_dict = {'s':42}
+ ```
+
+ **출력 결과:**
+
+ ```py
+ >>> s = SomeClass('s')
+ >>> some_dict[s] = 40
+ >>> some_dict
+ {'s': 40, 's': 42}
+ >>> keys = list(some_dict.keys())
+ >>> type(keys[0]), type(keys[1])
+ (__main__.SomeClass, str)
+ ```
+
+---
+
+### ▶ 여러분이 맞출 수 있는지 한번 볼까요?
+
+
+
+```py
+a, b = a[b] = {}, 5
+```
+
+**출력 결과:**
+
+```py
+>>> a
+{5: ({...}, 5)}
+```
+
+#### 💡 설명:
+
+- [파이썬 언어 레퍼런스](https://docs.python/org/ko/2/reference/simple_stmts.html#assignment-statements)에 따르면, 대입문의 구조는 다음과 같은 형태를 보입니다.
+ ```
+ (target_list "=")+ (expression_list | yield_expression)
+ ```
+ 그리고
+
+> 대입문은 표현식 목록 (이것이 하나의 표현식일 수도, 쉼표로 분리된 목록일 수도 있는데, 후자의 경우는 튜플이 만들어진다는 것을 기억하라) 의 값을 구하고, 왼쪽에서 오른쪽으로, 하나의 결과 객체를 타깃 목록의 각각에 대입한다.
+
+- `(target_list "=")+`의 `+`는 **하나 이상의** 타깃 목록이 있을 수 있음을 의미합니다. 예제의 경우 타깃 목록은 `a, b`, `a[b]` 입니다. (표현식 목록은 정확하게 하나이며 예제의 경우 `{}, 5` 입니다.)
+
+- 표현식 목록을 평가한 후 해당 값은 **왼쪽에서 오른쪽으로** 타깃 목록에 풀어지게 됩니다. 예제의 경우 먼저 `{}, 5`의 튜플이 `a, b`로 풀리고 `a = {}`, `b = 5`가 됩니다.
+
+- `a`는 이제 변경 가능한 객체인 `{}`에 할당됩니다.
+
+- 두 번째 타깃 목록은 `a[b]`입니다. (여러분은 `a`와 `b`가 구문 이전에 정의되지 않아 에러가 발생할 거라 예상할 수 있습니다. 하지만 우리는 방금 `a`에 `{}`, `b`에 `5`를 대입한 사실을 기억하세요)
+
+- 이제 딕셔너리에 있는 키 `5`를 튜플 `({}, 5)`로 설정하여 순환 참조를 생성합니다. (출력의 `{...}`는 `a`가 이미 참조하고 있는 객체를 가리킵니다.) 순화 참조의 다른 간단한 예는 다음과 같습니다.
+
+ ```py
+ >>> some_list = some_list[0] = [0]
+ >>> some_list
+ [[...]]
+ >>> some_list[0]
+ [[...]]
+ >>> some_list is some_list[0]
+ True
+ >>> some_list[0][0][0][0][0][0] == some_list
+ True
+ ```
+
+ 우리의 예제에서도 이것과 비슷합니다. (`a[b][0]`은 `a`와 같은 객체입니다.)
+
+- 요약하자면, 예제를 다음과 같이 나눌 수 있습니다.
+ ```py
+ a, b = {}, 5
+ a[b] = a, b
+ ```
+ 그리고 순환 참조는 `a[b][0]`이 `a`와 동일한 객체라는 사실에 의해 정당화 될 수 있습니다.
+ ```py
+ >>> a[b][0] is a
+ True
+ ```
+
+---
+
+---
+
+## "미끄러운 비탈길" 단원
+
+### ▶ 딕셔너리가 반복 중일 때 수정하기
+
+
+
+```py
+x = {0: None}
+
+for i in x:
+ del x[i]
+ x[i+1] = None
+ print(i)
+```
+
+**출력 결과 (Python 2.7- Python 3.5):**
+
+```
+0
+1
+2
+3
+4
+5
+6
+7
+```
+
+정확히 **8**번 돌고 멈춥니다.
+
+#### 💡 설명:
+
+- 딕셔너리가 반복될 때 동시에 편집할 수 있는 것은 지원되지 않습니다.
+- 8번 반복되는 이유는 더 많은 키를 소유하기 위해 딕셔너리가 크기를 조정하는 지점이기 때문입니다. (우리는 8개의 삭제 항목들이 있으므로, 크기의 조정이 필요합니다) 이는 실제 구현의 세부사항입니다.
+- 삭제된 키를 처리하는 과정과 크기의 조정이 이루어지는 시점은 Python의 구현에 따라 다를 수 있습니다.
+- 따라서, 파이썬 2.7 - 3.5 이외의 버전의 경우, 실행 횟수가 8과 다를 수 있습니다. (하지만 횟수가 어떻던 간에, 실행할 때 마다 동일한 결과입니다) [여기](https://github.com/satwikkansal/wtfpython/issues/53) 또는 StackOverflow의 [이 스레드](https://stackoverflow.com/questions/44763802/bug-in-python-dict)에서 이에 관한 토론을 찾을 수 있습니다.
+- 파이썬 3.7.6 이상에서는 이것을 시도할 경우 `RuntimeError: dictionary keys changed during iteration` 예외를 보여줍니다.
+
+---
+
+### ▶ 완강한 `del` 연산자
+
+
+
+
+```py
+class SomeClass:
+ def __del__(self):
+ print("Deleted!")
+```
+
+**출력 결과:**
+1\.
+
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x # "Deleted!"를 출력해야 합니다
+>>> del y
+Deleted!
+```
+
+휴, 드디어 삭제되었습니다. 여러분은 처음의 `x` 삭제에서 `__del__`이 호출되지 않은 것을 생각하실 수도 있습니다. 이제 예제를 살짝 비틀어 봅시다.
+
+2\.
+
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x
+>>> y # y가 존재하는지 확인합니다
+<__main__.SomeClass instance at 0x7f98a1a67fc8>
+>>> del y # 이전과 같이, "Deleted!"를 출력해야 합니다
+>>> globals() # 오, 그렇지 않네요. 우리의 전역변수를 확인해봅시다
+Deleted!
+{'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
+```
+
+좋습니다. 이제 삭제되었습니다 :confused:
+
+#### 💡 설명:
+
+- `del x`는 직접적으로 `x.__del__()`을 부르지 않습니다.
+- `del x`가 호출될 때, 파이썬은 `x`에 대한 참조 카운트를 하나씩 줄입니다. 그리고 `x.__del__()`은 x의 참조 카운트가 0에 도달할 때 실행됩니다.
+- 두번째 코드의 출력에서, `y.__del__()` 는 호출되지 않습니다. 왜냐하면 이전의 구문에 (`>>> y`) 대화형 인터프리터가 같은 객체에 대해 또 다른 참조를 만들고, 따라서 `del y`가 호출될 때 참조 카운트가 0에 도달하지 않습니다.
+- `globals`가 호출되면 존재하는 참조가 파괴돼, 이런 이유로 우리는 "Deleted!"가 출력되는 것을 볼 수 있습니다. (마침내!)
+
+---
+
+### ▶ 범위를 벗어난 변수
+
+
+
+1\.
+
+```py
+a = 1
+def some_func():
+ return a
+
+def another_func():
+ a += 1
+ return a
+```
+
+2\.
+
+```py
+def some_closure_func():
+ a = 1
+ def some_inner_func():
+ return a
+ return some_inner_func()
+
+def another_closure_func():
+ a = 1
+ def another_inner_func():
+ a += 1
+ return a
+ return another_inner_func()
+```
+
+**출력 결과:**
+
+```py
+>>> some_func()
+1
+>>> another_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+
+>>> some_closure_func()
+1
+>>> another_closure_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+```
+
+#### 💡 설명:
+
+- 범위 내의 변수에 할당하면, 해당 범위의 로컬 변수가 됩니다. 그래서 `a`는 `another_func`의 범위에 국한되지만 이전과 같은 범위에서 초기화 되지 않아 에러가 발생합니다.
+- `another_func`에서 외부 범위의 `a`를 변경하려면, `global` 키워드를 사용하세요.
+
+ ```py
+ def another_func()
+ global a
+ a += 1
+ return a
+ ```
+
+ **출력 결과:**
+
+ ```py
+ >>> another_func()
+ 2
+ ```
+
+- In `another_closure_func`, `a` becomes local to the scope of `another_inner_func`, but it has not been initialized previously in the same scope, which is why it throws an error.
+- To modify the outer scope variable `a` in `another_inner_func`, use the `nonlocal` keyword. The nonlocal statement is used to refer to variables defined in the nearest outer (excluding the global) scope.
+
+ ```py
+ def another_func():
+ a = 1
+ def another_inner_func():
+ nonlocal a
+ a += 1
+ return a
+ return another_inner_func()
+ ```
+
+ **Output:**
+
+ ```py
+ >>> another_func()
+ 2
+ ```
+
+- The keywords `global` and `nonlocal` tell the python interpreter to not delcare new variables and look them up in the corresponding outer scopes.
+
+- 짧지만 멋진 [이 가이드](http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html)를 읽고 네임스페이스와 범위 결정이 파이썬에서 작동하는 방법에 대해 알아보세요.
+
+---
+
+### ▶ 반복하는 동안 리스트의 아이템을 삭제하기
+
+
+
+```py
+list_1 = [1, 2, 3, 4]
+list_2 = [1, 2, 3, 4]
+list_3 = [1, 2, 3, 4]
+list_4 = [1, 2, 3, 4]
+
+for idx, item in enumerate(list_1):
+ del item
+
+for idx, item in enumerate(list_2):
+ list_2.remove(item)
+
+for idx, item in enumerate(list_3[:]):
+ list_3.remove(item)
+
+for idx, item in enumerate(list_4):
+ list_4.pop(idx)
+```
+
+**출력 결과:**
+
+```py
+>>> list_1
+[1, 2, 3, 4]
+>>> list_2
+[2, 4]
+>>> list_3
+[]
+>>> list_4
+[2, 4]
+```
+
+왜 출력 결과가 `[2, 4]`가 나오는지 알 수 있나요?
+
+#### 💡 설명:
+
+- 반복하고 있는 객체를 바꾸는 것은 좋은 생각이 아닙니다. 올바른 방법은 `list_3[:]`과 같이 복사본을 반복하는 것입니다.
+
+ ```py
+ >>> some_list = [1, 2, 3, 4]
+ >>> id(some_list)
+ 139798789457608
+ >>> id(some_list[:]) # 파이썬은 슬라이스된 리스트를 위해 새로운 객체를 생성하는 것을 알 수 있습니다.
+ 139798779601192
+ ```
+
+**`del`, `remove`, `pop`의 차이점**
+
+- `del var_name`은 로컬 또는 전역 네임스페이스에서 `var_name`의 바인딩을 삭제합니다. (그래서 `list_1`은 영향 받지 않습니다)
+- `remove`는 특정 인덱스가 아닌 첫번째 일치하는 값을 삭제하는 경우 값을 찾을 수 없으면 `ValueError`를 일으킵니다.
+- `pop`은 특정 인덱스에서 요소를 제거하고 반환하며, 인덱스가 유효하지 않으면 `IndexError`를 일으킵니다.
+
+**왜 `[2, 4]`가 출력되나요?**
+
+- 리스트의 반복은 인덱스별로 이루어지며, `list_2` 또는 `list_4`에서 `1`을 삭제하면, 리스트는 `[2, 3, 4]`가 됩니다. 나머지 요소들은 인덱스가 낮아지게 되어 `2`는 인덱스 0, `3`은 인덱스 1이 됩니다. 다음번 반복은 인덱스 1 (요소 `3`이 됩니다)을 보게 되고, `2`는 건너뛰게 됩니다. 리스트 순서의 모든 대안 요소들도 비슷한 상황이 일어납니다.
+
+* 예제를 설명하는 StackOverflow [스레드](https://stackoverflow.com/questions/45946228/what-happens-when-you-try-to-delete-a-list-element-while-iterating-over-it) 를 참고하였습니다.
+* 파이썬의 딕셔너리에 관련된 비슷한 예제로 이 StackOverflow [스레드](https://stackoverflow.com/questions/45877614/how-to-change-all-the-dictionary-keys-in-a-for-loop-with-d-items) 도 참고하세요.
+
+---
+
+### ▶ 반복자의 손실되는 zip \*
+
+
+
+```py
+>>> numbers = list(range(7))
+>>> numbers
+[0, 1, 2, 3, 4, 5, 6]
+>>> first_three, remaining = numbers[:3], numbers[3:]
+>>> first_three, remaining
+([0, 1, 2], [3, 4, 5, 6])
+>>> numbers_iter = iter(numbers)
+>>> list(zip(numbers_iter, first_three))
+[(0, 0), (1, 1), (2, 2)]
+# 지금까지는 좋은데, 나머지도 압축해봅시다
+>>> list(zip(numbers_iter, remaining))
+[(4, 3), (5, 4), (6, 5)]
+```
+
+`numbers` 리스트에서 요소 `3`이 어디로 갔을까요?
+
+#### 💡 설명:
+
+- 파이썬의 [이 문서](https://docs.python.org/3.3/library/functions.html#zip)에서, zip 함수의 대략적인 구현을 살펴봅시다.
+ ```py
+ def zip(*iterables):
+ sentinel = object()
+ iterators = [iter(it) for it in iterables]
+ while iterators:
+ result = []
+ for it in iterators:
+ elem = next(it, sentinel)
+ if elem is sentinel: return
+ result.append(elem)
+ yield tuple(result)
+ ```
+- 그래서 이 함수는 임의의 수의 반복 가능한 객체를 모아 `next` 함수를 호출하여 각각의 항목을 `result` 리스트에 추가하고, 반복 가능한 객체 중 하나가 고갈될 때에 중지합니다.
+- 여기서 주의해야 할 점은 반복 가능한 객체들이 고갈될 때, `result` 리스트에 들어 있는 기존의 요소들이 폐기되는 것입니다. `numbers_iter` 내부의 `3`에 그러한 일이 일어났습니다.
+- `zip`을 사용하여 위와 같은 일을 처리하는 올바른 방법은 다음과 같습니다,
+ ```py
+ >>> numbers = list(range(7))
+ >>> numbers_iter = iter(numbers)
+ >>> list(zip(first_three, numbers_iter))
+ [(0, 0), (1, 1), (2, 2)]
+ >>> list(zip(remaining, numbers_iter))
+ [(3, 3), (4, 4), (5, 5), (6, 6)]
+ ```
+ zip의 첫번째 인자는 가장 적은 요소를 가지고 있어야 합니다.
+
+---
+
+### ▶ 루프 변수가 유출되고 있습니다!
+
+
+
+1\.
+
+```py
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**출력 결과:**
+
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+하지만 `x`는 루프의 밖에서 선언된 적이 없습니다...
+
+2\.
+
+```py
+# 이번엔 먼저 x를 초기화해봅시다
+x = -1
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**출력 결과:**
+
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+3\.
+
+**출력 결과 (Python 2.x):**
+
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+4
+```
+
+**출력 결과 (Python 3.x):**
+
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+1
+```
+
+#### 💡 설명:
+
+- 파이썬에서, for 루프는 루프의 스코프를 사용하고 정의된 루프 변수는 뒤로 남겨둡니다. 이전에 전역 네임스페이스에서 for 루프 변수를 명시적으로 정의한 경우에도 적용됩니다. 이 경우, 기존에 존재하는 변수를 다시 바인딩합니다.
+
+- 파이썬 2.x와 파이썬 3.x의 인터프리터의 출력 결과의 차이는 다음의 [파이썬 3.0의 새로운 기능들](https://docs.python.org/3/whatsnew/3.0.html) 변경 로그에서 확인할 수 있습니다:
+
+ > "리스트 컴프리헨션은 이제 `[... for var in item1, item2, ...]` 문법을 지원하지 않습니다. 대신 `[... for var in (item1, item2, ...)]`을 사용하세요. 또한 리스트 컴프리헨션은 다른 의미들을 가지고 있는점에 주목해야합니다: 그들은 `list()` 생성 표현식 생성자의 문법 설탕에 가깝고, 특히 루프 제어 변수들은 더 이상 범위 밖으로 유출되지 않습니다.
+
+---
+
+### ▶ 기본 가변인수를 조심하세요!
+
+
+
+```py
+def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+```
+
+**출력 결과:**
+
+```py
+>>> some_func()
+['some_string']
+>>> some_func()
+['some_string', 'some_string']
+>>> some_func([])
+['some_string']
+>>> some_func()
+['some_string', 'some_string', 'some_string']
+```
+
+#### 💡 설명:
+
+- 파이썬에서 함수의 기본 변경 가능한 인수는 함수가 호출될 때마다 실제로 초기화되지 않습니다. 대신, 최근에 할당된 값이 기본값으로 사용됩니다. `some_func`에 `[]`를 인수로 넘겨줄 때 `default_arg`의 기본값이 사용되지 않아 결과가 예상대로 나오게 됩니다.
+
+ ```py
+ def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+ **출력 결과:**
+
+ ```py
+ >>> some_func.__defaults__ #이건 함수에 대한 기본 인수값을 보여줍니다
+ ([],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string'],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ >>> some_func([])
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ ```
+
+- 변경 가능한 인수로 인한 버그를 피하는 일반적인 방법으로는 기본값으로 `None`을 지정한 후에 해당 인수에 어떠한 값이 들어오는지 확인하는 것입니다. 예시:
+
+ ```py
+ def some_func(default_arg=None):
+ if default_arg is None:
+ default_arg = []
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+---
+
+### ▶ 여러 예외들을 잡기
+
+
+
+```py
+some_list = [1, 2, 3]
+try:
+ # ``IndexError``를 일으킵니다
+ print(some_list[4])
+except IndexError, ValueError:
+ print("Caught!")
+
+try:
+ # ``ValueError``를 일으킵니다
+ some_list.remove(4)
+except IndexError, ValueError:
+ print("Caught again!")
+```
+
+**출력 결과 (Python 2.x):**
+
+```py
+Caught!
+
+ValueError: list.remove(x): x not in list
+```
+
+**출력 결과 (Python 3.x):**
+
+```py
+ File "", line 3
+ except IndexError, ValueError:
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 설명
+
+- 예외처리 구문에 여러 개의 예외를 처리하려면, 해당 예외들을 튜플로 묶어 첫 번째 인수로 넘겨줘야 합니다. 두 번째 인수는 선택적 이름으로, 주어진 경우 일어난 예외 인스턴스가 바인딩 됩니다. 예를 들어,
+
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ # ``ValueError``를 일으킵니다
+ some_list.remove(4)
+ except (IndexError, ValueError), e:
+ print("Caught again!")
+ print(e)
+ ```
+
+ **출력 결과 (Python 2.x):**
+
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+
+ **출력 결과 (Python 3.x):**
+
+ ```py
+ File "", line 4
+ except (IndexError, ValueError), e:
+ ^
+ IndentationError: unindent does not match any outer indentation level
+ ```
+
+- 쉼표로 예외에서 변수를 분리하는 방법은 이제는 사용되지 않으며 파이썬 3에서는 작동하지 않습니다; 이 경우 `as`를 사용해야 합니다. 예를 들어,
+
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ some_list.remove(4)
+
+ except (IndexError, ValueError) as e:
+ print("Caught again!")
+ print(e)
+ ```
+
+ **출력 결과:**
+
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+
+---
+
+### ▶ 같은 피연산자, 다른 이야기!
+
+
+
+1\.
+
+```py
+a = [1, 2, 3, 4]
+b = a
+a = a + [5, 6, 7, 8]
+```
+
+**출력 결과:**
+
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4]
+```
+
+2\.
+
+```py
+a = [1, 2, 3, 4]
+b = a
+a += [5, 6, 7, 8]
+```
+
+**출력 결과:**
+
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4, 5, 6, 7, 8]
+```
+
+#### 💡 설명:
+
+- `a += b` 는 항상 `a = a + b`와 같게 동작하지 않습니다. 클래스는 _`op=`_ 연산자를 _다르게_ 구현할 수 있으며, 리스트는 다음과 같습니다.
+
+- `a = a + [5,6,7,8]` 표현식은 새로운 리스트를 생성하여 새로운 리스트에 대한 `a`의 참조를 설정하므로, `b`는 바뀌지 않습니다.
+
+- `a += [5,6,7,8]` 표현식은 실제로 `a`와 `b`가 여전히 내부에서 수정된 목록을 가리키도록 하는 "확장" 함수에 대치됩니다.
+
+---
+
+### ▶ 이름 확인은 클래스 범위를 무시합니다
+
+
+
+1\.
+
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = (x for i in range(10))
+```
+
+**출력 결과:**
+
+```py
+>>> list(SomeClass.y)[0]
+5
+```
+
+2\.
+
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = [x for i in range(10)]
+```
+
+**출력 결과 (Python 2.x):**
+
+```py
+>>> SomeClass.y[0]
+17
+```
+
+**출력 결과 (Python 3.x):**
+
+```py
+>>> SomeClass.y[0]
+5
+```
+
+#### 💡 설명
+
+- 클래스 정의 내에서 중첩된 범위는 클래스 수준에서 바인딩 된 이름을 무시합니다.
+- 생성 표현식은 자체적인 범위를 갖습니다.
+- 파이썬 3.x부터는 리스트 컴프리헨션 또한 자체적인 범위를 갖습니다.
+
+---
+
+### ▶ 모래밭에서 바늘찾기 \*
+
+
+
+다음의 시나리오 중 하나 이상을 접해보지 못한 파이써니스트는 한 번도 만나본 적이 없습니다,
+
+1\.
+
+```py
+x, y = (0, 1) if True else None, None
+```
+
+**출력 결과:**
+
+```py
+>>> x, y # (0, 1)이 예상됩니다
+((0, 1), None)
+```
+
+2\.
+
+```py
+t = ('one', 'two')
+for i in t:
+ print(i)
+
+t = ('one')
+for i in t:
+ print(i)
+
+t = ()
+print(t)
+```
+
+**출력 결과:**
+
+```py
+one
+two
+o
+n
+e
+tuple()
+```
+
+3\.
+
+```
+ten_words_list = [
+ "some",
+ "very",
+ "big",
+ "list",
+ "that"
+ "consists",
+ "of",
+ "exactly",
+ "ten",
+ "words"
+]
+```
+
+**출력 결과**
+
+```py
+>>> len(ten_words_list)
+9
+```
+
+4\. 충분히 강하게 주장하지 않음
+
+```py
+a = "python"
+b = "javascript"
+```
+
+**출력 결과:**
+
+```py
+# 실패 메세지가 있는 assert 구문.
+>>> assert(a == b, "Both languages are different")
+# AssertionError가 일어나지 않습니다
+```
+
+5\.
+
+```py
+some_list = [1, 2, 3]
+some_dict = {
+ "key_1": 1,
+ "key_2": 2,
+ "key_3": 3
+}
+
+some_list = some_list.append(4)
+some_dict = some_dict.update({"key_4": 4})
+```
+
+**출력 결과:**
+
+```py
+>>> print(some_list)
+None
+>>> print(some_dict)
+None
+```
+
+6\.
+
+```py
+def some_recursive_func(a):
+ if a[0] == 0:
+ return
+ a[0] -= 1
+ some_recursive_func(a)
+ return a
+
+def similar_recursive_func(a):
+ if a == 0:
+ return a
+ a -= 1
+ similar_recursive_func(a)
+ return a
+```
+
+**출력 결과:**
+
+```py
+>>> some_recursive_func([5, 0])
+[0, 0]
+>>> similar_recursive_func(5)
+4
+```
+
+#### 💡 설명:
+
+- 1번에서, 예상되는 동작에 대한 올바른 구문은 `x, y = (0, 1) if True else (None, None)`입니다.
+
+- 2번에서, 예상되는 동작에 대한 올바른 구문은 `t = ('one',)` 또는 `t = 'one',` (콤마가 없음)입니다. 그렇지 않으면 인터프리터는 `t`를 `str`로 생각해 문자 별로 반복합니다.
+
+- `()`은 특별한 토큰이며 빈 `tuple`을 의미합니다.
+
+- 3번에서, 여러분들도 이미 알아 차렷겟지만, 리스트의 5번째 요소 (`"that"`)의 뒤에 콤마가 빠져있습니다. 그래서 암묵적인 문자열 리터럴의 연결에 의해,
+
+ ```py
+ >>> ten_words_list
+ ['some', 'very', 'big', 'list', 'thatconsists', 'of', 'exactly', 'ten', 'words']
+ ```
+
+- 4번째 코드에서 `AssertionError`가 일어나지 않은 이유는 `a == b` 표현식이 아닌 전체 튜플을 비교하기 때문입니다. 다음의 코드에서 이를 해결할 수 있습니다.
+
+ ```py
+ >>> a = "python"
+ >>> b = "javascript"
+ >>> assert a == b
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError
+
+ >>> assert (a == b, "Values are not equal")
+ :1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
+
+ >>> assert a == b, "Values are not equal"
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError: Values aren not equal
+ ```
+
+- 다섯 번째 코드에서, `list.append`, `dict.update`, `list.sort`또는 다른 것들과 같이 아이템의 순서/매핑 객체의 항목을 수정하는 대부분의 메소드입니다. 그 자리에서 객체를 수정한 후 `None`을 반환합니다. 이를 뒷받침하는 근거는 그 자리에서 연산을 시행할 수 있는 경우 객체의 사본을 만드는 것을 피해 성능을 향상하기 위함입니다. ([이것](http://docs.python.org/3/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list)을 참조하였습니다)
+
+- 마지막으로, `list`와 같은 가변 객체를 전달은 참조로 호출되는 반면, `int`와 같은 불변 객체는 값으로 호출됩니다.
+
+- 이런 자잘한 것들까지 알고 있으면 장기적으로 디버깅 시간을 절약할 수 있습니다.
+
+---
+
+### ▶ 나눠봅시다 \*
+
+
+
+```py
+>>> 'a'.split()
+['a']
+
+# 같은 결과입니다
+>>> 'a'.split(' ')
+['a']
+
+# 하지만
+>>> len(''.split())
+0
+
+# 이건 같지 않네요
+>>> len(''.split(' '))
+1
+```
+
+#### 💡 설명:
+
+- 처음에는 split의 기본 구분자가 공백 한 칸 `' '`인 것처럼 보이지만, [문서](https://docs.python.org/2.7/library/stdtypes.html#str.split)에 따르면
+ > sep 이 지정되지 않거나 None 이면, 다른 분할 알고리즘이 적용됩니다: 연속된 공백 문자는 단일한 구분자로 간주하고, 문자열이 선행이나 후행 공백을 포함해도 결과는 시작과 끝에 빈 문자열을 포함하지 않습니다. 결과적으로, 빈 문자열이나 공백만으로 구성된 문자열을 None 구분자로 나누면 [] 를 돌려줍니다.
+ > sep 이 주어지면, 연속된 구분자는 묶이지 않고 빈 문자열을 구분하는 것으로 간주합니다 (예를 들어, '1,,2'.split(',') 는 ['1', '', '2'] 를 돌려줍니다). sep 인자는 여러 문자로 구성될 수 있습니다 (예를 들어, '1<>2<>3'.split('<>') 는 ['1', '2', '3'] 를 돌려줍니다). 지정된 구분자로 빈 문자열을 나누면 [''] 를 돌려줍니다.
+- 다음 코드에서 앞뒤의 공백이 어떻게 처리되는지 알게 되면 명확해질 겁니다,
+ ```py
+ >>> ' a '.split(' ')
+ ['', 'a', '']
+ >>> ' a '.split()
+ ['a']
+ >>> ''.split(' ')
+ ['']
+ ```
+
+---
+
+### ▶ 제멋대로 가져오기 \*
+
+
+
+
+```py
+# File: module.py
+
+def some_weird_name_func_():
+ print("works!")
+
+def _another_weird_name_func():
+ print("works!")
+
+```
+
+**출력 결과**
+
+```py
+>>> from module import *
+>>> some_weird_name_func_()
+"works!"
+>>> _another_weird_name_func()
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name '_another_weird_name_func' is not defined
+```
+
+#### 💡 설명:
+
+- 와일드카드 import는 자주 사용하지 않는 것이 좋습니다. 와일드카드 import에 대해 명확한 첫 번째 이유는 언더스코어로 시작하는 이름이 import 되기 때문입니다. 이로 인해 런타임 중에 에러가 발생할 수 있습니다.
+- 만약 `from ... import a, b, c` 문법을 사용한다면, `NameError`는 발생하지 않을 것입니다.
+ ```py
+ >>> from module import some_weird_name_func_, _another_weird_name_func
+ >>> _another_weird_name_func()
+ works!
+ ```
+- 만약 정말로 와일드카드 import가 사용하고 싶다면, 와일드카드 import를 할 때 사용할 수 있는 공용 객체가 들어 있는 리스트인 `__all__`을 모듈 내에 정의해야 합니다.
+
+ ```py
+ __all__ = ['_another_weird_name_func']
+
+ def some_weird_name_func_():
+ print("works!")
+
+ def _another_weird_name_func():
+ print("works!")
+ ```
+
+ **출력 결과**
+
+ ```py
+ >>> _another_weird_name_func()
+ "works!"
+ >>> some_weird_name_func_()
+ Traceback (most recent call last):
+ File "", line 1, in
+ NameError: name 'some_weird_name_func_' is not defined
+ ```
+
+---
+
+### ▶ 다 정렬되었나요? \*
+
+
+
+```py
+>>> x = 7, 8, 9
+>>> sorted(x) == x
+False
+>>> sorted(x) == sorted(x)
+True
+
+>>> y = reversed(x)
+>>> sorted(y) == sorted(y)
+False
+```
+
+#### 💡 설명:
+
+- 파이썬에서 `sorted` 메소드는 항상 리스트를 반환하고, 리스트와 튜플의 비교는 항상 `False`를 반환합니다.
+
+- ```py
+ >>> [] == tuple()
+ False
+ >>> x = 7, 8, 9
+ >>> type(x), type(sorted(x))
+ (tuple, list)
+ ```
+
+- `sorted`와 달리 `reversed` 메소드는 반복자를 반환합니다. 왜 그럴까요? 왜냐하면 정렬은 반복자가 그 자리에서 변경되거나 추가적인 컨테이너(리스트)를 사용해야 하지만, 뒤집는 것은 단순히 끝 인덱스부터 처음까지 반복하면 되기 때문입니다.
+
+- 따라서 `sorted(y) == sorted(y)`를 비교하는 동안에, 처음의 `sorted()`가 호출되면 `y`의 반복자를 소모하고, 다음의 호출에는 빈 리스트가 반환됩니다.
+
+ ```py
+ >>> x = 7, 8, 9
+ >>> y = reversed(x)
+ >>> sorted(y), sorted(y)
+ ([7, 8, 9], [])
+ ```
+
+---
+
+### ▶ 자정은 존재하지 않나요?
+
+
+
+```py
+from datetime import datetime
+
+midnight = datetime(2018, 1, 1, 0, 0)
+midnight_time = midnight.time()
+
+noon = datetime(2018, 1, 1, 12, 0)
+noon_time = noon.time()
+
+if midnight_time:
+ print("Time at midnight is", midnight_time)
+
+if noon_time:
+ print("Time at noon is", noon_time)
+```
+
+**출력 결과 (< 3.5):**
+
+```py
+('Time at noon is', datetime.time(12, 0))
+```
+
+자정은 출력되지 않습니다.
+
+#### 💡 설명:
+
+파이썬 3.5 이전에, `datetime.time` 객체의 불리언 값은 UTC 기준으로 자정을 나타내는 경우 `False`로 간주하였습니다. 이는 `if obj:` 구문을 사용하우 `obj`가 null 또는 "비어있음"인지 확인하는 경우 오류가 발생하기 쉽습니다.
+
+---
+
+---
+
+## "숨겨진 보물들!" 단원
+
+이 단원에는 저 같은 초보자들이 (더 이상은 아니지만) 대부분 모르고 있는 파이썬에 대한 덜 알려지고 흥미로운 것들이 몇 가지 포함되어있습니다.
+
+### ▶ 파이썬, 날 날게해줄 수 있니?
+
+
+
+자, 여기 있습니다
+
+```py
+import antigravity
+```
+
+**출력 결과:**
+쉿... 이건 일급비밀이야.
+
+#### 💡 설명:
+
+- `antigravity` 모듈은 파이썬 개발자들이 추가한 몇 안 되는 이스터에그입니다.
+- `import antigravity` 는 파이썬에 대한 [고전 XKCD 만화](https://xkcd.com/353)을 웹 브라우저에 띄워줍니다.
+- 더 많은 것이 그 안에 있는데, **또 다른 이스터에그가 이스터에그 안에 있습니다**. [코드](https://github.com/python/cpython/blob/master/Lib/antigravity.py#L7-L17)를 보게 된다면, [XKCD의 geohashing 알고리즘](https://xkcd.com/426)을 구현하는 함수가 정의되어 있습니다.
+
+---
+
+### ▶ `goto`, 하지만 왜?
+
+
+
+```py
+from goto import goto, label
+for i in range(9):
+ for j in range(9):
+ for k in range(9):
+ print("I am trapped, please rescue!")
+ if k == 2:
+ goto .breakout # 깊게 중첩된 루프에서 탈출
+label .breakout
+print("Freedom!")
+```
+
+**출력 결과 (Python 2.3):**
+
+```py
+I am trapped, please rescue!
+I am trapped, please rescue!
+Freedom!
+```
+
+#### 💡 설명:
+
+- 파이썬에 `goto`가 추가된 버전은 2004년 4월 1일에 만우절 장난으로 [발표](https://mail.python.org/pipermail/python-announce-list/2004-April/002982.html)되었습니다.
+- 현재 버전의 파이썬은 이 모듈을 가지고 있지 않습니다.
+- 비록 이것이 작동하지만, 사용하지 마십시오. 파이썬에는 `goto`가 존재하지 않는 [이유](https://docs.python.org/3/faq/design.html#why-is-there-no-goto)가 있습니다.
+
+---
+
+### ▶ 마음 단단히 먹으세요!
+
+
+
+만약 여러분이 파이썬에서 스코프를 나타내기 위해 공백을 사용하는 것을 좋아하지 않는 사람 중 한 명이라면, C-스타일의 {}을 가져와 사용할 수 있습니다.
+
+```py
+from __future__ import braces
+```
+
+**출력 결과:**
+
+```py
+ File "some_file.py", line 1
+ from __future__ import braces
+SyntaxError: not a chance
+```
+
+중괄호? 절대 안돼! 만약 이게 실망스럽다면 자바를 사용하세요. 또 하나 놀라운 것은 `__future__`모듈에서 발생한 `SyntaxError`가 [코드](https://github.com/python/cpython/blob/master/Lib/__future__.py)의 어디에 있는지 찾을 수 있나요?
+
+#### 💡 설명:
+
+- `__future__` 모듈은 일반적으로 미래의 파이썬 버전에서 추가될 기능을 제공합니다. 하지만 이 특정한 맥락에서 "미래"는 아이러니합니다.
+- 이것은 이 문제에 대한 커뮤니티의 감정과 관련된 이스터에그입니다.
+- 그 코드는 실제로 [여기](https://github.com/python/cpython/blob/025eb98dc0c1dc27404df6c544fc2944e0fa9f3a/Python/future.c#L49) `future.c` 파일 안에 존재합니다.
+- CPython 컴파일러가 [future 구문](https://docs.python.org/3.3/reference/simple_stmts.html#future-statements)과 마주칠 때, 먼저 `future.c`에서 적절한 코드를 실행한 후 그걸 일반적인 구문으로 간주합니다.
+
+---
+
+### ▶ 평생 친근한 아저씨 같은 언어를 만나봅시다
+
+
+
+**출력 결과 (Python 3.x)**
+
+```py
+>>> from __future__ import barry_as_FLUFL
+>>> "Ruby" != "Python" # 이건 의심할 여지가 없습니다
+ File "some_file.py", line 1
+ "Ruby" != "Python"
+ ^
+SyntaxError: invalid syntax
+
+>>> "Ruby" <> "Python"
+True
+```
+
+또 시작이군.
+
+#### 💡 설명:
+
+- 이것은 2009년 4월 1일에 출시된 [PEP-401](https://www.python.org/dev/peps/pep-0401/) 와 관련이 있습니다. (이제 여러분은 무엇을 의미하는지 알 것입니다)
+- PEP-401의 일부를 인용하면
+
+ > 파이선 3.0의 != 비항등 연산자는 손가락의 고통을 유발하는 끔직한 실수라는 것을 인지하고, FLUFL은 유일한 문법으로 <> 다이아몬드 연산자를 복구시켰습니다.
+
+- 베리 아저씨가 PEP에서 공유한 것들은 더 많은데, [여기](https://www.python.org/dev/peps/pep-0401/) 서 읽을 수 있습니다.
+- 이것은 대화형 환경에서는 잘 작동하지만, 파이썬 파일을 통해서는 `SyntaxError`를 일으킵니다. ([이 이슈](https://github.com/satwikkansal/wtfpython/issues/94)를 읽어보세요) 하지만, 여러분이 구분을 `eval`이나 `compile`으로 감싼다면 잘 작동할 것입니다.
+ ```py
+ from __future__ import barry_as_FLUFL
+ print(eval('"Ruby" <> "Python"'))
+ ```
+
+---
+
+### ▶ 파이썬 조차 사랑이 복잡하다는 것을 이해합니다
+
+
+
+```py
+import this
+```
+
+잠깐, **this**가 뭔가요? `this`는 사랑입니다 :heart:
+
+**출력 결과:**
+
+```
+The Zen of Python, by Tim Peters
+
+Beautiful is better than ugly.
+Explicit is better than implicit.
+Simple is better than complex.
+Complex is better than complicated.
+Flat is better than nested.
+Sparse is better than dense.
+Readability counts.
+Special cases aren't special enough to break the rules.
+Although practicality beats purity.
+Errors should never pass silently.
+Unless explicitly silenced.
+In the face of ambiguity, refuse the temptation to guess.
+There should be one-- and preferably only one --obvious way to do it.
+Although that way may not be obvious at first unless you're Dutch.
+Now is better than never.
+Although never is often better than *right* now.
+If the implementation is hard to explain, it's a bad idea.
+If the implementation is easy to explain, it may be a good idea.
+Namespaces are one honking great idea -- let's do more of those!
+```
+
+이것은 the Zen of Python 입니다!
+
+```py
+>>> love = this
+>>> this is love
+True
+>>> love is True
+False
+>>> love is False
+False
+>>> love is not True or False
+True
+>>> love is not True or False; love is love # 사랑은 복잡합니다
+True
+```
+
+#### 💡 설명:
+
+- 파이썬의 `this` 모듈은 The Zen Of Python ([pep 20](https://www.python.org/dev/peps/pep-0020)) 을 위한 이스터에그입니다.
+- 그리고 이게 아주 흥미롭다고 생각하면, [this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py)의 구현을 확인해보세요. 흥미롭게도, **the code for the Zen을 스스로 위반합니다** (그리고 아마도 유일하게 이런 일이 있는 곳입니다).
+- `love is not True or False; love is love`라는 표현에 대해 아이러니하지만, 이것은 자기-설명적인 (그렇지 않다면, `is`와 `is not`에 관련된 예시를 봐주세요) 표현입니다.
+
+---
+
+### ▶ 네, 존재합니다!
+
+
+
+**반복문에 대한 `else` 조건**의 예로 다음과 같은게 있습니다:
+
+```py
+ def does_exists_num(l, to_find):
+ for num in l:
+ if num == to_find:
+ print("Exists!")
+ break
+ else:
+ print("Does not exist")
+```
+
+**출력 결과:**
+
+```py
+>>> some_list = [1, 2, 3, 4, 5]
+>>> does_exists_num(some_list, 4)
+Exists!
+>>> does_exists_num(some_list, -1)
+Does not exist
+```
+
+**예외 처리에 대한 `else` 조건**의 예는 다음과 같습니다,
+
+```py
+try:
+ pass
+except:
+ print("Exception occurred!!!")
+else:
+ print("Try block executed successfully...")
+```
+
+**출력 결과:**
+
+```py
+Try block executed successfully...
+```
+
+#### 💡 설명:
+
+- 모든 반복이 끝난 후 명시된 `break`가 없을 때, `else` 조건이 실행됩니다. "nobreak" 조건이라 생각할 수 있습니다.
+- try 블록 뒤의 `else` 조건은 `try` 문에서 try 블록이 성공적으로 완료된 후 도달하므로 "완료 조건"이라고도 합니다.
+
+---
+
+### ▶ Ellipsis \*
+
+
+
+```py
+def some_func():
+ Ellipsis
+```
+
+**출력 결과**
+
+```py
+>>> some_func()
+# 출력도 없고, 에러도 없다
+
+>>> SomeRandomString
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'SomeRandomString' is not defined
+
+>>> Ellipsis
+Ellipsis
+```
+
+#### 💡 설명
+
+- 파이썬에서, `Ellipsis`는 `...`에 해당하는 전역 내장 객체입니다.
+ ```py
+ >>> ...
+ Ellipsis
+ ```
+- Ellipsis 는 여러가지 목적으로 사용될 수 있는데,
+
+ - 아직 작성되지 않은 코드의 자리 표시자 (placeholder)로 사용될 수 있습니다 (`pass` 구문과 마찬가지로).
+ - 슬라이스 문법에서 남은 방향의 전체 슬레이스를 나타낼 수 있습니다.
+
+ ```py
+ >>> import numpy as np
+ >>> three_dimensional_array = np.arange(8).reshape(2, 2, 2)
+ array([
+ [
+ [0, 1],
+ [2, 3]
+ ],
+
+ [
+ [4, 5],
+ [6, 7]
+ ]
+ ])
+ ```
+
+ 우리의 `three_dimensional_array`는 배열의 배열의 배열입니다. 가장 안쪽 배열의 두번째 (1번 인덱스) 를 출력하고 싶다고 가정하면, 앞의 모든 차원을 생략하는데 Ellipsis를 사용할 수 있습니다.
+
+ ```py
+ >>> three_dimensional_array[:,:,1]
+ array([[1, 3],
+ [5, 7]])
+ >>> three_dimensional_array[..., 1] # Ellipsis 사용.
+ array([[1, 3],
+ [5, 7]])
+ ```
+
+ 참고: 이건 모든 차원에서 작동합니다. 여러분이 첫번째와 마지막 차원에서 슬라이스를 선택하고 중간의 값들을 무시하려면 이러한 방법이 있습니다. (`n_dimensional_array[firs_dim_slice, ..., last_dim_slice]`)
+
+ - [타입 힌트](https://docs.python.org/3/library/typing.html) 에서는 파입의 일부만 나타내기 위해 사용합니다. (`(Callable[..., int]` 또는 `Tuple[str, ...]`))
+ - Ellipsis를 기본 함수 인수로 ("인수가 전달되지 않음", "아무 값도 전달되지 않음"의 시나리오를 구분하기 위해) Ellipsis를 사용할 수 있습니다.
+
+---
+
+### ▶ Inpinity
+
+
+
+철자는 의도된 것입니다. 이것에 대한 수정사항을 보내지 마세요.
+
+**출력 결과 (Python 3.x):**
+
+```py
+>>> infinity = float('infinity')
+>>> hash(infinity)
+314159
+>>> hash(float('-inf'))
+-314159
+```
+
+#### 💡 설명:
+
+- 무한대의 해시는 10⁵ x π 입니다.
+- 흥미롭게도, 파이썬 3에서 `float('-inf')`의 해시는 "-10⁵ x π" 입니다. 반면에 파이썬 2에서는 "-10⁵ x e" 입니다.
+
+---
+
+### ▶ 망쳐봅시다
+
+
+
+1\.
+
+```py
+class Yo(object):
+ def __init__(self):
+ self.__honey = True
+ self.bro = True
+```
+
+**출력 결과:**
+
+```py
+>>> Yo().bro
+True
+>>> Yo().__honey
+AttributeError: 'Yo' object has no attribute '__honey'
+>>> Yo()._Yo__honey
+True
+```
+
+2\.
+
+```py
+class Yo(object):
+ def __init__(self):
+ # 이번엔 대칭적으로 해봅시다
+ self.__honey__ = True
+ self.bro = True
+```
+
+**출력 결과:**
+
+```py
+>>> Yo().bro
+True
+
+>>> Yo()._Yo__honey__
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'Yo' object has no attribute '_Yo__honey__'
+```
+
+왜 `Yo()._Yo__honey`가 동작했을까요?
+
+3\.
+
+```py
+_A__variable = "Some value"
+
+class A(object):
+ def some_func(self):
+ return __variable # 아직 아무것도 초기화되지 않았습니다
+```
+
+**출력 결과:**
+
+```py
+>>> A().__variable
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'A' object has no attribute '__variable'
+
+>>> A().some_func()
+'Some value'
+```
+
+#### 💡 설명:
+
+- [이름 망치기](https://en.wikipedia.org/wiki/Name_mangling) 는 서로 다른 네임스페이스 간의 이름이 충돌되는 것을 피하기 위해 사용됩니다.
+- 파이썬에서, 인터프리터는 클래스 멤버의 이름 중 `__` (언더스코어 두개 또는 "dunder") 로 시작하고 앞에 `_NameOfTheClass`로 하나 이상의 언더스코어로 끝나지 않는 이름을 수정 (망치기) 합니다.
+- 첫번째 코드에서 `__honey` 속성에 접근하기 위해 앞에 `_Yo`를 붙여야 했는데, 이는 다른 클래스에서 동일한 이름의 속성과 충돌되는 것을 막을 수 있습니다.
+- 하지만 왜 두번쨰 코드는 작동하지 않을까요? 왜냐하면 이름 망치기가 이름 끝의 언더스코어 두개를 제거합니다.
+- 세번째 코드또한 이름 망치기의 결과입니다. `return __variable` 구문의 `__variable`이 `_A_variable`로 바뀌었는데, 이는 우리가 스코프의 밖에서 선언한 변수의 이름이기도 합니다.
+- 또한, 망친 이름이 255자보다 길어지면, 잘리게 될겁니다.
+
+---
+
+---
+
+## "겉모습은 기만적입니다!" 단원
+
+### ▶ 줄 건너뛰기?
+
+
+
+**출력 결과:**
+
+```py
+>>> value = 11
+>>> valuе = 32
+>>> value
+11
+```
+
+뭐라고요?
+
+**참고:** 이를 재현하는 가장 쉬운 방법은 위의 코드에서 구문을 복사해서 파일/셸에 붙여넣는 것입니다.
+
+#### 💡 설명
+
+일부 비-서양의 문자들은 영어의 알파벳과 똑같아 보이지만 인터프리터에 의해 별개의 것으로 여겨집니다.
+
+```py
+>>> ord('е') # 키릴 문자 'e' (Ye)
+1077
+>>> ord('e') # 라틴 문자 'e', 영어에 사용되고 표준 키보드를 사용하여 타이핑한 것
+101
+>>> 'е' == 'e'
+False
+
+>>> value = 42 # 라틴 문자 e
+>>> valuе = 23 # 키릴 문자 'e', Python 2.x 인터프리터는 `SyntaxError`를 일으킵니다
+>>> value
+42
+```
+
+내장된 `ord()` 함수는 문자의 유니코드 [코드 포인트](https://en.wikipedia.org/wiki/Code_point) 를 반환하며, 키릴 문자 'e'와 라틴 문자 'e'의 다른 코드 위치는 예제의 동작이 옳음을 보여줍니다.
+
+---
+
+### ▶ 순간이동
+
+
+
+```py
+# 먼저 `pip install numpy`를 하세요.
+import numpy as np
+
+def energy_send(x):
+ # numpy 배열을 초기화합니다.
+ np.array([float(x)])
+
+def energy_receive():
+ # 빈 numpy 배열을 반환합니다.
+ return np.empty((), dtype=np.float).tolist()
+```
+
+**출력 결과:**
+
+```py
+>>> energy_send(123.456)
+>>> energy_receive()
+123.456
+```
+
+노벨상은 어디있나요?
+
+#### 💡 설명:
+
+- `energy_send` 함수에서 생성된 numpy 배열은 반환되지 않아 메모리 공간을 자유롭게 재할당할 수 있습니다.
+- `numpy.empty()`는 다시 초기화하지 않고 다음에 사용 가능한 메모리 슬롯을 반환합니다. 이 메모리 위치는 막 풀려난 것과 같습니다. (보통 그러나, 항상 그렇지는 않습니다.)
+
+---
+
+### ▶ 음, 뭔가 수상한데...
+
+
+
+```py
+def square(x):
+ """
+ 숫자의 합으로 제곱을 구하는 간단한 함수.
+ """
+ sum_so_far = 0
+ for counter in range(x):
+ sum_so_far = sum_so_far + x
+ return sum_so_far
+```
+
+**출력 결과 (Python 2.x):**
+
+```py
+>>> square(10)
+10
+```
+
+100이 아니여야 하나요?
+
+**참고:** 이걸 재현할 수 없는 경우 [mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py)를 셸에서 실행해보세요.
+
+#### 💡 설명
+
+- **탭과 스페이스를 혼용하지 마세요!** 예제의 반환 직전에 있는 문자는 "탭"이며 다른 곳의 들여쓰기는 "4 스페이스"로 되어있습니다.
+- 파이썬이 탭을 처리하는 방법입니다:
+
+ > 탭은 왼쪽에서 오른쪽으로 1~8개의 공백으로 치환되며 치환된 항목을 포함하여 총 문자 수가 8의 배수가 되어야 합니다.
+
+- 즉, `square` 함수의 마지막 줄에 있는 "탭"은 8개의 공백으로 바뀌어 루프 안으로 들어가게 됩니다.
+- 파이썬 3는 그럴 때 자동으로 오류를 발생시킬 만큼 친절합니다.
+
+ **출력 결과 (Python 3.x):**
+
+ ```py
+ TabError: inconsistent use of tabs and spaces in indentation
+ ```
+
+---
+
+---
+
+## "기타 등등" 단원
+
+### ▶ `+=` 가 더 빨라요
+
+
+
+```py
+# 3개의 문자열을 "+"을 사용해서:
+>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.25748300552368164
+# 3개의 문자열을 "+="을 사용해서:
+>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.012188911437988281
+```
+
+#### 💡 설명:
+
+- `s1 += s2 + s3`에서 `s1`과 같은 첫 번째 문자열은 전체 문자열을 계산하는 동안에 파괴되지 않기 때문에 두 개 이상의 연결된 문자열에 대해서 `+=`가 `+` 보다 빠릅니다.
+
+---
+
+### ▶ 거대한 문자열을 만들어봐요!
+
+
+
+```py
+def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s += "xyz"
+ assert len(s) == 3*iters
+
+def add_bytes_with_plus(iters):
+ s = b""
+ for i in range(iters):
+ s += b"xyz"
+ assert len(s) == 3*iters
+
+def add_string_with_format(iters):
+ fs = "{}"*iters
+ s = fs.format(*(["xyz"]*iters))
+ assert len(s) == 3*iters
+
+def add_string_with_join(iters):
+ l = []
+ for i in range(iters):
+ l.append("xyz")
+ s = "".join(l)
+ assert len(s) == 3*iters
+
+def convert_list_to_string(l, iters):
+ s = "".join(l)
+ assert len(s) == 3*iters
+```
+
+**출력 결과:**
+
+```py
+# 더 좋은 가독성을 위해 %timeit을 사용하여 ipython shell에서 실행했습니다.
+# 파이썬 shell/scriptm= 에서 timeit 모듈을 사용할 수 있습니다. 아래와 같은 방식입니다.
+# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
+
+>>> NUM_ITERS = 1000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS)
+124 µs ± 4.73 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS)
+211 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS)
+61 µs ± 2.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS)
+117 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS)
+10.1 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+반복 횟수를 10배로 늘렸습니다.
+
+```py
+>>> NUM_ITERS = 10000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linear increase in execution time
+1.26 ms ± 76.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS) # Quadratic increase
+6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS) # Linear increase
+645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS) # Linear increase
+1.17 ms ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS) # Linear increase
+86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+#### 💡 설명
+
+- [timeit](https://docs.python.org/3/library/timeit.html) 또는 [%timeit](https://ipython.org/ipython-doc/dev/interactive/magics.html#magic-timeit)에 대해 더 읽을 수 있습니다. 그것들은 코드의 실행 시간을 측정하는 데 사용됩니다.
+- 긴 문자열들을 생성하는데 `+` 을 사용하지 마세요. - 파이썬에서, `str` 은 변하지 않아서 좌우의 문자열들은 각각의 쌍들에 대해 새로운 문자열로 복사됩니다. 만약 길이 10의 문자열 4개를 연결한다면, 40개의 문자(character)만 복사하지 않고 (10+10) + ((10+10)+10) + (((10+10)+10)+10) = 90개의 문자(character)를 복사합니다. 문자열의 수와 길이가 증가함에 따라 상황은 이차적으로 악화합니다. (`add_bytes_with_plus` 함수로 실행 시간을 보였습니다.)
+- 그러므로, `.format.` 또는 `%` 문법을 사용하는 것을 권고합니다. (하지만, 매우 짧은 문자열들의 경우 `+` 보다 약간 느립니다.)
+- 더 좋은 방법으로, iterable 객체의 형태로 사용 가능한 콘텐츠가 있다면, 훨씬 더 빠른 `''.join(iterable_object)`을 사용할 수 있습니다.
+- `add_bytes_with_plus`와 달리 앞의 예에서 보여준 `+=` 최적화로 인해 `add_string_with_plus`는 실행 시간이 이차적으로 증가하지 않습니다. `s += "xyz"` 대신 `s = s + "x" + "y" + "z"` 이였다면 실행 시간이 이차적으로 증가했을 겁니다.
+
+ ```py
+ def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s = s + "x" + "y" + "z"
+ assert len(s) == 3*iters
+
+ >>> %timeit -n100 add_string_with_plus(1000)
+ 388 µs ± 22.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+ >>> %timeit -n100 add_string_with_plus(10000) # Quadratic increase in execution time
+ 9 ms ± 298 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+ ```
+
+- 거대한 문자열을 구성하고 만드는 많은 방법은 [Zen of Python](https://www.python.org/dev/peps/pep-0020/) 과 약간 대조적입니다. 이에 따르면,
+
+ > 어떤 문제든지 해결할 하나의 - 가급적이면 유일한 - 명백한 방법이 존재해야 합니다.
+
+---
+
+### ▶ `dict` 검색 속도 느려지게 하기 *
+
+```py
+some_dict = {str(i): 1 for i in range(1_000_000)}
+another_dict = {str(i): 1 for i in range(1_000_000)}
+```
+
+**출력 결과:**
+```py
+>>> %timeit some_dict['5']
+28.6 ns ± 0.115 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> some_dict[1] = 1
+>>> %timeit some_dict['5']
+37.2 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+
+>>> %timeit another_dict['5']
+28.5 ns ± 0.142 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> another_dict[1] # 존재하지 않는 키에 접근을 해볼까요
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 1
+>>> %timeit another_dict['5']
+38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+```
+왜 같은 검색의 속도가 느려질까요?
+
+#### 💡 설명:
++ CPython은 모든 타입의 키 (`str`, `int`, 모든 오브젝트 ...)에 대해 일반적인 딕셔너리 검색 함수가 있고 흔한 경우인 `str` 키로만 이루어져 있는 딕셔너리에 대한 함수가 있습니다.
++ (CPython에서 이름이 `lookdict_unicode` [소스](https://github.com/python/cpython/blob/522691c46e2ae51faaad5bbbce7d959dd61770df/Objects/dictobject.c#L841)) 함수는 (검색하려는 키를 포함해서) 모든 키가 문자열 인것을 알고, `__eq__` 메소드를 호출하는 대신 빠르고 간단한 문자열 비교를 사용합니다.
++ `dict` 인스턴스가 처음으로 `str`이 아닌 키로 접근되었을 때, 추후 검색은 일반적인 함수를 사용하도록 수정됩니다.
++ 이 과정은 특정 `dict` 인스턴스에 대해서 되돌릴 수 없으며, 키가 딕셔너리 안에 없어도 작동합니다. 그래서 실패한 검색도 같은 효과를 가지게 된 것입니다.
+
+### ▶ `dict` 인스턴스 부풀리기 \*
+
+
+
+```py
+import sys
+
+class SomeClass:
+ def __init__(self):
+ self.some_attr1 = 1
+ self.some_attr2 = 2
+ self.some_attr3 = 3
+ self.some_attr4 = 4
+
+
+def dict_size(o):
+ return sys.getsizeof(o.__dict__)
+
+```
+
+**출력 결과:** (파이썬 3.8, 다른 파이썬 3 버전은 조금 다를 수 있습니다.)
+
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104
+>>> dict_size(o2)
+104
+>>> del o1.some_attr1
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+>>> dict_size(o1)
+232
+```
+
+새로운 인터프리터에서 다시 시도해볼까요?:
+
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104 # 예상한 대로 나왔네요
+>>> o1.some_attr5 = 5
+>>> o1.some_attr6 = 6
+>>> dict_size(o1)
+360
+>>> dict_size(o2)
+272
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+```
+
+무엇이 이 딕셔너리들을 부풀리게 했을까요? 그리고 왜 새롭게 생성된 객체도 부풀려질까요?
+
+#### 💡 설명:
+
+- CPython은 다양한 딕셔너리에서 같은 "키" 객체를 재사용할 수 있습니다.
+이것은 특별히 키(인스턴스 속성)들이 주로 모든 인스턴스에서 비슷한 딕셔너리의 인스턴스의 메모리 사용량을 줄이기 위해서 [PEP 412](https://www.python.org/dev/peps/pep-0412/)에서 추가되었습니다.
+- 이 최적화는 인스턴스 딕셔너리에는 원활히 적용되는데, 몇 몇 가정이 만족되지 않게 되면 작동하지 않습니다.
+- 키를 공유하는 딕셔너리는 삭제를 지원하지 않습니다; 만약 어떤 인스턴스 속성이 삭제되었을 때, 그 딕셔너리는 "비공유"가 되고, 후의 그 클래스 인스턴스는 키를 공유하지 않게 됩니다.
+- 추가로, (새로운 키가 삽입되어서) 딕셔너리의 키들의 크기가 조정되었다면, 그 키가 그 딕셔너리에서만 사용되었을 경우에만 계속 공유된 상태를 유지합니다 (이것은 `__init__`에서 인스턴스를 처음 만들 때 "비공유" 상태가 되지 않고 많은 속성을 추가할 수 있게 합니다).
+만약 크기가 조정될 때 다양한 인스턴스가 존재하면, 키를 더 이상 공유하지 않게 되고 후의 그 클래스의 모든 인스턴스에 대해서 공유하지 않게 됩니다: CPython은 그 인스턴스가 같은 속성의 집합을 사용하는지 알 수 없게 되므로, 키를 공유하는 시도를 하지 않게 됩니다.
+- 작은 팁으로, 만약 프로그램의 메모리 공간을 줄이고 싶다면: 인스턴스 속성을 지우지 말고, 꼭 모든 속성을 `__init__`에서 초기화 하세요!
+
+---
+
+### ▶ 사소한 것들 \*
+
+
+
+- `join()` 은 리스트 연산이 아닌 문자열 연산입니다. (처음 보기에는 직관적이지 않습니다.)
+
+ **💡 설명:** `join()`이 문자열의 메소드라면 모든 iterable 자료형 (리스트(list), 튜플(tuple), 반복자(iterators)) 에서 동작할 수 있습니다. 만약 리스트의 메소드라면 모든 타입에 대해 따로 정의해야 합니다. 또한, 일반적인 `list` 객체 API에 문자열 방식의 메소드를 붙이는 것은 별로 말이 되지 않습니다.
+
+- 이상하게 보이지만 의미상 올바른 구문들:
+
+ - `[] = ()` 은 의미상 올바른 구문입니다. (빈 `tuple`을 빈 `list` 안으로 풀어 넣습니다.(unpacking))
+ - `'a'[0][0][0][0][0]` 은 파이썬에서 문자열들이 [sequences](https://docs.python.org/ko/3/glossary.html#term-sequence) (iterables 하고 인덱스로 요소에 접근이 가능합니다) 이므로 의미상 올바른 구문입니다.
+ - `3 --0-- 5 == 8`과 `--5 == 5` 둘다 의미상 올바른 구문이며 결괏값은 `True`입니다.
+
+- `a`을 숫자라고 고려할 때, `++a`와 `--a` 둘 다 파이썬에서 올바른 구문이지만 C, C++, 또는 Java 같은 언어에서 유사한 구문과는 같은 결과를 보이지 않습니다.
+
+ ```py
+ >>> a = 5
+ >>> a
+ 5
+ >>> ++a
+ 5
+ >>> --a
+ 5
+ ```
+
+ **💡 설명:**
+
+ - 파이썬 문법에는 `++` 연산자가 없습니다. 이것은 두 개의 `+` 연산자입니다.
+ - `++a` 는 `+(+a)`로 분석되어 `a`가 됩니다. 마찬가지로 `--a` 도 올바른 구문입니다.
+ - 이 StackOverflow [스레드](https://stackoverflow.com/questions/3654830/why-are-there-no-and-operators-in-python)에서 파이썬에서 증가 및 감소 연산자가 없는 이유에 대한 토론을 확인할 수 있습니다.
+
+- 파이썬의 Walrus 연산자에 대해 알고 있을 겁니다. 그런데 _space-invader 연산자_ 에 대해 들어보셨나요?
+ ```py
+ >>> a = 42
+ >>> a -=- 1
+ >>> a
+ 43
+ ```
+ 다른 증가 연산자와 함께, 대체 증가 연산자로 사용됩니다.
+ ```py
+ >>> a +=+ 1
+ >>> a
+ >>> 44
+ ```
+ **💡 설명:** 이 장난은 [Raymond Hettinger's tweet](https://twitter.com/raymondh/status/1131103570856632321?lang=en) 에서 왔습니다. space-invader 연산자는 실제로 `a -= (-1)` 의 잘못된 형식입니다. `a = a - (- 1)`와 같습니다. `a += (+ 1)`도 비슷한 방식으로 적용됩니다.
+- 파이썬은 문서화되지 않은 [converse implication](https://en.wikipedia.org/wiki/Converse_implication) 연산자를 가지고 있습니다.
+
+ ```py
+ >>> False ** False == True
+ True
+ >>> False ** True == False
+ True
+ >>> True ** False == True
+ True
+ >>> True ** True == True
+ True
+ ```
+
+ **💡 설명:** 만약 `False` 와 `True` 을 0과 1로 대체하고 계산을 해보면, 진리표는 converse implication 연산자와 같습니다. ([Source](https://github.com/cosmologicon/pywat/blob/master/explanation.md#the-undocumented-converse-implication-operator))
+
+- 우리는 계속 연산자들을 말하고 있기 때문에, 행렬 곱셈을 위한 `@` 연산자도 있습니다. (걱정하지 마세요, 이번엔 진짜입니다).
+
+ ```py
+ >>> import numpy as np
+ >>> np.array([2, 2, 2]) @ np.array([7, 8, 8])
+ 46
+ ```
+
+ **💡 설명:** 파이썬 3.5부터 `@` 연산자를 추가해 과학계를 염두에 두었습니다. 어떤 객체든 `__matmul__` 의 마법 메소드를 오버로드해 이 연산자의 행동을 정의할 수 있습니다.
+
+- 파이썬 3.8 이상에서는 `f'{some_var=}` 와 같은 일반적인 f-string 구문을 사용하여 빠른 디버깅을 할 수 있습니다. 예를 들어,
+
+ ```py
+ >>> some_string = "wtfpython"
+ >>> f'{some_string=}'
+ "some_string='wtfpython'"
+ ```
+
+- 파이썬은 함수들의 지역 변수 저장소에 2바이트를 사용합니다. 이론적으로, 이것은 함수에서 65536개의 변수만 정의될 수 있는 것을 의미합니다. 하지만 파이썬에는 2^16개 이상의 변수 이름들을 저장하는 데 사용할 수 있는 유용한 해결책이 내장되어 있습니다. 다음 코드는 65536개 이상의 지역 변수가 정의되었을 때 스택에서 발생하는 상황을 보여줍니다. (주의: 이 코드는 약 2^18줄의 텍스트를 출력하므로, 준비하십시오!):
+
+ ```py
+ import dis
+ exec("""
+ def f():
+ """ + """
+ """.join(["X" + str(x) + "=" + str(x) for x in range(65539)]))
+
+ f()
+
+ print(dis.dis(f))
+ ```
+
+- 여러 파이썬 스레드들이 동시에 _파이썬 코드_ 를 실행하지 않습니다. (예, 제대로 들으셨습니다!) 여러 개의 스레드를 생성하여 파이썬 코드를 동시에 실행하도록 하는 것이 직관적으로 보일 수 있지만 파이썬의 [Global Interpreter Lock](https://wiki.python.org/moin/GlobalInterpreterLock) 때문에 당신이 만들고 실행시키는 스레드들은 같은 코어를 차례대로 동작하게 하는 것뿐입니다. 파이썬의 쓰레드는 IO-bound 작업에 적합하지만, CPU-bound 작업에서 병렬화를 달성하려면 [multiprocessing](https://docs.python.org/3/library/multiprocessing.html) 모듈을 사용하는 것이 좋을 수 있습니다.
+
+- 때때로, `print` 메소드는 값을 바로 출력하지 못할 수 있습니다. 예를 들어,
+
+ ```py
+ # File some_file.py
+ import time
+
+ print("wtfpython", end="_")
+ time.sleep(3)
+ ```
+
+ 출력 버퍼가 `\n` 에 도달한 후 또는 프로그램의 실행이 끝날 때 출력 버퍼가 플러시 되기 때문에 `end` 인자로 인하여 3초 뒤에 `wtfpython` 을 출력합니다. `flush=True` 인자를 전달하여 버퍼를 강제로 플러시 할 수도 있습니다.
+
+- 범위를 벗어난 리스트 슬라이싱은 에러를 던지지 않습니다.
+
+ ```py
+ >>> some_list = [1, 2, 3, 4, 5]
+ >>> some_list[111:]
+ []
+ ```
+
+- iterable 을 슬라이싱 하면 항상 새로운 객체가 생성되는 것은 아닙니다. 예를 들어,
+
+ ```py
+ >>> some_str = "wtfpython"
+ >>> some_list = ['w', 't', 'f', 'p', 'y', 't', 'h', 'o', 'n']
+ >>> some_list is some_list[:] # False expected because a new object is created.
+ False
+ >>> some_str is some_str[:] # True because strings are immutable, so making a new object is of not much use.
+ True
+ ```
+
+- 파이썬 3 에서 `int('١٢٣٤٥٦٧٨٩')` 는 `123456789` 을 반환합니다. 파이썬에서, 십진수 문자들에는 숫자 문자들과 십진법 숫자들을 형성하는데 사용될 수 있는 모든 문자가 포함됩니다, e.g. U+0660, ARABIC-INDIC DIGIT ZERO. 이 동작과 관련된 [interesting story](https://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/) 입니다.
+
+- 파이썬 3 이상에서는 더 나은 가독성을 위해 밑줄로 숫자 리터럴을 분리할 수 있습니다.
+
+ ```py
+ >>> six_million = 6_000_000
+ >>> six_million
+ 6000000
+ >>> hex_address = 0xF00D_CAFE
+ >>> hex_address
+ 4027435774
+ ```
+
+- `'abc'.count('') == 4`. 다음은 더 명확하게 만들어 주는 `count` 메소드의 비슷한 구현입니다.
+ ```py
+ def count(s, sub):
+ result = 0
+ for i in range(len(s) + 1 - len(sub)):
+ result += (s[i:i + len(sub)] == sub)
+ return result
+ ```
+ 이 동작은 원래 문자열에서 길이가 0인 슬라이스들에 빈 substring(`''`)이 일치하기 때문입니다.
+
+---
+
+---
+
+# 기여하기
+
+wtfpython에 기여할 수 있는 몇 가지 방법이 있어요,
+
+- 새로운 예제들 추천
+- 번역 돕기 ([issues labeled translation](https://github.com/satwikkansal/wtfpython/issues?q=is%3Aissue+is%3Aopen+label%3Atranslation) 을 보세요.)
+- 오래된 정보, 오타, 서식 오류 등의 작은 수정들
+- 차이점들 식별 (불충분한 설명, 중복된 예제 등등.)
+- 이 프로젝트를 더욱 재미있고 유용하게 만들기 위한 창의적인 제안들
+
+더 많은 정보는 [CONTRIBUTING.md](/CONTRIBUTING.md)을는보세요. 자유롭게 새로운 [issue](https://github.com/satwikkansal/wtfpython/issues/new)를 만들어 토론해보세요.
+
+추신: 역링크 요청으로 연락하지 마세요. 프로젝트와 관련이 높지 않으면 링크를 추가하지 않습니다.
+
+# 감사의 말
+
+이 항목들의 아이디어와 디자인은 Denys Dovhan's 의 멋진 프로젝트 [wtfjs](https://github.com/denysdovhan/wtfjs) 에서 영감을 받았습니다. Pythonista들의 압도적인 지지는 그것의 현재의 모습을 주었습니다.
+
+#### 몇 개의 멋진 링크들!
+
+- https://www.youtube.com/watch?v=sH4XF6pKKmk
+- https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python
+- https://sopython.com/wiki/Common_Gotchas_In_Python
+- https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines
+- https://stackoverflow.com/questions/1011431/common-pitfalls-in-python
+- https://www.python.org/doc/humor/
+- https://github.com/cosmologicon/pywat#the-undocumented-converse-implication-operator
+- https://www.codementor.io/satwikkansal/python-practices-for-efficient-code-performance-memory-and-usability-aze6oiq65
+- https://github.com/wemake-services/wemake-python-styleguide/search?q=wtfpython&type=Issues
+- WFTPython discussion threads on [Hacker News](https://news.ycombinator.com/item?id=21862073) and [Reddit](https://www.reddit.com/r/programming/comments/edsh3q/what_the_fck_python_30_exploring_and/).
+
+# 🎓 License
+
+[![WTFPL 2.0][license-image]][license-url]
+
+© [Satwik Kansal](https://satwikkansal.xyz)
+
+[license-url]: http://www.wtfpl.net
+[license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square
+
+## 친구들을 놀라게 해보세요!
+
+만약 wtfpython이 마음에 드셨다면, 친구들에게 빠르게 공유하기 위한 퀵 링크들을 사용할 수 있어요.
+
+[Twitter](https://twitter.com/intent/tweet?url=https://github.com/satwikkansal/wtfpython&text=If%20you%20really%20think%20you%20know%20Python,%20think%20once%20more!%20Check%20out%20wtfpython&hashtags=python,wtfpython) | [Linkedin](https://www.linkedin.com/shareArticle?url=https://github.com/satwikkansal&title=What%20the%20f*ck%20Python!&summary=If%20you%20really%20thing%20you%20know%20Python,%20think%20once%20more!) | [Facebook](https://www.facebook.com/dialog/share?app_id=536779657179021&display=page&href=https%3A%2F%2Fgithub.com%2Fsatwikkansal%2Fwtfpython"e=If%20you%20really%20think%20you%20know%20Python%2C%20think%20once%20more!)
+
+## Need a pdf version?
+
+I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details [here](https://satwikkansal.xyz/wtfpython-pdf/) to get them as soon as they are finished.
+
+**That's all folks!** For upcoming content like this, you can add your email [here](https://www.satwikkansal.xyz/content-like-wtfpython/).
diff --git a/docs/RU.md b/docs/RU.md
new file mode 100644
index 0000000..17d668d
--- /dev/null
+++ b/docs/RU.md
@@ -0,0 +1,3844 @@
+---
+hide:
+ - toc
+---
+
+
+What the f*ck Python! 😱
+Изучение и понимание Python с помощью нестандартного поведения и "магического" поведения.
+
+Другие переводы: [English Original](https://github.com/satwikkansal/wtfpython) | [Russian Русский](https://github.com/frontdevops/wtfpython) | [Chinese 中文](https://github.com/robertparley/wtfpython-cn) | [Vietnamese Tiếng Việt](https://github.com/vuduclyunitn/wtfptyhon-vi) | [Spanish Español](https://web.archive.org/web/20220511161045/https://github.com/JoseDeFreitas/wtfpython-es) | [Korean 한국어](https://github.com/buttercrab/wtfpython-ko) | [Add translation](https://github.com/satwikkansal/wtfpython/issues/new?title=Add%20translation%20for%20[LANGUAGE]&body=Expected%20time%20to%20finish:%20[X]%20weeks.%20I%27ll%20start%20working%20on%20it%20from%20[Y].)
+
+Еще способы попробовать: [Interactive](https://colab.research.google.com/github/satwikkansal/wtfpython/blob/master/irrelevant/wtf.ipynb) | [CLI](https://pypi.python.org/pypi/wtfpython)
+
+Python, будучи прекрасно разработанным языком программирования высокого уровня с интерпретатором, предоставляет нам множество возможностей для удобства программиста. Но иногда результаты работы фрагмента Python могут показаться неочевидными на первый взгляд.
+
+Вот забавный проект, пытающийся объяснить, что именно происходит под капотом некоторых неинтуитивных сниппетов и менее известных возможностей Python.
+
+Хотя некоторые из примеров, которые вы увидите ниже, возможно, не являются WTF в прямом смысле этого слова, но они раскроют некоторые интересные части Python, о которых вы могли не знать. Я считаю, что это хороший способ изучить внутреннее устройство языка программирования, и я верю, что вам это тоже покажется интересным!
+
+Если вы опытный программист на Python, вы можете принять это как вызов, чтобы получить большинство из них правильно с первой попытки. Возможно, вы уже сталкивались с некоторыми из них раньше, и я смогу оживить ваши старые добрые воспоминания! :sweat_smile:
+
+PS: Если вы постоянный читатель, вы можете узнать о новых изменениях [здесь](https://github.com/satwikkansal/wtfpython/releases/) (примеры, отмеченные звездочкой - это примеры, добавленные в последней основной редакции).
+
+Ну чтож, начнем...
+
+# Table of Contents
+
+
+
+- [Структура примеров](#structure-of-the-examples)
+ + [▶ Какое-то модное название](#-some-fancy-title)
+- [Применение](#usage)
+- [👀 Примеры](#-examples)
+ * [Раздел: Напряги мозги!](#section-strain-your-brain)
+ + [▶ Перво-наперво! *](#-first-things-first-)
+ + [▶ Иногда строки могут быть хитрыми](#-strings-can-be-tricky-sometimes)
+ + [▶ Осторожнее с цепочками операций](#-be-careful-with-chained-operations)
+ + [▶ Как не надо использовать оператор `is`](#-how-not-to-use-is-operator)
+ + [▶ Мистика хэширования](#-hash-brownies)
+ + [▶ В глубине души мы все одинаковы](#-deep-down-were-all-the-same)
+ + [▶ Беспорядок внутри порядка *](#-disorder-within-order-)
+ + [▶ Продолжай пытаться... *](#-keep-trying-)
+ + [▶ Для чего?](#-for-what)
+ + [▶ Расхождение во времени оценки](#-evaluation-time-discrepancy)
+ + [▶ `is not ...` не является `is(not ...)`](#-is-not--is-not-is-not-)
+ + [▶ Крестики-нолики, где X побеждает в первой попытке!](#-a-tic-tac-toe-where-x-wins-in-the-first-attempt)
+ + [▶ Переменная Шредингера](#-schrödingers-variable-)
+ + [▶ The chicken-egg problem *](#-the-chicken-egg-problem-)
+ + [▶ Отношения подклассов](#-subclass-relationships)
+ + [▶ Методы равенства и тождества](#-methods-equality-and-identity)
+ + [▶ All-true-ation *](#-all-true-ation-)
+ + [▶ Удивительная запятая](#-the-surprising-comma)
+ + [▶ Строки и обратные слеши](#-strings-and-the-backslashes)
+ + [▶ not knot!](#-not-knot)
+ + [▶ Строки с половиной тройных кавычек](#-half-triple-quoted-strings)
+ + [▶ What's wrong with booleans?](#-whats-wrong-with-booleans)
+ + [▶ Атрибуты класса и атрибуты экземпляра](#-class-attributes-and-instance-attributes)
+ + [▶ yielding None](#-yielding-none)
+ + [▶ Yielding из... возврата! *](#-yielding-from-return-)
+ + [▶ NaN-рефлексивность *](#-nan-reflexivity-)
+ + [▶ Мутация неизменного!](#-mutating-the-immutable)
+ + [▶ Исчезающая переменная из внешней области видимости](#-the-disappearing-variable-from-outer-scope)
+ + [▶ Загадочное преобразование типа ключа](#-the-mysterious-key-type-conversion)
+ + [▶ Посмотрим, сможете ли вы угадать это?](#-lets-see-if-you-can-guess-this)
+ + [▶ Exceeds the limit for integer string conversion](#-exceeds-the-limit-for-integer-string-conversion)
+ * [Раздел: Скользкие склоны](#section-slippery-slopes)
+ + [▶ Изменение словаря во время итерации по нему](#-modifying-a-dictionary-while-iterating-over-it)
+ + [▶ Упрямая операция `del`](#-stubborn-del-operation)
+ + [▶ Переменная вне области видимости](#-the-out-of-scope-variable)
+ + [▶ Удаление элемента списка во время итерации](#-deleting-a-list-item-while-iterating)
+ + [▶ zip итераторов с потерями *](#-lossy-zip-of-iterators-)
+ + [▶ Loop variables leak out!](#-loop-variables-leaking-out)
+ + [▶ Остерегайтесь мутабельных аргументов по умолчанию!](#-beware-of-default-mutable-arguments)
+ + [▶ Catching the Exceptions](#-catching-the-exceptions)
+ + [▶ Same operands, different story!](#-same-operands-different-story)
+ + [▶ Разрешение имен, игнорирующее область действия класса](#-name-resolution-ignoring-class-scope)
+ + [▶ Округление как у банкира *](#-rounding-like-a-banker-)
+ + [▶ Needles in a Haystack *](#-needles-in-a-haystack-)
+ + [▶ Splitsies *](#-splitsies-)
+ + [▶ Дикий импорт *](#-wild-imports-)
+ + [▶ Все отсортировано? *](#-all-sorted-)
+ + [▶ Полуночное время не существует?](#-midnight-time-doesnt-exist)
+ * [Раздел: Скрытые сокровища!](#section-the-hidden-treasures)
+ + [▶ О'кей Питон, ты можешь заставить меня летать?](#-okay-python-can-you-make-me-fly)
+ + [▶ `goto`, но почему?](#-goto-but-why)
+ + [▶ Держитесь!](#-brace-yourself)
+ + [▶ Давайте встретимся с дружелюбным языковым дядей на всю жизнь](#-lets-meet-friendly-language-uncle-for-life)
+ + [▶ Даже Python понимает, что любовь - это сложно](#-even-python-understands-that-love-is-complicated)
+ + [▶ Да, это существует!](#-yes-it-exists)
+ + [▶ Многоточие *](#-ellipsis-)
+ + [▶ Inpinity](#-inpinity)
+ + [▶ Let's mangle](#-lets-mangle)
+ * [Раздел: Внешность обманчива!](#section-appearances-are-deceptive)
+ + [▶ Пропускаете строки?](#-skipping-lines)
+ + [▶ Телепортация](#-teleportation)
+ + [▶ Ну, что-то тут нечисто...](#-well-something-is-fishy)
+ * [Раздел: Разное](#section-miscellaneous)
+ + [▶ `+=` быстрее](#--is-faster)
+ + [▶ Давайте создадим гигантскую строку!](#-lets-make-a-giant-string)
+ + [▶ Замедление поиска `dict` *](#-slowing-down-dict-lookups-)
+ + [▶ Раздувание экземпляров словарей *](#-bloating-instance-dicts-)
+ + [▶ Minor Ones *](#-minor-ones-)
+- [Contributing](#contributing)
+- [Благодарности](#acknowledgements)
+- [🎓 Лицензия](#-license)
+ * [Удивите и своих друзей!](#surprise-your-friends-as-well)
+ * [Больше подобных материалов?](#more-content-like-this)
+
+
+# Структура примеров
+
+Все примеры имеют следующую структуру:
+
+> ### ▶ Какой-то заголовок
+>
+> ```py
+> # Код с приколдесами.
+> # Подготовка к магии...
+> ```
+>
+> **Вывод (Python версия(и)):**
+>
+> ```py
+> >>> triggering_statement
+> Неожиданные результаты
+> ```
+> (Опционально): Одна строка, описывающая неожиданный результат
+>
+>
+> #### 💡 Объяснение:
+>
+> * Краткое объяснение того, что происходит и почему это происходит.
+> ```py
+> # Код
+> # Дополнительные примеры для дальнейшего разъяснения (если необходимо)
+> ```
+> **Вывод (Python версия(и)):**
+>
+> ```py
+> >>> trigger # какой-нибудь пример, позволяющий легко раскрыть магию
+> # обоснованный вывод
+> ```
+
+**Важно:** Все примеры протестированы на интерактивном интерпретаторе Python 3.5.2, и они должны работать для всех версий Python, если это явно не указано перед выводом.
+
+# Применение
+
+Хороший способ получить максимальную пользу от этих примеров, на мой взгляд, - читать их в последовательном порядке, причем для каждого примера:
+- Внимательно прочитайте исходный код для настройки примера. Если вы опытный программист на Python, то в большинстве случаев вы сможете предугадать, что произойдет дальше.
+- Прочитайте фрагменты вывода и,
+ + Проверьте, совпадают ли выходные данные с вашими ожиданиями.
+ + Убедитесь, что вы знаете точную причину, по которой вывод получился именно таким.
+ - Если ответ отрицательный (что совершенно нормально), сделайте глубокий вдох и прочитайте объяснение (а если вы все еще не понимаете, крикните! и создайте проблему [здесь](https://github.com/satwikkansal/wtfpython/issues/new)).
+ - Если "да", похлопайте себя по спине и переходите к следующему примеру.
+
+PS: Вы также можете читать WTFPython в командной строке, используя [pypi package](https://pypi.python.org/pypi/wtfpython),
+```sh
+$ pip install wtfpython -U
+$ wtfpython
+```
+---
+
+# 👀 Примеры
+
+## Секция: Напряги мозги!
+
+### ▶ Важное о главном!
+
+
+
+
+По какой-то причине "моржовый оператор"(walrus) (`:=`) в Python 3.8 стал довольно популярным. Давайте проверим его,
+
+1\.
+
+```py
+# Python version 3.8+
+
+>>> a = "wtf_walrus"
+>>> a
+'wtf_walrus'
+
+>>> a := "wtf_walrus"
+File "", line 1
+ a := "wtf_walrus"
+ ^
+SyntaxError: invalid syntax
+
+>>> (a := "wtf_walrus") # This works though
+'wtf_walrus'
+>>> a
+'wtf_walrus'
+```
+
+2 \.
+
+```py
+# Python version 3.8+
+
+>>> a = 6, 9
+>>> a
+(6, 9)
+
+>>> (a := 6, 9)
+(6, 9)
+>>> a
+6
+
+>>> a, b = 6, 9 # Typical unpacking
+>>> a, b
+(6, 9)
+>>> (a, b = 16, 19) # Oops
+ File "", line 1
+ (a, b = 16, 19)
+ ^
+SyntaxError: invalid syntax
+
+>>> (a, b := 16, 19) # This prints out a weird 3-tuple
+(6, 16, 19)
+
+>>> a # a is still unchanged?
+6
+
+>>> b
+16
+```
+
+
+
+#### 💡 Обьяснение
+
+**Быстрый разбор что такое "моржовый оператор" (walrus)**
+
+"Моржовый оператор" (`:=`) была введена в Python 3.8, она может быть полезна в ситуациях, когда вы хотите присвоить значения переменным в выражении.
+
+```py
+def some_func():
+ # Assume some expensive computation here
+ # time.sleep(1000)
+ return 5
+
+# So instead of,
+if some_func():
+ print(some_func()) # Which is bad practice since computation is happening twice
+
+# or
+a = some_func()
+if a:
+ print(a)
+
+# Now you can concisely write
+if a := some_func():
+ print(a)
+```
+
+**Вывод (> 3.8):**
+
+```py
+5
+5
+5
+```
+
+Это сэкономило одну строку кода и неявно предотвратило вызов `some_func` дважды.
+
+- Непарентезированное "выражение присваивания" (использование моржового оператора), ограничено на верхнем уровне, отсюда `SyntaxError` в выражении `a := "wtf_walrus"` первого фрагмента. Расстановка парентез сработала, как и ожидалось, и присвоила `a`.
+
+- Как обычно, выделение скобками выражения, содержащего оператор `=`, не допускается. Отсюда синтаксическая ошибка в `(a, b = 6, 9)`.
+
+- Синтаксис моржового оператора имеет вид `NAME:= expr`, где `NAME` - допустимый идентификатор, а `expr` - допустимое выражение. Следовательно, упаковка и распаковка итерабельных выражений не поддерживается, что означает,
+
+ - `(a := 6, 9)` is equivalent to `((a := 6), 9)` and ultimately `(a, 9) ` (where `a`'s value is 6')
+
+ ```py
+ >>> (a := 6, 9) == ((a := 6), 9)
+ True
+ >>> x = (a := 696, 9)
+ >>> x
+ (696, 9)
+ >>> x[0] is a # Both reference same memory location
+ True
+ ```
+
+ - Similarly, `(a, b := 16, 19)` is equivalent to `(a, (b := 16), 19)` which is nothing but a 3-tuple.
+
+---
+
+### ▶ Строки иногда ведут себя непредсказуемо
+
+
+1\.
+
+```py
+>>> a = "some_string"
+>>> id(a)
+140420665652016
+>>> id("some" + "_" + "string") # Notice that both the ids are same.
+140420665652016
+```
+
+2\.
+```py
+>>> a = "wtf"
+>>> b = "wtf"
+>>> a is b
+True
+
+>>> a = "wtf!"
+>>> b = "wtf!"
+>>> a is b
+False
+
+```
+
+3\.
+
+```py
+>>> a, b = "wtf!", "wtf!"
+>>> a is b # Все версии, кроме 3.7.x
+True
+
+>>> a = "wtf!"; b = "wtf!"
+>>> a is b # Это выведет True или False в зависимости от того, где вы вызываете (python shell / ipython / as a script)
+False
+```
+
+```py
+# This time in file some_file.py
+a = "wtf!"
+b = "wtf!"
+print(a is b)
+
+# выводит True при вызове модуля!
+```
+
+4\.
+
+**Результат (< Python3.7 )**
+
+```py
+>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
+True
+>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
+False
+```
+
+Логично, правда?
+
+#### 💡 Объяснение:
++ Поведение в первом и втором фрагментах связано с оптимизацией CPython (называемой интернированием строк), которая пытается использовать существующие неизменяемые объекты в некоторых случаях вместо того, чтобы каждый раз создавать новый объект.
++ После "интернирования" многие переменные могут ссылаться на один и тот же строковый объект в памяти (тем самым экономя память).
++ В приведенных выше фрагментах строки неявно интернированы. Решение о том, когда неявно интернировать строку, зависит от реализации. Существуют некоторые правила, по которым можно определить, будет ли строка интернирована или нет:
+ * Все строки длины 0 и длины 1 интернируются.
+ * Строки интернируются во время компиляции (`'wtf'' будет интернирована, но `''.join(['w'', 't'', 'f'])` не будет интернирована)
+ * Строки, не состоящие из букв ASCII, цифр или знаков подчеркивания, не интернализируются. Это объясняет, почему `'wtf!'` не интернируется из-за `!`. Реализацию этого правила в CPython можно найти [здесь](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)
+ ![image](../images/string-intern/string_intern.png)
++ Когда `a` и `b` имеют значение `"wtf!"` в одной строке, интерпретатор Python создает новый объект, а затем одновременно ссылается на вторую переменную. Если вы делаете это в отдельных строках, он не "знает", что уже существует `"wtf!"` как объект (потому что `"wtf!"` не является неявно интернированным в соответствии с фактами, упомянутыми выше). Это оптимизация времени компиляции. Эта оптимизация не применяется к версиям CPython 3.7.x (более подробное обсуждение смотрите здесь [issue](https://github.com/satwikkansal/wtfpython/issues/100)).
++ Единица компиляции в интерактивной среде, такой как IPython, состоит из одного оператора, тогда как в случае модулей она состоит из всего модуля. `a, b = "wtf!", "wtf!"` - это одно утверждение, тогда как `a = "wtf!"; b = "wtf!"` - это два утверждения в одной строке. Это объясняет, почему тождества различны в `a = "wtf!"; b = "wtf!"`, а также объясняет, почему они одинаковы при вызове в `some_file.py`.
++ Резкое изменение в выводе четвертого фрагмента связано с [peephole optimization](https://en.wikipedia.org/wiki/Peephole_optimization) техникой, известной как Constant folding. Это означает, что выражение `'a'*20` заменяется на `'aaaaaaaaaaaaaaaaaaaa'` во время компиляции, чтобы сэкономить несколько тактов во время выполнения. Складывание констант происходит только для строк длиной менее 21. (Почему? Представьте себе размер файла `.pyc`, созданного в результате выражения `'a'*10**10`). [Вот](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288) исходный текст реализации для этого.
++ Примечание: В Python 3.7 складывание констант было перенесено из оптимизатора peephole в новый оптимизатор AST с некоторыми изменениями в логике, поэтому четвертый фрагмент не работает в Python 3.7. Подробнее об изменении можно прочитать [здесь](https://bugs.python.org/issue11549).
+
+---
+
+
+### ▶ Be careful with chained operations
+
+```py
+>>> (False == False) in [False] # makes sense
+False
+>>> False == (False in [False]) # makes sense
+False
+>>> False == False in [False] # now what?
+True
+
+>>> True is False == False
+False
+>>> False is False is False
+True
+
+>>> 1 > 0 < 1
+True
+>>> (1 > 0) < 1
+False
+>>> 1 > (0 < 1)
+False
+```
+
+#### 💡 Объяснение:
+
+As per https://docs.python.org/3/reference/expressions.html#comparisons
+
+> Формально, если a, b, c, ..., y, z - выражения, а op1, op2, ..., opN - операторы сравнения, то a op1 b op2 c ... y opN z эквивалентно a op1 b и b op2 c и ... y opN z, за исключением того, что каждое выражение оценивается не более одного раза.
+
+Хотя такое поведение может показаться вам глупым в приведенных выше примерах, оно просто фантастично для таких вещей, как `a == b == c` и `0 <= x <= 100`.
+
+* `False is False is False` эквивалентно `(False is False) и (False is False)`.
+* ``True is False == False`` эквивалентно ``(True is False) and (False == False)`` и так как первая часть высказывания (`True is False``) оценивается в `False``, то все выражение оценивается в `False``.
+* `1 > 0 < 1` эквивалентно `(1 > 0) и (0 < 1)`, которое оценивается в `True`.
+* Выражение `(1 > 0) < 1` эквивалентно `True < 1` и
+ ```py
+ >>> int(True)
+ 1
+ >>> True + 1 # не имеет значения для данного примера, но просто для интереса
+ 2
+ ```
+ В итоге, `1 < 1` выполняется и дает результат `False`
+
+---
+
+### ▶ Как не надо использовать оператор `is`
+
+Ниже приведен очень известный пример, представленный во всем Интернете.
+
+1\.
+
+```py
+>>> a = 256
+>>> b = 256
+>>> a is b
+True
+
+>>> a = 257
+>>> b = 257
+>>> a is b
+False
+```
+
+2\.
+
+```py
+>>> a = []
+>>> b = []
+>>> a is b
+False
+
+>>> a = tuple()
+>>> b = tuple()
+>>> a is b
+True
+```
+
+3\.
+**Результат**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+True
+```
+
+**Вывод (Python 3.7.x specifically)**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+False
+```
+
+#### 💡 Объяснение:
+
+**Разница между `is` и `==`**.
+
+* Оператор `is` проверяет, ссылаются ли оба операнда на один и тот же объект (т.е. проверяет, совпадают ли идентификаторы операндов или нет).
+* Оператор `==` сравнивает значения обоих операндов и проверяет, одинаковы ли они.
+* Таким образом, оператор `is` предназначен для равенства ссылок, а `==` - для равенства значений. Пример, чтобы прояснить ситуацию,
+ ```py
+ >>> class A: pass
+ >>> A() is A() # These are two empty objects at two different memory locations.
+ False
+ ```
+
+**`256` - существующий объект, а `257` - нет**.
+
+При запуске python будут выделены числа от `-5` до `256`. Эти числа используются часто, поэтому имеет смысл просто иметь их наготове.
+
+Цитирую по https://docs.python.org/3/c-api/long.html
+> Текущая реализация хранит массив целочисленных объектов для всех целых чисел от -5 до 256, когда вы создаете int в этом диапазоне, вы просто получаете обратно ссылку на существующий объект. Поэтому должно быть возможно изменить значение 1. Я подозреваю, что поведение Python в этом случае не определено. :-)
+
+```py
+>>> id(256)
+10922528
+>>> a = 256
+>>> b = 256
+>>> id(a)
+10922528
+>>> id(b)
+10922528
+>>> id(257)
+140084850247312
+>>> x = 257
+>>> y = 257
+>>> id(x)
+140084850247440
+>>> id(y)
+140084850247344
+```
+
+Здесь интерпретатору не хватает мозгов при выполнении `y = 257` понять, что мы уже создали целое число со значением `257,` и поэтому он продолжает создавать другой объект в памяти.
+
+Подобная оптимизация применима и к другим **изменяемым** объектам, таким как пустые кортежи. Поскольку списки являются изменяемыми, поэтому `[] is []` вернет `False`, а `() is ()` вернет `True`. Это объясняет наш второй фрагмент. Перейдем к третьему,
+
+**И `a`, и `b` ссылаются на один и тот же объект при инициализации одним и тем же значением в одной и той же строке*.
+
+**Вывод**
+
+```py
+>>> a, b = 257, 257
+>>> id(a)
+140640774013296
+>>> id(b)
+140640774013296
+>>> a = 257
+>>> b = 257
+>>> id(a)
+140640774013392
+>>> id(b)
+140640774013488
+```
+
+* Когда a и b устанавливаются в `257` в одной строке, интерпретатор Python создает новый объект, а затем одновременно ссылается на вторую переменную. Если вы делаете это в отдельных строках, он не "знает", что объект `257` уже существует.
+
+* Это оптимизация компилятора и относится именно к интерактивной среде. Когда вы вводите две строки в живом интерпретаторе, они компилируются отдельно, поэтому оптимизируются отдельно. Если бы вы попробовали этот пример в файле `.py', вы бы не увидели такого же поведения, потому что файл компилируется весь сразу. Эта оптимизация не ограничивается целыми числами, она работает и для других неизменяемых типов данных, таких как строки (проверьте пример "Строки - это сложно") и плавающие числа,
+
+ ```py
+ >>> a, b = 257.0, 257.0
+ >>> a is b
+ True
+ ```
+
+* Почему это не сработало в Python 3.7? Абстрактная причина в том, что такие оптимизации компилятора зависят от реализации (т.е. могут меняться в зависимости от версии, ОС и т.д.). Я все еще выясняю, какое именно изменение реализации вызвало проблему, вы можете проверить этот [issue](https://github.com/satwikkansal/wtfpython/issues/100) для получения обновлений.
+
+---
+
+
+### ▶ Hash brownies
+
+1\.
+```py
+some_dict = {}
+some_dict[5.5] = "JavaScript"
+some_dict[5.0] = "Ruby"
+some_dict[5] = "Python"
+```
+
+**Вывод:**
+
+```py
+>>> some_dict[5.5]
+"JavaScript"
+>>> some_dict[5.0] # "Python" destroyed the existence of "Ruby"?
+"Python"
+>>> some_dict[5]
+"Python"
+
+>>> complex_five = 5 + 0j
+>>> type(complex_five)
+complex
+>>> some_dict[complex_five]
+"Python"
+```
+
+Так почему же Python повсюду?
+
+
+#### 💡 Объяснение.
+
+* Уникальность ключей в словаре Python определяется *эквивалентностью*, а не тождеством. Поэтому, даже если `5`, `5.0` и `5 + 0j` являются различными объектами разных типов, поскольку они равны, они не могут находиться в одном и том же `дикте` (или `наборе`). Как только вы вставите любой из них, попытка поиска по любому другому, но эквивалентному ключу будет успешной с исходным сопоставленным значением (а не завершится ошибкой `KeyError`):
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> 5 is not 5.0 is not 5 + 0j
+ True
+ >>> some_dict = {}
+ >>> some_dict[5.0] = "Ruby"
+ >>> 5.0 in some_dict
+ True
+ >>> (5 in some_dict) and (5 + 0j in some_dict)
+ True
+ ```
+* Это применимо и при установке элемента. Поэтому, когда вы делаете `some_dict[5] = "Python"`, Python находит существующий элемент с эквивалентным ключом `5.0 -> "Ruby"`, перезаписывает его значение на место, а исходный ключ оставляет в покое.
+ ```py
+ >>> some_dict
+ {5.0: 'Ruby'}
+ >>> some_dict[5] = "Python"
+ >>> some_dict
+ {5.0: 'Python'}
+ ```
+* Итак, как мы можем обновить ключ до `5` (вместо `5.0`)? На самом деле мы не можем сделать это обновление на месте, но что мы можем сделать, так это сначала удалить ключ (`del some_dict[5.0]`), а затем установить его (`some_dict[5]`), чтобы получить целое число `5` в качестве ключа вместо плавающего `5.0`, хотя это нужно в редких случаях.
+
+* Как Python нашел `5` в словаре, содержащем `5.0`? Python делает это за постоянное время без необходимости сканирования каждого элемента, используя хэш-функции. Когда Python ищет ключ `foo` в словаре, он сначала вычисляет `hash(foo)` (что выполняется в постоянном времени). Поскольку в Python требуется, чтобы объекты, которые сравниваются одинаково, имели одинаковое хэш-значение ([docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__) здесь), `5`, `5.0` и `5 + 0j` имеют одинаковое хэш-значение.
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> hash(5) == hash(5.0) == hash(5 + 0j)
+ True
+ ```
+ **Примечание:** Обратное не обязательно верно: Объекты с одинаковыми хэш-значениями сами могут быть неравными. (Это вызывает так называемую [хэш-коллизию](https://en.wikipedia.org/wiki/Collision_(computer_science)) и ухудшает производительность постоянного времени, которую обычно обеспечивает хэширование).
+
+---
+
+### ▶ В глубине души мы все одинаковы.
+
+```py
+class WTF:
+ pass
+```
+
+**Вывод:**
+```py
+>>> WTF() == WTF() # two different instances can't be equal
+False
+>>> WTF() is WTF() # identities are also different
+False
+>>> hash(WTF()) == hash(WTF()) # hashes _should_ be different as well
+True
+>>> id(WTF()) == id(WTF())
+True
+```
+#### 💡 Объяснение:
+
+* При вызове `id` Python создал объект класса `WTF` и передал его функции `id`. Функция `id` забирает свой `id` (местоположение в памяти) и выбрасывает объект. Объект уничтожается.
+* Когда мы делаем это дважды подряд, Python выделяет ту же самую область памяти и для второго объекта. Поскольку (в CPython) `id` использует участок памяти в качестве идентификатора объекта, идентификатор двух объектов одинаков.
+* Таким образом, id объекта уникален только в течение жизни объекта. После уничтожения объекта или до его создания, что-то другое может иметь такой же id.
+* Но почему оператор `is` имеет значение `False`? Давайте посмотрим с помощью этого фрагмента.
+ ```py
+ class WTF(object):
+ def __init__(self): print("I")
+ def __del__(self): print("D")
+ ```
+
+ **Вывод:**
+ ```py
+ >>> WTF() is WTF()
+ I
+ I
+ D
+ D
+ False
+ >>> id(WTF()) == id(WTF())
+ I
+ D
+ I
+ D
+ True
+ ```
+ Как вы можете заметить, порядок, в котором уничтожаются объекты, имеет значение.
+
+---
+
+### ▶ Нарушение в пределах порядка *
+
+```py
+from collections import OrderedDict
+
+dictionary = dict()
+dictionary[1] = 'a'; dictionary[2] = 'b';
+
+ordered_dict = OrderedDict()
+ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+
+another_ordered_dict = OrderedDict()
+another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+
+class DictWithHash(dict):
+ """
+ A dict that also implements __hash__ magic.
+ """
+ __hash__ = lambda self: 0
+
+class OrderedDictWithHash(OrderedDict):
+ """
+ An OrderedDict that also implements __hash__ magic.
+ """
+ __hash__ = lambda self: 0
+```
+
+**Вывод**
+```py
+>>> dictionary == ordered_dict # If a == b
+True
+>>> dictionary == another_ordered_dict # and b == c
+True
+>>> ordered_dict == another_ordered_dict # then why isn't c == a ??
+False
+
+# Мы все знаем, что множество состоит только из уникальных элементов,
+# давайте попробуем составить множество из этих словарей и посмотрим, что получится...
+
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unhashable type: 'dict'
+
+# Имеет смысл, поскольку в словаре не реализовано свойство __hash__, ну чтож давайте использовать
+# наши классы-обертки.
+>>> dictionary = DictWithHash()
+>>> dictionary[1] = 'a'; dictionary[2] = 'b';
+>>> ordered_dict = OrderedDictWithHash()
+>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+>>> another_ordered_dict = OrderedDictWithHash()
+>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+1
+>>> len({ordered_dict, another_ordered_dict, dictionary}) # changing the order
+2
+```
+
+Что здесь происходит?
+
+#### 💡 Объяснение:
+
+- Причина, по которой не выполняется транзитивное равенство между `dictionary`, `ordered_dict` и `another_ordered_dict`, заключается в том, как реализован метод `__eq__` в классе `OrderedDict`. Из [docs](https://docs.python.org/3/library/collections.html#ordereddict-objects)
+
+ > Тесты равенства между объектами OrderedDict чувствительны к порядку и реализуются как `list(od1.items())==list(od2.items())`. Тесты на равенство между объектами `OrderedDict` и другими объектами Mapping нечувствительны к порядку, как обычные словари.
+- Причина такого поведения равенства в том, что оно позволяет напрямую подставлять объекты `OrderedDict` везде, где используется обычный словарь.
+- Итак, почему изменение порядка влияет на длину генерируемого объекта `set`? Ответ заключается только в отсутствии интранзитивного равенства. Поскольку множества являются "неупорядоченными" коллекциями уникальных элементов, порядок вставки элементов не должен иметь значения. Но в данном случае он имеет значение. Давайте немного разберемся в этом,
+ ```py
+ >>> some_set = set()
+ >>> some_set.add(dictionary) # these are the mapping objects from the snippets above
+ >>> ordered_dict in some_set
+ True
+ >>> some_set.add(ordered_dict)
+ >>> len(some_set)
+ 1
+ >>> another_ordered_dict in some_set
+ True
+ >>> some_set.add(another_ordered_dict)
+ >>> len(some_set)
+ 1
+
+ >>> another_set = set()
+ >>> another_set.add(ordered_dict)
+ >>> another_ordered_dict in another_set
+ False
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ >>> dictionary in another_set
+ True
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ ```
+ Таким образом, несоответствие связано с тем, что `another_ordered_dict в another_set` является `False`, потому что `ordered_dict` уже присутствовал в `another_set` и, как было замечено ранее, `ordered_dict == another_ordered_dict` является `False`.
+
+---
+
+### ▶ Keep trying... *
+
+```py
+def some_func():
+ try:
+ return 'from_try'
+ finally:
+ return 'from_finally'
+
+def another_func():
+ for _ in range(3):
+ try:
+ continue
+ finally:
+ print("Finally!")
+
+def one_more_func(): # A gotcha!
+ try:
+ for i in range(3):
+ try:
+ 1 / i
+ except ZeroDivisionError:
+ # Let's throw it here and handle it outside for loop
+ raise ZeroDivisionError("A trivial divide by zero error")
+ finally:
+ print("Iteration", i)
+ break
+ except ZeroDivisionError as e:
+ print("Zero division error occurred", e)
+```
+
+**Результат:**
+
+```py
+>>> some_func()
+'from_finally'
+
+>>> another_func()
+Finally!
+Finally!
+Finally!
+
+>>> 1 / 0
+Traceback (most recent call last):
+ File "", line 1, in
+ZeroDivisionError: division by zero
+
+>>> one_more_func()
+Iteration 0
+
+```
+
+#### 💡 Объяснение:
+
+- Когда оператор `return`, `break` или `continue` выполняется в наборе `try` оператора "try...finally", на выходе также выполняется пункт `finally`.
+- Возвращаемое значение функции определяется последним выполненным оператором `return`. Поскольку предложение `finally` выполняется всегда, оператор `return`, выполненный в предложении `finally`, всегда будет последним.
+- Оговорка заключается в том, что если в предложении finally выполняется оператор `return` или `break`, то временно сохраненное исключение отбрасывается.
+
+---
+
+
+### ▶ Для чего?
+
+```py
+some_string = "wtf"
+some_dict = {}
+for i, some_dict[i] in enumerate(some_string):
+ i = 10
+```
+
+**Вывод:**
+```py
+>>> some_dict # An indexed dict appears.
+{0: 'w', 1: 't', 2: 'f'}
+```
+
+#### 💡 Объяснение:
+
+* Оператор `for` определяется в [грамматике Python](https://docs.python.org/3/reference/grammar.html) как:
+ ```
+ for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
+ ```
+ Где `exprlist` - цель присваивания. Это означает, что эквивалент `{exprlist} = {next_value}` **выполняется для каждого элемента** в итерабле.
+ Интересный пример, иллюстрирующий это:
+ ```py
+ for i in range(4):
+ print(i)
+ i = 10
+ ```
+
+ **Результат:**
+ ```
+ 0
+ 1
+ 2
+ 3
+ ```
+
+ Ожидали ли вы, что цикл будет запущен только один раз?
+
+ **💡 Объяснение:**.
+
+ - Оператор присваивания `i = 10` никогда не влияет на итерации цикла из-за того, как циклы for работают в Python. Перед началом каждой итерации следующий элемент, предоставляемый итератором (в данном случае `range(4)`), распаковывается и присваивается переменной целевого списка (в данном случае `i`).
+
+* Функция `enumerate(some_string)` на каждой итерации выдает новое значение `i` (счетчик, идущий вверх) и символ из `some_string`. Затем она устанавливает (только что присвоенный) ключ `i` словаря `some_dict` на этот символ. Развертывание цикла можно упростить следующим образом:
+ ```py
+ >>> i, some_dict[i] = (0, 'w')
+ >>> i, some_dict[i] = (1, 't')
+ >>> i, some_dict[i] = (2, 'f')
+ >>> some_dict
+ ```
+
+---
+
+### ▶ Несоответствие времени оценки
+
+1\.
+```py
+array = [1, 8, 15]
+# A typical generator expression
+gen = (x for x in array if array.count(x) > 0)
+array = [2, 8, 22]
+```
+
+**Вывод:**
+
+```py
+>>> print(list(gen)) # Where did the other values go?
+[8]
+```
+
+2\.
+
+```py
+array_1 = [1,2,3,4]
+gen_1 = (x for x in array_1)
+array_1 = [1,2,3,4,5]
+
+array_2 = [1,2,3,4]
+gen_2 = (x for x in array_2)
+array_2[:] = [1,2,3,4,5]
+```
+
+**Вывод:**
+```py
+>>> print(list(gen_1))
+[1, 2, 3, 4]
+
+>>> print(list(gen_2))
+[1, 2, 3, 4, 5]
+```
+
+3\.
+
+```py
+array_3 = [1, 2, 3]
+array_4 = [10, 20, 30]
+gen = (i + j for i in array_3 for j in array_4)
+
+array_3 = [4, 5, 6]
+array_4 = [400, 500, 600]
+```
+
+**Вывод:**
+```py
+>>> print(list(gen))
+[401, 501, 601, 402, 502, 602, 403, 503, 603]
+```
+
+#### 💡 Пояснение
+
+- В выражении [generator](https://wiki.python.org/moin/Generators) условие `in` оценивается во время объявления, но условное условие оценивается во время выполнения.
+- Поэтому перед выполнением `array` переназначается на список `[2, 8, 22]`, а поскольку из `1`, `8` и `15` только счетчик `8` больше `0`, генератор выдает только `8`.
+- Различия в выводе `g1` и `g2` во второй части связаны с тем, как переменным `array_1` и `array_2` присваиваются новые значения.
+- В первом случае `array_1` привязывается к новому объекту `[1,2,3,4,5]`, а поскольку предложение `in` оценивается во время объявления, оно по-прежнему ссылается на старый объект `[1,2,3,4]` (который не уничтожается).
+- Во втором случае присвоение среза `array_2` обновляет тот же старый объект `[1,2,3,4]` до `[1,2,3,4,5]`. Следовательно, и `g2`, и `array_2` по-прежнему имеют ссылку на один и тот же объект (который теперь обновлен до `[1,2,3,4,5]`).
+- Хорошо, следуя логике, рассмотренной до сих пор, не должно ли значение `list(gen)` в третьем фрагменте быть `[11, 21, 31, 12, 22, 32, 13, 23, 33]`? (потому что `array_3` и `array_4` будут вести себя так же, как `array_1`). Причина, по которой (только) значения `array_4` обновляются, объясняется в [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details)
+
+ > Только крайнее for-выражение оценивается немедленно, остальные выражения откладываются до запуска генератора.
+
+---
+
+
+### ▶ `is not ...` is not `is (not ...)`
+
+```py
+>>> 'something' is not None
+True
+>>> 'something' is (not None)
+False
+```
+
+#### 💡 Пояснение
+
+- `is not` является единым бинарным оператором, и его поведение отличается от раздельного использования `is` и `not`.
+- `is not` имеет значение `False`, если переменные по обе стороны оператора указывают на один и тот же объект, и `True` в противном случае.
+- В примере `(not None)` оценивается в `True`, поскольку значение `None` является `False` в булевом контексте, поэтому выражение становится `'something' is True`.
+
+---
+
+### ▶ Крестики-нолики, где X побеждает с первой попытки!
+
+
+```py
+# Let's initialize a row
+row = [""] * 3 #row i['', '', '']
+# Let's make a board
+board = [row] * 3
+```
+
+**Результат:**
+
+```py
+>>> board
+[['', '', ''], ['', '', ''], ['', '', '']]
+>>> board[0]
+['', '', '']
+>>> board[0][0]
+''
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['X', '', ''], ['X', '', '']]
+```
+
+Мы же не назначили три ``Х``?
+
+#### 💡 Объяснение:
+
+Когда мы инициализируем переменную `row`, эта визуализация объясняет, что происходит в памяти
+
+![image](../images/tic-tac-toe/after_row_initialized.png)
+
+А когда `board` инициализируется путем умножения `row`, вот что происходит в памяти (каждый из элементов `board[0]`, `board[1]` и `board[2]` является ссылкой на тот же список, на который ссылается `row`)
+
+![image](../images/tic-tac-toe/after_board_initialized.png)
+
+Мы можем избежать этого сценария, не используя переменную `row` для генерации `board`. (Вопрос задан в [этом](https://github.com/satwikkansal/wtfpython/issues/68) выпуске).
+
+```py
+>>> board = [['']*3 for _ in range(3)]
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['', '', ''], ['', '', '']]
+```
+
+---
+
+### ▶ Переменная Шредингера *
+
+
+
+```py
+funcs = []
+results = []
+for x in range(7):
+ def some_func():
+ return x
+ funcs.append(some_func)
+ results.append(some_func()) # note the function call here
+
+funcs_results = [func() for func in funcs]
+```
+
+**Вывод (Python version):**
+```py
+>>> results
+[0, 1, 2, 3, 4, 5, 6]
+>>> funcs_results
+[6, 6, 6, 6, 6, 6, 6]
+```
+
+Значения `x` были разными в каждой итерации до добавления `some_func` к `funcs`, но все функции возвращают 6, когда они оцениваются после завершения цикла.
+
+2.
+
+```py
+>>> powers_of_x = [lambda x: x**i for i in range(10)]
+>>> [f(2) for f in powers_of_x]
+[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
+```
+
+#### 💡 Объяснение:
+* При определении функции внутри цикла, которая использует переменную цикла в своем теле, закрытие функции цикла привязывается к *переменной*, а не к ее *значению*. Функция ищет `x` в окружающем контексте, а не использует значение `x` на момент создания функции. Таким образом, все функции используют для вычислений последнее значение, присвоенное переменной. Мы можем видеть, что используется `x` из окружающего контекста (т.е. *не* локальная переменная):
+```py
+>>> import inspect
+>>> inspect.getclosurevars(funcs[0])
+ClosureVars(nonlocals={}, globals={'x': 6}, builtins={}, unbound=set())
+```
+Since `x` is a global value, we can change the value that the `funcs` will lookup and return by updating `x`:
+
+```py
+>>> x = 42
+>>> [func() for func in funcs]
+[42, 42, 42, 42, 42, 42, 42]
+```
+
+* Чтобы получить желаемое поведение, вы можете передать переменную цикла как именованную переменную в функцию. **Почему это работает?** Потому что это определит переменную *внутри* области видимости функции. Она больше не будет обращаться к окружающей (глобальной) области видимости для поиска значения переменной, а создаст локальную переменную, которая будет хранить значение `x` в данный момент времени.
+
+```py
+funcs = []
+for x in range(7):
+ def some_func(x=x):
+ return x
+ funcs.append(some_func)
+```
+
+**Вывод:**
+
+```py
+>>> funcs_results = [func() for func in funcs]
+>>> funcs_results
+[0, 1, 2, 3, 4, 5, 6]
+```
+
+`x` больше не и спользуется в глобальной области видимости
+
+```py
+>>> inspect.getclosurevars(funcs[0])
+ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())
+```
+
+---
+
+### ▶ Проблема курицы и яйца *
+
+1\.
+```py
+>>> isinstance(3, int)
+True
+>>> isinstance(type, object)
+True
+>>> isinstance(object, type)
+True
+```
+
+Так какой же базовый класс является "окончательным"? Кстати, это еще не все,
+
+2\.
+
+```py
+>>> class A: pass
+>>> isinstance(A, A)
+False
+>>> isinstance(type, type)
+True
+>>> isinstance(object, object)
+True
+```
+
+3\.
+
+```py
+>>> issubclass(int, object)
+True
+>>> issubclass(type, object)
+True
+>>> issubclass(object, type)
+False
+```
+
+
+#### 💡 Объяснение
+
+- `тип` - это [метакласс](https://realpython.com/python-metaclasses/) в Python.
+- **Все** в Python является `объектом`, что включает в себя как классы, так и их объекты (экземпляры).
+- Класс `type` является метаклассом класса `object`, и каждый класс (включая `type`) наследует прямо или косвенно от `object`.
+- Между `object` и `type` нет реального базового класса. Путаница в приведенных выше фрагментах возникает потому, что мы думаем об этих отношениях (`issubclass` и `isinstance`) в терминах классов Python. Отношения между `object` и `type` не могут быть воспроизведены в чистом Python. Точнее говоря, следующие отношения не могут быть воспроизведены в чистом Python,
+ + класс A является экземпляром класса B, а класс B является экземпляром класса A.
+ + класс A является экземпляром самого себя.
+- Эти отношения между `object` и `type` (оба являются экземплярами друг друга, а также самих себя) существуют в Python из-за "обмана" на уровне реализации.
+
+---
+
+### ▶ Отношения между подклассами
+
+**Вывод:**
+```py
+>>> from collections import Hashable
+>>> issubclass(list, object)
+True
+>>> issubclass(object, Hashable)
+True
+>>> issubclass(list, Hashable)
+False
+```
+
+Предполагается, что отношения подклассов должны быть транзитивными, верно? (т.е. если `A` является подклассом `B`, а `B` является подклассом `C`, то `A` _должен_ быть подклассом `C`)
+
+#### 💡 Пояснение:
+
+* Отношения подклассов не обязательно являются транзитивными в Python. Любой может определить свой собственный, произвольный `__subclasscheck__` в метаклассе.
+* Когда вызывается `issubclass(cls, Hashable)`, он просто ищет не-фальшивый метод "`__hash__`" в `cls` или во всем, от чего он наследуется.
+* Поскольку `object` является хэшируемым, а `list` - нехэшируемым, это нарушает отношение транзитивности.
+* Более подробное объяснение можно найти [здесь] (https://www.naftaliharris.com/blog/python-subclass-intransitivity/).
+---
+
+### ▶ Methods equality and identity
+
+
+1.
+```py
+class SomeClass:
+ def method(self):
+ pass
+
+ @classmethod
+ def classm(cls):
+ pass
+
+ @staticmethod
+ def staticm():
+ pass
+```
+
+**Результат:**
+```py
+>>> print(SomeClass.method is SomeClass.method)
+True
+>>> print(SomeClass.classm is SomeClass.classm)
+False
+>>> print(SomeClass.classm == SomeClass.classm)
+True
+>>> print(SomeClass.staticm is SomeClass.staticm)
+True
+```
+
+Обращаясь к `classm` дважды, мы получаем одинаковый объект, но не *одинаковый*? Давайте посмотрим, что происходит
+с экземплярами `SomeClass`:
+
+2.
+```py
+o1 = SomeClass()
+o2 = SomeClass()
+```
+
+**Вывод:**
+```py
+>>> print(o1.method == o2.method)
+False
+>>> print(o1.method == o1.method)
+True
+>>> print(o1.method is o1.method)
+False
+>>> print(o1.classm is o1.classm)
+False
+>>> print(o1.classm == o1.classm == o2.classm == SomeClass.classm)
+True
+>>> print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm)
+True
+```
+
+Двойной доступ к `классу` или `методу` создает одинаковые, но не *одинаковые* объекты для одного и того же экземпляра `какого-либо класса`.
+
+#### 💡 Пояснение.
+* Функции являются [дескрипторами](https://docs.python.org/3/howto/descriptor.html). Всякий раз, когда к функции обращаются как к
+атрибута, вызывается дескриптор, создавая объект метода, который "связывает" функцию с объектом, владеющим атрибутом.
+атрибутом. При вызове метод вызывает функцию, неявно передавая связанный объект в качестве первого аргумента
+(именно так мы получаем `self` в качестве первого аргумента, несмотря на то, что не передаем его явно).
+```py
+>>> o1.method
+>
+```
+* При многократном обращении к атрибуту каждый раз создается объект метода! Поэтому `o1.method is o1.method` является
+никогда не является истиной. Доступ к функциям как к атрибутам класса (в отличие от экземпляра) не создает методов, однако; поэтому
+`SomeClass.method is SomeClass.method` является истинным.
+```py
+>>> SomeClass.method
+
+```
+* `classmethod` преобразует функции в методы класса. Методы класса - это дескрипторы, которые при обращении к ним создают
+объект метода, который связывает *класс* (тип) объекта, а не сам объект.
+```py
+>>> o1.classm
+>
+```
+* В отличие от функций, `classmethod` будет создавать метод и при обращении к нему как к атрибуту класса (в этом случае они
+привязываются к классу, а не к его типу). Поэтому `SomeClass.classm is SomeClass.classm` является ошибочным.
+```py
+>>> SomeClass.classm
+>
+```
+* Объект метода сравнивается с равным, если обе функции равны, а связанные объекты одинаковы. Поэтому
+`o1.method == o1.method` является истинным, хотя и не является одним и тем же объектом в памяти.
+* `staticmethod` преобразует функции в дескриптор "no-op", который возвращает функцию как есть. Никакой метод
+никогда не создается, поэтому сравнение с `is` является истинным.
+```py
+>>> o1.staticm
+
+>>> SomeClass.staticm
+
+```
+* Необходимость создавать новые объекты "метод" каждый раз, когда Python вызывает методы экземпляра, и необходимость изменять аргументы
+каждый раз, чтобы вставить `self`, сильно сказывается на производительности.
+CPython 3.7 [решил эту проблему](https://bugs.python.org/issue26110), введя новые опкоды, которые работают с вызовом методов
+без создания временных объектов методов. Это используется только при фактическом вызове функции доступа, так что
+приведенные здесь фрагменты не затронуты и по-прежнему генерируют методы :)
+
+### ▶ All-true-ation *
+
+
+```py
+>>> all([True, True, True])
+True
+>>> all([True, True, False])
+False
+
+>>> all([])
+True
+>>> all([[]])
+False
+>>> all([[[]]])
+True
+```
+
+Почему это изменение True-False?
+
+#### 💡 Объяснение:
+
+- Реализация функции `all` эквивалентна
+
+- ```py
+ def all(iterable):
+ for element in iterable:
+ if not element:
+ return False
+ return True
+ ```
+
+- `all([])` возвращает `True`, поскольку итерируемый массив пуст.
+- `all([[]])` возвращает `False`, поскольку переданный массив имеет один элемент, `[]`, а в python пустой список является ложным.
+- `all([[[[]]])` и более высокие рекурсивные варианты всегда `True`. Это происходит потому, что единственный элемент переданного массива (`[[...]]`) уже не пуст, а списки со значениями являются истинными.
+
+---
+
+### ▶ Неожиданная запятая
+
+**Вывод (< 3.6):**
+
+```py
+>>> def f(x, y,):
+... print(x, y)
+...
+>>> def g(x=4, y=5,):
+... print(x, y)
+...
+>>> def h(x, **kwargs,):
+ File "", line 1
+ def h(x, **kwargs,):
+ ^
+SyntaxError: invalid syntax
+
+>>> def h(*args,):
+ File "", line 1
+ def h(*args,):
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Объяснение:
+
+- Запятая в конце списка формальных параметров функции Python не всегда законна.
+- В Python список аргументов определяется частично с помощью ведущих запятых, а частично с помощью проходных запятых. Этот конфликт приводит к ситуациям, когда запятая оказывается в середине, и ни одно правило ее не принимает.
+- **Примечание:** Проблема с запятыми в конце списка аргументов [исправлена в Python 3.6](https://bugs.python.org/issue9232). Замечания в [этом](https://bugs.python.org/issue9232#msg248399) сообщении кратко обсуждают различные варианты использования запятых в Python.
+
+---
+
+### ▶ Строки и обратные слэши
+
+**Вывод:**
+```py
+>>> print("\"")
+"
+
+>>> print(r"\"")
+\"
+
+>>> print(r"\")
+File "", line 1
+ print(r"\")
+ ^
+SyntaxError: EOL while scanning string literal
+
+>>> r'\'' == "\\'"
+True
+```
+
+#### 💡 Пояснение
+
+- В обычной строке python обратная косая черта используется для экранирования символов, которые могут иметь специальное значение (например, одинарная кавычка, двойная кавычка и сама обратная косая черта).
+ ```py
+ >>> "wt\"f"
+ 'wt"f'
+ ```
+- В необработанном строковом литерале (на что указывает префикс `r`) обратные косые черты передаются как есть, вместе с поведением экранирования следующего символа.
+ ```py
+ >>> r'wt\"f' == 'wt\\"f'
+ True
+ >>> print(repr(r'wt\"f')
+ 'wt\\"f'
+
+ >>> print("\n")
+
+ >>> print(r"\\n")
+ '\\n'
+ ```
+- Это означает, что когда синтаксический анализатор встречает обратную косую черту в необработанной строке, он ожидает, что за ней последует другой символ. А в нашем случае (`print(r"\")`) обратная косая черта вырвалась из концевой кавычки, оставив парсер без завершающей кавычки (отсюда `SyntaxError`). Вот почему обратный слеш не работает в конце необработанной строки.
+
+---
+
+### ▶ not knot!
+
+```py
+x = True
+y = False
+```
+
+**Результат:**
+```py
+>>> not x == y
+True
+>>> x == not y
+ File "", line 1
+ x == not y
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Объяснение:
+
+* Старшинство операторов влияет на то, как оценивается выражение, и оператор `==` имеет более высокий приоритет, чем оператор `not` в Python.
+* Поэтому `not x == y` эквивалентно `not (x == y)`, что эквивалентно `not (True == False)`, в итоге оценивающемуся в `True`.
+* Но `x == not y` вызывает `SyntaxError`, потому что его можно считать эквивалентным `(x == not) y`, а не `x == (not y)`, что можно было бы ожидать на первый взгляд.
+* Парсер ожидал, что лексема `not` будет частью оператора `not in` (потому что оба оператора `==` и `not in` имеют одинаковый приоритет), но после того, как он не смог найти лексему `in`, следующую за лексемой `not`, он выдает `SyntaxError`.
+
+---
+
+### ▶ Половина строк в тройных кавычках
+
+**Вывод:**
+```py
+>>> print('wtfpython''')
+wtfpython
+>>> print("wtfpython""")
+wtfpython
+>>> # The following statements raise `SyntaxError`
+>>> # print('''wtfpython')
+>>> # print("""wtfpython")
+ File "", line 3
+ print("""wtfpython")
+ ^
+SyntaxError: EOF while scanning triple-quoted string literal
+```
+
+#### 💡 Объяснение:
++ Python поддерживает неявную [конкатенацию строковых литералов](https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation), Пример,
+ ```
+ >>> print("wtf" "python")
+ wtfpython
+ >>> print("wtf" "") # or "wtf"""
+ wtf
+ ```
++ `'''` and `"""` также являются разделителями строк в Python, что вызывает SyntaxError, поскольку интерпретатор Python ожидал завершающую тройную кавычку в качестве разделителя при сканировании текущего встреченного строкового литерала с тройной кавычкой.
+---
+
+### ▶ Что не так с логическими значениями?
+
+1\.
+
+```py
+# A simple example to count the number of booleans and
+# integers in an iterable of mixed data types.
+mixed_list = [False, 1.0, "some_string", 3, True, [], False]
+integers_found_so_far = 0
+booleans_found_so_far = 0
+
+for item in mixed_list:
+ if isinstance(item, int):
+ integers_found_so_far += 1
+ elif isinstance(item, bool):
+ booleans_found_so_far += 1
+```
+
+**Результат:**
+```py
+>>> integers_found_so_far
+4
+>>> booleans_found_so_far
+0
+```
+
+
+2\.
+```py
+>>> some_bool = True
+>>> "wtf" * some_bool
+'wtf'
+>>> some_bool = False
+>>> "wtf" * some_bool
+''
+```
+
+3\.
+
+```py
+def tell_truth():
+ True = False
+ if True == False:
+ print("I have lost faith in truth!")
+```
+
+**Результат (< 3.x):**
+
+```py
+>>> tell_truth()
+I have lost faith in truth!
+```
+
+
+
+#### 💡 Объяснение:
+
+* `bool` это подкласс класса `int` в Python
+
+ ```py
+ >>> issubclass(bool, int)
+ True
+ >>> issubclass(int, bool)
+ False
+ ```
+
+* И `True` и так же `False` это инстансы класса `int`
+ ```py
+ >>> isinstance(True, int)
+ True
+ >>> isinstance(False, int)
+ True
+ ```
+
+* Целочисленное значение `True` равно `1`, а `False` равно `0`.
+ ```py
+ >>> int(True)
+ 1
+ >>> int(False)
+ 0
+ ```
+
+* Смотри StackOverflow [answer](https://stackoverflow.com/a/8169049/4354153) там есть объяснение.
+
+* Изначально в Python не было типа `bool` (использовали 0 для false и ненулевое значение 1 для true). В версиях 2.x были добавлены `True`, `False` и тип `bool`, но для обратной совместимости `True` и `False` нельзя было сделать константами. Они просто были встроенными переменными, и их можно было переназначить.
+
+* Python 3 был несовместим с предыдущими версиями, эту проблему наконец-то исправили, и поэтому последний фрагмент не будет работать с Python 3.x!
+
+---
+
+### ▶ Атрибуты класса и экземпляра
+
+1\.
+```py
+class A:
+ x = 1
+
+class B(A):
+ pass
+
+class C(A):
+ pass
+```
+
+**Результат:**
+```py
+>>> A.x, B.x, C.x
+(1, 1, 1)
+>>> B.x = 2
+>>> A.x, B.x, C.x
+(1, 2, 1)
+>>> A.x = 3
+>>> A.x, B.x, C.x # C.x changed, but B.x didn't
+(3, 2, 3)
+>>> a = A()
+>>> a.x, A.x
+(3, 3)
+>>> a.x += 1
+>>> a.x, A.x
+(4, 3)
+```
+
+2\.
+```py
+class SomeClass:
+ some_var = 15
+ some_list = [5]
+ another_list = [5]
+ def __init__(self, x):
+ self.some_var = x + 1
+ self.some_list = self.some_list + [x]
+ self.another_list += [x]
+```
+
+**Результат:**
+
+```py
+>>> some_obj = SomeClass(420)
+>>> some_obj.some_list
+[5, 420]
+>>> some_obj.another_list
+[5, 420]
+>>> another_obj = SomeClass(111)
+>>> another_obj.some_list
+[5, 111]
+>>> another_obj.another_list
+[5, 420, 111]
+>>> another_obj.another_list is SomeClass.another_list
+True
+>>> another_obj.another_list is some_obj.another_list
+True
+```
+
+#### 💡 Объяснение:
+
+* Переменные класса и переменные экземпляров класса внутренне обрабатываются как словари объекта класса. Если имя переменной не найдено в словаре текущего класса, оно ищется в родительских классах.
+* Оператор += изменяет изменяемый объект на месте, не создавая новый объект. Таким образом, изменение атрибута одного экземпляра влияет на другие экземпляры и атрибут класса также.
+---
+
+### ▶ Возврат None из генератора (yielding None)
+
+```py
+some_iterable = ('a', 'b')
+
+def some_func(val):
+ return "something"
+```
+
+**Результат (<= 3.7.x):**
+
+```py
+>>> [x for x in some_iterable]
+['a', 'b']
+>>> [(yield x) for x in some_iterable]
+ at 0x7f70b0a4ad58>
+>>> list([(yield x) for x in some_iterable])
+['a', 'b']
+>>> list((yield x) for x in some_iterable)
+['a', None, 'b', None]
+>>> list(some_func((yield x)) for x in some_iterable)
+['a', 'something', 'b', 'something']
+```
+
+#### 💡 Объяснение:
+- Это баг в обработке yield в генераторах и списочных выражениях CPython.
+- Исходный код и объяснение можно найти здесь: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions
+- Связанный отчет об ошибке: https://bugs.python.org/issue10544
+- В Python 3.8+ yield внутри списочных выражений больше не допускается и выдает SyntaxError.
+
+---
+
+
+### ▶ Yielding from... return! *
+
+1\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ yield from range(x)
+```
+
+**Результат (> 3.3):**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+Куда исчезло "wtf"? Это связано с каким-то особым эффектом yield from? Давайте проверим это. То же самое, это тоже не сработало.
+
+2\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ for i in range(x):
+ yield i
+```
+
+**Результат:**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+То же самое, это тоже не сработало. Что происходит?
+
+#### 💡 Объяснение:
+
++ С Python 3.3 стало возможным использовать оператор return в генераторах с возвращением значения (см. PEP380). В официальной документации говорится, что
+
+> "... return expr в генераторе вызывает исключение StopIteration(expr) при выходе из генератора."
+
++ В случае some_func(3) StopIteration возбуждается в начале из-за оператора return. Исключение StopIteration автоматически перехватывается внутри обертки list(...) и цикла for. Поэтому два вышеприведенных фрагмента приводят к пустому списку.
+
++ Чтобы получить ["wtf"] из генератора some_func, нам нужно перехватить исключение StopIteration.
+
+ ```py
+ try:
+ next(some_func(3))
+ except StopIteration as e:
+ some_string = e.value
+ ```
+
+ ```py
+ >>> some_string
+ ["wtf"]
+ ```
+
+---
+
+### ▶ Nan-reflexivity *
+
+
+
+1\.
+
+```py
+a = float('inf')
+b = float('nan')
+c = float('-iNf') # These strings are case-insensitive
+d = float('nan')
+```
+
+**Результат:**
+
+```py
+>>> a
+inf
+>>> b
+nan
+>>> c
+-inf
+>>> float('some_other_string')
+ValueError: could not convert string to float: some_other_string
+>>> a == -c # inf==inf
+True
+>>> None == None # None == None
+True
+>>> b == d # but nan!=nan
+False
+>>> 50 / a
+0.0
+>>> a / a
+nan
+>>> 23 + b
+nan
+```
+
+2\.
+
+```py
+>>> x = float('nan')
+>>> y = x / x
+>>> y is y # identity holds
+True
+>>> y == y # equality fails of y
+False
+>>> [y] == [y] # but the equality succeeds for the list containing y
+True
+```
+
+
+
+#### 💡 Объяснение:
+
+- `'inf'` и `'nan'` - это специальные строки (без учета регистра), которые при явном приведении к типу float используются для представления математической "бесконечности" и "не число" соответственно.
+
+- Поскольку согласно стандартам IEEE `NaN! = NaN`, соблюдение этого правила нарушает предположение о рефлексивности элемента коллекции в Python, то есть если x является частью коллекции, такой как `list`, реализации, такие как сравнение, основаны на предположении, что `x == x`. Из-за этого предположения сначала сравниваются идентификаторы (так как это быстрее),
+ когда сравниваются два элемента, а значения сравниваются только при несовпадении идентификаторов. Следующий фрагмент сделает вещи более ясными:
+
+ ```py
+ >>> x = float('nan')
+ >>> x == x, [x] == [x]
+ (False, True)
+ >>> y = float('nan')
+ >>> y == y, [y] == [y]
+ (False, True)
+ >>> x == y, [x] == [y]
+ (False, False)
+ ```
+
+ Поскольку идентификаторы `x` и `y` разные, рассматриваются значения, которые также различаются; следовательно, на этот раз сравнение возвращает `False`.
+
+- Интересное чтение: [Рефлексивность и другие основы цивилизации](https://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/)
+
+---
+
+### ▶ Мутируем немутируемое!
+
+
+
+Это может показаться тривиальным, если вы знаете, как работают ссылки в Python. Но если вы не знаете, то это может быть немного удивительно.
+
+```py
+some_tuple = ("A", "tuple", "with", "values")
+another_tuple = ([1, 2], [3, 4], [5, 6])
+```
+
+**Результат:**
+```py
+>>> some_tuple[2] = "change this"
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple[2].append(1000) #This throws no error
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000])
+>>> another_tuple[2] += [99, 999]
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000, 99, 999])
+```
+
+Но я думал, что кортежи неизменяемы... Что происходит?
+
+#### 💡 Объяснение:
+
+* Цитата из https://docs.python.org/3/reference/datamodel.html
+
+ > Неизменяемые последовательности
+ Объект неизменяемого типа последовательности не может измениться после создания. (Если объект содержит ссылки на другие объекты, эти другие объекты могут быть изменяемыми и могут быть изменены; однако набор объектов, на которые непосредственно ссылается неизменяемый объект, не может изменяться.)
+
+* Оператор `+=` изменяет список на месте. Присваивание элемента не работает, но когда возникает исключение, элемент уже был изменен на месте.
+* Также есть объяснение в официальном [Python FAQ](https://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works).
+
+---
+
+### ▶ Исчезновение переменной из внешней области видимости
+
+
+```py
+e = 7
+try:
+ raise Exception()
+except Exception as e:
+ pass
+```
+
+**Результат (Python 2.x):**
+```py
+>>> print(e)
+# prints nothing
+```
+
+**Результат (Python 3.x):**
+```py
+>>> print(e)
+NameError: name 'e' is not defined
+```
+
+#### 💡 Объяснение:
+
+* Source: https://docs.python.org/3/reference/compound_stmts.html#except
+
+ When an exception has been assigned using `as` target, it is cleared at the end of the `except` clause. This is as if
+
+ ```py
+ except E as N:
+ foo
+ ```
+
+ was translated into
+
+ ```py
+ except E as N:
+ try:
+ foo
+ finally:
+ del N
+ ```
+
+ 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.
+
+* 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 that have their separate inner-scopes. The example below illustrates this:
+
+ ```py
+ def f(x):
+ del(x)
+ print(x)
+
+ x = 5
+ y = [5, 4, 3]
+ ```
+
+ **Результат:**
+ ```py
+ >>> f(x)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> f(y)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> x
+ 5
+ >>> y
+ [5, 4, 3]
+ ```
+
+* In Python 2.x, the variable name `e` gets assigned to `Exception()` instance, so when you try to print, it prints nothing.
+
+ **Результат (Python 2.x):**
+ ```py
+ >>> e
+ Exception()
+ >>> print e
+ # Nothing is printed!
+ ```
+
+---
+
+
+### ▶ The mysterious key type conversion
+
+```py
+class SomeClass(str):
+ pass
+
+some_dict = {'s': 42}
+```
+
+**Результат:**
+```py
+>>> type(list(some_dict.keys())[0])
+str
+>>> s = SomeClass('s')
+>>> some_dict[s] = 40
+>>> some_dict # expected: Two different keys-value pairs
+{'s': 40}
+>>> type(list(some_dict.keys())[0])
+str
+```
+
+#### 💡 Объяснение:
+
+* Both the object `s` and the string `"s"` hash to the same value because `SomeClass` inherits the `__hash__` method of `str` class.
+* `SomeClass("s") == "s"` evaluates to `True` because `SomeClass` also inherits `__eq__` method from `str` class.
+* Since both the objects hash to the same value and are equal, they are represented by the same key in the dictionary.
+* For the desired behavior, we can redefine the `__eq__` method in `SomeClass`
+ ```py
+ class SomeClass(str):
+ def __eq__(self, other):
+ return (
+ type(self) is SomeClass
+ and type(other) is SomeClass
+ and super().__eq__(other)
+ )
+
+ # When we define a custom __eq__, Python stops automatically inheriting the
+ # __hash__ method, so we need to define it as well
+ __hash__ = str.__hash__
+
+ some_dict = {'s':42}
+ ```
+
+ **Результат:**
+ ```py
+ >>> s = SomeClass('s')
+ >>> some_dict[s] = 40
+ >>> some_dict
+ {'s': 40, 's': 42}
+ >>> keys = list(some_dict.keys())
+ >>> type(keys[0]), type(keys[1])
+ (__main__.SomeClass, str)
+ ```
+
+---
+
+### ▶ Посмотрим, сможете ли вы угадать что здесь?
+
+```py
+a, b = a[b] = {}, 5
+```
+
+**Результат:**
+```py
+>>> a
+{5: ({...}, 5)}
+```
+
+#### 💡 Объяснение:
+
+* According to [Python language reference](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements), assignment statements have the form
+ ```
+ (target_list "=")+ (expression_list | yield_expression)
+ ```
+ and
+
+> 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.
+
+* 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`).
+
+* After the expression list is evaluated, its 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`.
+
+* `a` is now assigned to `{}`, which is a mutable object.
+
+* 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`).
+
+* 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
+ ```py
+ >>> some_list = some_list[0] = [0]
+ >>> some_list
+ [[...]]
+ >>> some_list[0]
+ [[...]]
+ >>> some_list is some_list[0]
+ True
+ >>> some_list[0][0][0][0][0][0] == some_list
+ True
+ ```
+ Similar is the case in our example (`a[b][0]` is the same object as `a`)
+
+* So to sum it up, you can break the example down to
+ ```py
+ a, b = {}, 5
+ a[b] = a, b
+ ```
+ And the circular reference can be justified by the fact that `a[b][0]` is the same object as `a`
+ ```py
+ >>> a[b][0] is a
+ True
+ ```
+
+### ▶ Exceeds the limit for integer string conversion
+```py
+>>> # Python 3.10.6
+>>> int("2" * 5432)
+>>> # Python 3.10.8
+>>> int("2" * 5432)
+```
+**Вывод:**
+```py
+>>> # Python 3.10.6
+222222222222222222222222222222222222222222222222222222222222222...
+>>> # Python 3.10.8 and Python 3.11
+Traceback (most recent call last):
+ ...
+ValueError: Exceeds the limit (4300) for integer string conversion:
+ value has 5432 digits; use sys.set_int_max_str_digits()
+ to increase the limit.
+```
+#### 💡 Объяснение:
+Этот вызов `int()` прекрасно работает в Python 3.10.6 и вызывает ошибку ValueError в Python 3.10.8, 3.11. Обратите внимание, что Python все еще может работать с большими целыми числами. Ошибка возникает только при преобразовании между целыми числами и строками.
+К счастью, вы можете увеличить предел допустимого количества цифр, если ожидаете, что операция превысит его. Для этого можно воспользоваться одним из следующих способов:
+- -X int_max_str_digits command-line flag
+- set_int_max_str_digits() function from the sys module
+- PYTHONINTMAXSTRDIGITS environment variable
+
+[Смотри документацию](https://docs.python.org/3/library/stdtypes.html#int-max-str-digits) для получения более подробной информации об изменении лимита по умолчанию, если вы ожидаете, что ваш код превысит это значение.
+
+---
+
+## Section: Slippery Slopes
+
+### ▶ Modifying a dictionary while iterating over it
+
+```py
+x = {0: None}
+
+for i in x:
+ del x[i]
+ x[i+1] = None
+ print(i)
+```
+
+**Результат (Python 2.7- Python 3.5):**
+
+```
+0
+1
+2
+3
+4
+5
+6
+7
+```
+
+Yes, it runs for exactly **eight** times and stops.
+
+#### 💡 Объяснение:
+
+* Iteration over a dictionary that you edit at the same time is not supported.
+* 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.
+* How deleted keys are handled and when the resize occurs might be different for different Python implementations.
+* So for Python versions other than Python 2.7 - Python 3.5, the count might be different from 8 (but whatever the count is, it's going to be the same every time you run it). You can find some discussion around this [here](https://github.com/satwikkansal/wtfpython/issues/53) or in [this](https://stackoverflow.com/questions/44763802/bug-in-python-dict) StackOverflow thread.
+* Python 3.7.6 onwards, you'll see `RuntimeError: dictionary keys changed during iteration` exception if you try to do this.
+
+---
+
+### ▶ Stubborn `del` operation
+
+
+
+```py
+class SomeClass:
+ def __del__(self):
+ print("Deleted!")
+```
+
+**Результат:**
+1\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x # this should print "Deleted!"
+>>> del y
+Deleted!
+```
+
+Phew, deleted at last. You might have guessed what saved `__del__` from being called in our first attempt to delete `x`. Let's add more twists to the example.
+
+2\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x
+>>> y # check if y exists
+<__main__.SomeClass instance at 0x7f98a1a67fc8>
+>>> del y # Like previously, this should print "Deleted!"
+>>> globals() # oh, it didn't. Let's check all our global variables and confirm
+Deleted!
+{'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
+```
+
+Okay, now it's deleted :confused:
+
+#### 💡 Объяснение:
++ `del x` doesn’t directly call `x.__del__()`.
++ When `del x` is encountered, Python deletes the name `x` from current scope and decrements by 1 the reference count of the object `x` referenced. `__del__()` is called only when the object's reference count reaches zero.
++ In the second output snippet, `__del__()` was not called because the previous statement (`>>> y`) in the interactive interpreter created another reference to the same object (specifically, the `_` magic variable which references the result value of the last non `None` expression on the REPL), thus preventing the reference count from reaching zero when `del y` was encountered.
++ Calling `globals` (or really, executing anything that will have a non `None` result) caused `_` to reference the new result, dropping the existing reference. Now the reference count reached 0 and we can see "Deleted!" being printed (finally!).
+
+---
+
+### ▶ The out of scope variable
+
+
+1\.
+```py
+a = 1
+def some_func():
+ return a
+
+def another_func():
+ a += 1
+ return a
+```
+
+2\.
+```py
+def some_closure_func():
+ a = 1
+ def some_inner_func():
+ return a
+ return some_inner_func()
+
+def another_closure_func():
+ a = 1
+ def another_inner_func():
+ a += 1
+ return a
+ return another_inner_func()
+```
+
+**Результат:**
+```py
+>>> some_func()
+1
+>>> another_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+
+>>> some_closure_func()
+1
+>>> another_closure_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+```
+
+#### 💡 Объяснение:
+* 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.
+* To modify the outer scope variable `a` in `another_func`, we have to use the `global` keyword.
+ ```py
+ def another_func()
+ global a
+ a += 1
+ return a
+ ```
+
+ **Результат:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+* In `another_closure_func`, `a` becomes local to the scope of `another_inner_func`, but it has not been initialized previously in the same scope, which is why it throws an error.
+* To modify the outer scope variable `a` in `another_inner_func`, use the `nonlocal` keyword. The nonlocal statement is used to refer to variables defined in the nearest outer (excluding the global) scope.
+ ```py
+ def another_func():
+ a = 1
+ def another_inner_func():
+ nonlocal a
+ a += 1
+ return a
+ return another_inner_func()
+ ```
+
+ **Результат:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+* The keywords `global` and `nonlocal` tell the python interpreter to not declare new variables and look them up in the corresponding outer scopes.
+* 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.
+
+---
+
+### ▶ Deleting a list item while iterating
+
+```py
+list_1 = [1, 2, 3, 4]
+list_2 = [1, 2, 3, 4]
+list_3 = [1, 2, 3, 4]
+list_4 = [1, 2, 3, 4]
+
+for idx, item in enumerate(list_1):
+ del item
+
+for idx, item in enumerate(list_2):
+ list_2.remove(item)
+
+for idx, item in enumerate(list_3[:]):
+ list_3.remove(item)
+
+for idx, item in enumerate(list_4):
+ list_4.pop(idx)
+```
+
+**Результат:**
+```py
+>>> list_1
+[1, 2, 3, 4]
+>>> list_2
+[2, 4]
+>>> list_3
+[]
+>>> list_4
+[2, 4]
+```
+
+Can you guess why the output is `[2, 4]`?
+
+#### 💡 Объяснение:
+
+* 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.
+
+ ```py
+ >>> some_list = [1, 2, 3, 4]
+ >>> id(some_list)
+ 139798789457608
+ >>> id(some_list[:]) # Notice that python creates new object for sliced list.
+ 139798779601192
+ ```
+
+**Difference between `del`, `remove`, and `pop`:**
+* `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).
+* `remove` removes the first matching value, not a specific index, raises `ValueError` if the value is not found.
+* `pop` removes the element at a specific index and returns it, raises `IndexError` if an invalid index is specified.
+
+**Why the output is `[2, 4]`?**
+- 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.
+
+* 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
+* 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.
+
+---
+
+
+### ▶ Lossy zip of iterators *
+
+
+```py
+>>> numbers = list(range(7))
+>>> numbers
+[0, 1, 2, 3, 4, 5, 6]
+>>> first_three, remaining = numbers[:3], numbers[3:]
+>>> first_three, remaining
+([0, 1, 2], [3, 4, 5, 6])
+>>> numbers_iter = iter(numbers)
+>>> list(zip(numbers_iter, first_three))
+[(0, 0), (1, 1), (2, 2)]
+# so far so good, let's zip the remaining
+>>> list(zip(numbers_iter, remaining))
+[(4, 3), (5, 4), (6, 5)]
+```
+Where did element `3` go from the `numbers` list?
+
+#### 💡 Объяснение:
+
+- From Python [docs](https://docs.python.org/3.3/library/functions.html#zip), here's an approximate implementation of zip function,
+ ```py
+ def zip(*iterables):
+ sentinel = object()
+ iterators = [iter(it) for it in iterables]
+ while iterators:
+ result = []
+ for it in iterators:
+ elem = next(it, sentinel)
+ if elem is sentinel: return
+ result.append(elem)
+ yield tuple(result)
+ ```
+- So the function takes in arbitrary number of iterable objects, adds each of their items to the `result` list by calling the `next` function on them, and stops whenever any of the iterable is exhausted.
+- The caveat here is when any iterable is exhausted, the existing elements in the `result` list are discarded. That's what happened with `3` in the `numbers_iter`.
+- The correct way to do the above using `zip` would be,
+ ```py
+ >>> numbers = list(range(7))
+ >>> numbers_iter = iter(numbers)
+ >>> list(zip(first_three, numbers_iter))
+ [(0, 0), (1, 1), (2, 2)]
+ >>> list(zip(remaining, numbers_iter))
+ [(3, 3), (4, 4), (5, 5), (6, 6)]
+ ```
+ The first argument of zip should be the one with fewest elements.
+
+---
+
+### ▶ Loop variables leaking out!
+
+1\.
+```py
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Результат:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+But `x` was never defined outside the scope of for loop...
+
+2\.
+```py
+# This time let's initialize x first
+x = -1
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Результат:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+3\.
+
+**Результат (Python 2.x):**
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+4
+```
+
+**Результат (Python 3.x):**
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+1
+```
+
+#### 💡 Объяснение:
+
+- 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.
+
+- 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) changelog:
+
+ > "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."
+
+---
+
+### ▶ Beware of default mutable arguments!
+
+
+```py
+def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+```
+
+**Результат:**
+```py
+>>> some_func()
+['some_string']
+>>> some_func()
+['some_string', 'some_string']
+>>> some_func([])
+['some_string']
+>>> some_func()
+['some_string', 'some_string', 'some_string']
+```
+
+#### 💡 Объяснение:
+
+- 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.
+
+ ```py
+ def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+ **Результат:**
+ ```py
+ >>> some_func.__defaults__ #This will show the default argument values for the function
+ ([],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string'],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ >>> some_func([])
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ ```
+
+- 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:
+
+ ```py
+ def some_func(default_arg=None):
+ if default_arg is None:
+ default_arg = []
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+---
+
+### ▶ Catching the Exceptions
+
+```py
+some_list = [1, 2, 3]
+try:
+ # This should raise an ``IndexError``
+ print(some_list[4])
+except IndexError, ValueError:
+ print("Caught!")
+
+try:
+ # This should raise a ``ValueError``
+ some_list.remove(4)
+except IndexError, ValueError:
+ print("Caught again!")
+```
+
+**Результат (Python 2.x):**
+```py
+Caught!
+
+ValueError: list.remove(x): x not in list
+```
+
+**Результат (Python 3.x):**
+```py
+ File "", line 3
+ except IndexError, ValueError:
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Объяснение
+
+* 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,
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ # This should raise a ``ValueError``
+ some_list.remove(4)
+ except (IndexError, ValueError), e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Результат (Python 2.x):**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+ **Результат (Python 3.x):**
+ ```py
+ File "", line 4
+ except (IndexError, ValueError), e:
+ ^
+ IndentationError: unindent does not match any outer indentation level
+ ```
+
+* 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,
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ some_list.remove(4)
+
+ except (IndexError, ValueError) as e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Результат:**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+
+---
+
+### ▶ Same operands, different story!
+
+1\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a = a + [5, 6, 7, 8]
+```
+
+**Результат:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4]
+```
+
+2\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a += [5, 6, 7, 8]
+```
+
+**Результат:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4, 5, 6, 7, 8]
+```
+
+#### 💡 Объяснение:
+
+* `a += b` doesn't always behave the same way as `a = a + b`. Classes *may* implement the *`op=`* operators differently, and lists do this.
+
+* The expression `a = a + [5,6,7,8]` generates a new list and sets `a`'s reference to that new list, leaving `b` unchanged.
+
+* 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.
+
+---
+
+### ▶ Name resolution ignoring class scope
+
+1\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = (x for i in range(10))
+```
+
+**Результат:**
+```py
+>>> list(SomeClass.y)[0]
+5
+```
+
+2\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = [x for i in range(10)]
+```
+
+**Результат (Python 2.x):**
+```py
+>>> SomeClass.y[0]
+17
+```
+
+**Результат (Python 3.x):**
+```py
+>>> SomeClass.y[0]
+5
+```
+
+#### 💡 Объяснение
+- Scopes nested inside class definition ignore names bound at the class level.
+- A generator expression has its own scope.
+- Starting from Python 3.X, list comprehensions also have their own scope.
+
+---
+
+### ▶ Rounding like a banker *
+
+Let's implement a naive function to get the middle element of a list:
+```py
+def get_middle(some_list):
+ mid_index = round(len(some_list) / 2)
+ return some_list[mid_index - 1]
+```
+
+**Python 3.x:**
+```py
+>>> get_middle([1]) # looks good
+1
+>>> get_middle([1,2,3]) # looks good
+2
+>>> get_middle([1,2,3,4,5]) # huh?
+2
+>>> len([1,2,3,4,5]) / 2 # good
+2.5
+>>> round(len([1,2,3,4,5]) / 2) # why?
+2
+```
+It seems as though Python rounded 2.5 to 2.
+
+#### 💡 Объяснение:
+
+- This is not a float precision error, in fact, this behavior is intentional. Since Python 3.0, `round()` uses [banker's rounding](https://en.wikipedia.org/wiki/Rounding#Round_half_to_even) where .5 fractions are rounded to the nearest **even** number:
+
+```py
+>>> round(0.5)
+0
+>>> round(1.5)
+2
+>>> round(2.5)
+2
+>>> import numpy # numpy does the same
+>>> numpy.round(0.5)
+0.0
+>>> numpy.round(1.5)
+2.0
+>>> numpy.round(2.5)
+2.0
+```
+
+- This is the recommended way to round .5 fractions as described in [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules). However, the other way (round away from zero) is taught in school most of the time, so banker's rounding is likely not that well known. Furthermore, some of the most popular programming languages (for example: JavaScript, Java, C/C++, Ruby, Rust) do not use banker's rounding either. Therefore, this is still quite special to Python and may result in confusion when rounding fractions.
+- See the [round() docs](https://docs.python.org/3/library/functions.html#round) or [this stackoverflow thread](https://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior) for more information.
+- Note that `get_middle([1])` only returned 1 because the index was `round(0.5) - 1 = 0 - 1 = -1`, returning the last element in the list.
+
+---
+
+### ▶ Needles in a Haystack *
+
+
+
+I haven't met even a single experience Pythonist till date who has not come across one or more of the following scenarios,
+
+1\.
+
+```py
+x, y = (0, 1) if True else None, None
+```
+
+**Результат:**
+
+```py
+>>> x, y # expected (0, 1)
+((0, 1), None)
+```
+
+2\.
+
+```py
+t = ('one', 'two')
+for i in t:
+ print(i)
+
+t = ('one')
+for i in t:
+ print(i)
+
+t = ()
+print(t)
+```
+
+**Результат:**
+
+```py
+one
+two
+o
+n
+e
+tuple()
+```
+
+3\.
+
+```
+ten_words_list = [
+ "some",
+ "very",
+ "big",
+ "list",
+ "that"
+ "consists",
+ "of",
+ "exactly",
+ "ten",
+ "words"
+]
+```
+
+**Результат**
+
+```py
+>>> len(ten_words_list)
+9
+```
+
+4\. Not asserting strongly enough
+
+```py
+a = "python"
+b = "javascript"
+```
+
+**Результат:**
+
+```py
+# An assert statement with an assertion failure message.
+>>> assert(a == b, "Both languages are different")
+# No AssertionError is raised
+```
+
+5\.
+
+```py
+some_list = [1, 2, 3]
+some_dict = {
+ "key_1": 1,
+ "key_2": 2,
+ "key_3": 3
+}
+
+some_list = some_list.append(4)
+some_dict = some_dict.update({"key_4": 4})
+```
+
+**Результат:**
+
+```py
+>>> print(some_list)
+None
+>>> print(some_dict)
+None
+```
+
+6\.
+
+```py
+def some_recursive_func(a):
+ if a[0] == 0:
+ return
+ a[0] -= 1
+ some_recursive_func(a)
+ return a
+
+def similar_recursive_func(a):
+ if a == 0:
+ return a
+ a -= 1
+ similar_recursive_func(a)
+ return a
+```
+
+**Результат:**
+
+```py
+>>> some_recursive_func([5, 0])
+[0, 0]
+>>> similar_recursive_func(5)
+4
+```
+
+#### 💡 Объяснение:
+
+* For 1, the correct statement for expected behavior is `x, y = (0, 1) if True else (None, None)`.
+
+* 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.
+
+* `()` is a special token and denotes empty `tuple`.
+
+* In 3, as you might have already figured out, there's a missing comma after 5th element (`"that"`) in the list. So by implicit string literal concatenation,
+
+ ```py
+ >>> ten_words_list
+ ['some', 'very', 'big', 'list', 'thatconsists', 'of', 'exactly', 'ten', 'words']
+ ```
+
+* No `AssertionError` was raised in 4th snippet because instead of asserting the individual expression `a == b`, we're asserting entire tuple. The following snippet will clear things up,
+
+ ```py
+ >>> a = "python"
+ >>> b = "javascript"
+ >>> assert a == b
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError
+
+ >>> assert (a == b, "Values are not equal")
+ :1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
+
+ >>> assert a == b, "Values are not equal"
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError: Values are not equal
+ ```
+
+* As for the fifth snippet, 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](https://docs.python.org/3/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list)).
+
+* Last one should be fairly obvious, mutable object (like `list`) can be altered in the function, and the reassignment of an immutable (`a -= 1`) is not an alteration of the value.
+
+* Being aware of these nitpicks can save you hours of debugging effort in the long run.
+
+---
+
+
+### ▶ Splitsies *
+
+```py
+>>> 'a'.split()
+['a']
+
+# is same as
+>>> 'a'.split(' ')
+['a']
+
+# but
+>>> len(''.split())
+0
+
+# isn't the same as
+>>> len(''.split(' '))
+1
+```
+
+#### 💡 Объяснение:
+
+- It might appear at first that the default separator for split is a single space `' '`, but as per the [docs](https://docs.python.org/3/library/stdtypes.html#str.split)
+ > If sep is not specified or is `None`, a different splitting algorithm is applied: runs of consecutive whitespace are regarded as a single separator, and the result will contain no empty strings at the start or end if the string has leading or trailing whitespace. Consequently, splitting an empty string or a string consisting of just whitespace with a None separator returns `[]`.
+ > If sep is given, consecutive delimiters are not grouped together and are deemed to delimit empty strings (for example, `'1,,2'.split(',')` returns `['1', '', '2']`). Splitting an empty string with a specified separator returns `['']`.
+- Noticing how the leading and trailing whitespaces are handled in the following snippet will make things clear,
+ ```py
+ >>> ' a '.split(' ')
+ ['', 'a', '']
+ >>> ' a '.split()
+ ['a']
+ >>> ''.split(' ')
+ ['']
+ ```
+
+---
+
+### ▶ Wild imports *
+
+
+
+```py
+# File: module.py
+
+def some_weird_name_func_():
+ print("works!")
+
+def _another_weird_name_func():
+ print("works!")
+
+```
+
+**Результат**
+
+```py
+>>> from module import *
+>>> some_weird_name_func_()
+"works!"
+>>> _another_weird_name_func()
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name '_another_weird_name_func' is not defined
+```
+
+#### 💡 Объяснение:
+
+- It is often advisable to not use wildcard imports. The first obvious reason for this is, in wildcard imports, the names with a leading underscore don't get imported. This may lead to errors during runtime.
+- Had we used `from ... import a, b, c` syntax, the above `NameError` wouldn't have occurred.
+ ```py
+ >>> from module import some_weird_name_func_, _another_weird_name_func
+ >>> _another_weird_name_func()
+ works!
+ ```
+- If you really want to use wildcard imports, then you'd have to define the list `__all__` in your module that will contain a list of public objects that'll be available when we do wildcard imports.
+ ```py
+ __all__ = ['_another_weird_name_func']
+
+ def some_weird_name_func_():
+ print("works!")
+
+ def _another_weird_name_func():
+ print("works!")
+ ```
+ **Результат**
+
+ ```py
+ >>> _another_weird_name_func()
+ "works!"
+ >>> some_weird_name_func_()
+ Traceback (most recent call last):
+ File "", line 1, in
+ NameError: name 'some_weird_name_func_' is not defined
+ ```
+
+---
+
+### ▶ All sorted? *
+
+
+
+```py
+>>> x = 7, 8, 9
+>>> sorted(x) == x
+False
+>>> sorted(x) == sorted(x)
+True
+
+>>> y = reversed(x)
+>>> sorted(y) == sorted(y)
+False
+```
+
+#### 💡 Объяснение:
+
+- The `sorted` method always returns a list, and comparing lists and tuples always returns `False` in Python.
+
+- ```py
+ >>> [] == tuple()
+ False
+ >>> x = 7, 8, 9
+ >>> type(x), type(sorted(x))
+ (tuple, list)
+ ```
+
+- Unlike `sorted`, the `reversed` method returns an iterator. Why? Because sorting requires the iterator to be either modified in-place or use an extra container (a list), whereas reversing can simply work by iterating from the last index to the first.
+
+- So during comparison `sorted(y) == sorted(y)`, the first call to `sorted()` will consume the iterator `y`, and the next call will just return an empty list.
+
+ ```py
+ >>> x = 7, 8, 9
+ >>> y = reversed(x)
+ >>> sorted(y), sorted(y)
+ ([7, 8, 9], [])
+ ```
+
+---
+
+### ▶ Midnight time doesn't exist?
+
+```py
+from datetime import datetime
+
+midnight = datetime(2018, 1, 1, 0, 0)
+midnight_time = midnight.time()
+
+noon = datetime(2018, 1, 1, 12, 0)
+noon_time = noon.time()
+
+if midnight_time:
+ print("Time at midnight is", midnight_time)
+
+if noon_time:
+ print("Time at noon is", noon_time)
+```
+
+**Результат (< 3.5):**
+
+```py
+('Time at noon is', datetime.time(12, 0))
+```
+The midnight time is not printed.
+
+#### 💡 Объяснение:
+
+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."
+
+---
+---
+
+
+
+## Section: The Hidden treasures!
+
+This section contains a few lesser-known and interesting things about Python that most beginners like me are unaware of (well, not anymore).
+
+### ▶ Okay Python, Can you make me fly?
+
+Well, here you go
+
+```py
+import antigravity
+```
+
+**Результат:**
+Sshh... It's a super-secret.
+
+#### 💡 Объяснение:
++ `antigravity` module is one of the few easter eggs released by Python developers.
++ `import antigravity` opens up a web browser pointing to the [classic XKCD comic](https://xkcd.com/353/) about Python.
++ 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/).
+
+---
+
+### ▶ `goto`, but why?
+
+
+```py
+from goto import goto, label
+for i in range(9):
+ for j in range(9):
+ for k in range(9):
+ print("I am trapped, please rescue!")
+ if k == 2:
+ goto .breakout # breaking out from a deeply nested loop
+label .breakout
+print("Freedom!")
+```
+
+**Результат (Python 2.3):**
+```py
+I am trapped, please rescue!
+I am trapped, please rescue!
+Freedom!
+```
+
+#### 💡 Объяснение:
+- 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.
+- Current versions of Python do not have this module.
+- 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.
+
+---
+
+### ▶ Держитесь!
+
+Если вы относитесь к тем людям, которым не нравится использование пробелов в Python для обозначения диапазонов, вы можете использовать C-стиль {} импортировав это,
+
+```py
+from __future__ import braces
+```
+
+**Результат:**
+```py
+ File "some_file.py", line 1
+ from __future__ import braces
+SyntaxError: not a chance
+```
+
+Скобочки? Ни за что! Если это разочаровывало вас, используйте PHP :). Хорошо, еще одна удивительная вещь, можете ли вы найти ошибку
+`SyntaxError` которая вызвана в модуле `__future__` [code](https://github.com/python/cpython/blob/master/Lib/__future__.py)?
+
+#### 💡 Объяснение:
++ The `__future__` module is normally used to provide features from future versions of Python. The "future" in this specific context is however, ironic.
++ This is an easter egg concerned with the community's feelings on this issue.
++ The code is actually present [here](https://github.com/python/cpython/blob/025eb98dc0c1dc27404df6c544fc2944e0fa9f3a/Python/future.c#L49) in `future.c` file.
++ When the CPython compiler encounters a [future statement](https://docs.python.org/3.3/reference/simple_stmts.html#future-statements), it first runs the appropriate code in `future.c` before treating it as a normal import statement.
+
+---
+
+### ▶ Давайте познакомимся с дружелюбным Дядей Барри
+
+
+**Результат (Python 3.x)**
+```py
+>>> from __future__ import barry_as_FLUFL
+>>> "Ruby" != "Python" # there's no doubt about it
+ File "some_file.py", line 1
+ "Ruby" != "Python"
+ ^
+SyntaxError: invalid syntax
+
+>>> "Ruby" <> "Python"
+True
+```
+
+Вот так просто.
+
+#### 💡 Объяснение:
+- Это относится к [PEP-401](https://www.python.org/dev/peps/pep-0401/) released on April 1, 2009 (now you know, what it means).
+- Цитируя из PEP-401
+
+ > 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.
+ Некоторые считают, что оператор неравенства != в Python 3.0 отвратительный (хотя в других языках это вполне привычная и узнаваемая конструкция) и вызывал боль, FLUFL разрешает единственный вариант оператора неравенства в виде ромба <>.
+- У Дяди Барри было еще много чего рассказать в PEP; вы можете прочитать их [здесь](https://www.python.org/dev/peps/pep-0401/).
+- Это работает хорошо в интерактивной среде, но при запуске через файл python вызывает `SyntaxError` (смотри этот [ишью](https://github.com/satwikkansal/wtfpython/issues/94)). Однако вы можете обернуть оператор внутри `eval` или `compile`, чтобы заставить его работать (но зачем?)
+ ```py
+ from __future__ import barry_as_FLUFL
+ print(eval('"Ruby" <> "Python"'))
+ ```
+
+---
+
+### ▶ Даже Python понимает, что любовь - это сложно.
+
+```py
+import this
+```
+
+Подождите, что **это** (this) такое? Это любовь! :heart:
+
+**Результат:**
+```
+Дзен Python, от Тима Петерса
+
+Красивое лучше, чем уродливое.
+Явное лучше, чем неявное.
+Простое лучше, чем сложное.
+Сложное лучше, чем запутанное.
+Плоское лучше, чем вложенное.
+Разреженное лучше, чем плотное.
+Читаемость имеет значение.
+Особые случаи не настолько особые, чтобы нарушать правила.
+При этом практичность важнее безупречности.
+Ошибки никогда не должны замалчиваться.
+Если они не замалчиваются явно.
+Встретив двусмысленность, отбрось искушение угадать.
+Должен существовать один и, желательно, только один очевидный способ сделать это.
+Хотя он поначалу может быть и не очевиден, если вы не голландец [^1].
+Сейчас лучше, чем никогда.
+Хотя никогда зачастую лучше, чем прямо сейчас.
+Если реализацию сложно объяснить — идея плоха.
+Если реализацию легко объяснить — идея, возможно, хороша.
+Пространства имён — отличная штука! Будем делать их больше!
+```
+
+Это Дзен Python!
+
+```py
+>>> love = this
+>>> this is love
+True
+>>> love is True
+False
+>>> love is False
+False
+>>> love is not True or False
+True
+>>> love is not True or False; love is love # Love is complicated
+True
+```
+
+#### 💡 Объяснение:
+
+* `this` module in Python is an easter egg for The Zen Of Python ([PEP 20](https://www.python.org/dev/peps/pep-0020)).
+* 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).
+* Regarding the statement `love is not True or False; love is love`, ironic but it's self-explanatory (if not, please see the examples related to `is` and `is not` operators).
+
+* Модуль `this` в Python - это пасхальное яйцо для The Zen Of Python ([PEP 20](https://www.python.org/dev/peps/pep-0020)).
+* И если вы думаете, что это уже достаточно интересно, посмотрите реализацию [this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py). Интересно, что **код для дзена нарушает сам себя** (и это, вероятно, единственное место, где это происходит, но это не точно).
+* Что касается утверждения `любовь не является истиной или ложью; любовь - это любовь`, иронично, но это самоочевидно (если нет, пожалуйста, посмотрите примеры, связанные с операторами `is` и `is not`).
+
+---
+
+### ▶ Yes, it exists!
+
+**The `else` clause for loops.** One typical example might be:
+
+```py
+ def does_exists_num(l, to_find):
+ for num in l:
+ if num == to_find:
+ print("Exists!")
+ break
+ else:
+ print("Does not exist")
+```
+
+**Результат:**
+```py
+>>> some_list = [1, 2, 3, 4, 5]
+>>> does_exists_num(some_list, 4)
+Exists!
+>>> does_exists_num(some_list, -1)
+Does not exist
+```
+
+**The `else` clause in exception handling.** An example,
+
+```py
+try:
+ pass
+except:
+ print("Exception occurred!!!")
+else:
+ print("Try block executed successfully...")
+```
+
+**Результат:**
+```py
+Try block executed successfully...
+```
+
+#### 💡 Объяснение:
+- The `else` clause after a loop is executed only when there's no explicit `break` after all the iterations. You can think of it as a "nobreak" clause.
+- `else` clause after a try block is also called "completion clause" as reaching the `else` clause in a `try` statement means that the try block actually completed successfully.
+
+---
+### ▶ Ellipsis *
+
+```py
+def some_func():
+ Ellipsis
+```
+
+**Результат**
+```py
+>>> some_func()
+# No output, No Error
+
+>>> SomeRandomString
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'SomeRandomString' is not defined
+
+>>> Ellipsis
+Ellipsis
+```
+
+#### 💡 Объяснение
+- In Python, `Ellipsis` is a globally available built-in object which is equivalent to `...`.
+ ```py
+ >>> ...
+ Ellipsis
+ ```
+- Ellipsis can be used for several purposes,
+ + As a placeholder for code that hasn't been written yet (just like `pass` statement)
+ + In slicing syntax to represent the full slices in remaining direction
+ ```py
+ >>> import numpy as np
+ >>> three_dimensional_array = np.arange(8).reshape(2, 2, 2)
+ array([
+ [
+ [0, 1],
+ [2, 3]
+ ],
+
+ [
+ [4, 5],
+ [6, 7]
+ ]
+ ])
+ ```
+ So our `three_dimensional_array` is an array of array of arrays. Let's say we want to print the second element (index `1`) of all the innermost arrays, we can use Ellipsis to bypass all the preceding dimensions
+ ```py
+ >>> three_dimensional_array[:,:,1]
+ array([[1, 3],
+ [5, 7]])
+ >>> three_dimensional_array[..., 1] # using Ellipsis.
+ array([[1, 3],
+ [5, 7]])
+ ```
+ Note: this will work for any number of dimensions. You can even select slice in first and last dimension and ignore the middle ones this way (`n_dimensional_array[firs_dim_slice, ..., last_dim_slice]`)
+ + In [type hinting](https://docs.python.org/3/library/typing.html) to indicate only a part of the type (like `(Callable[..., int]` or `Tuple[str, ...]`))
+ + You may also use Ellipsis as a default function argument (in the cases when you want to differentiate between the "no argument passed" and "None value passed" scenarios).
+
+---
+
+### ▶ Inpinity
+
+The spelling is intended. Please, don't submit a patch for this.
+
+**Результат (Python 3.x):**
+```py
+>>> infinity = float('infinity')
+>>> hash(infinity)
+314159
+>>> hash(float('-inf'))
+-314159
+```
+
+#### 💡 Объяснение:
+- Hash of infinity is 10⁵ x π.
+- Interestingly, the hash of `float('-inf')` is "-10⁵ x π" in Python 3, whereas "-10⁵ x e" in Python 2.
+
+---
+
+### ▶ Let's mangle
+
+1\.
+```py
+class Yo(object):
+ def __init__(self):
+ self.__honey = True
+ self.bro = True
+```
+
+**Результат:**
+```py
+>>> Yo().bro
+True
+>>> Yo().__honey
+AttributeError: 'Yo' object has no attribute '__honey'
+>>> Yo()._Yo__honey
+True
+```
+
+2\.
+```py
+class Yo(object):
+ def __init__(self):
+ # Let's try something symmetrical this time
+ self.__honey__ = True
+ self.bro = True
+```
+
+**Результат:**
+```py
+>>> Yo().bro
+True
+
+>>> Yo()._Yo__honey__
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'Yo' object has no attribute '_Yo__honey__'
+```
+
+Why did `Yo()._Yo__honey` work?
+
+3\.
+
+```py
+_A__variable = "Some value"
+
+class A(object):
+ def some_func(self):
+ return __variable # not initialized anywhere yet
+```
+
+**Результат:**
+```py
+>>> A().__variable
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'A' object has no attribute '__variable'
+
+>>> A().some_func()
+'Some value'
+```
+
+
+#### 💡 Объяснение:
+
+* [Name Mangling](https://en.wikipedia.org/wiki/Name_mangling) is used to avoid naming collisions between different namespaces.
+* In Python, the interpreter modifies (mangles) the class member names starting with `__` (double underscore a.k.a "dunder") and not ending with more than one trailing underscore by adding `_NameOfTheClass` in front.
+* So, to access `__honey` attribute in the first snippet, we had to append `_Yo` to the front, which would prevent conflicts with the same name attribute defined in any other class.
+* But then why didn't it work in the second snippet? Because name mangling excludes the names ending with double underscores.
+* The third snippet was also a consequence of name mangling. The name `__variable` in the statement `return __variable` was mangled to `_A__variable`, which also happens to be the name of the variable we declared in the outer scope.
+* Also, if the mangled name is longer than 255 characters, truncation will happen.
+
+---
+---
+
+## Section: Appearances are deceptive!
+
+### ▶ Skipping lines?
+
+**Результат:**
+```py
+>>> value = 11
+>>> valuе = 32
+>>> value
+11
+```
+
+Wut?
+
+**Note:** The easiest way to reproduce this is to simply copy the statements from the above snippet and paste them into your file/shell.
+
+#### 💡 Объяснение
+
+Some non-Western characters look identical to letters in the English alphabet but are considered distinct by the interpreter.
+
+```py
+>>> ord('е') # cyrillic 'e' (Ye)
+1077
+>>> ord('e') # latin 'e', as used in English and typed using standard keyboard
+101
+>>> 'е' == 'e'
+False
+
+>>> value = 42 # latin e
+>>> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
+>>> value
+42
+```
+
+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.
+
+---
+
+### ▶ Teleportation
+
+
+
+```py
+# `pip install numpy` first.
+import numpy as np
+
+def energy_send(x):
+ # Initializing a numpy array
+ np.array([float(x)])
+
+def energy_receive():
+ # Return an empty numpy array
+ return np.empty((), dtype=np.float).tolist()
+```
+
+**Результат:**
+```py
+>>> energy_send(123.456)
+>>> energy_receive()
+123.456
+```
+
+Where's the Nobel Prize?
+
+#### 💡 Объяснение:
+
+* Notice that the numpy array created in the `energy_send` function is not returned, so that memory space is free to reallocate.
+* `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).
+
+---
+
+### ▶ Well, something is fishy...
+
+```py
+def square(x):
+ """
+ A simple function to calculate the square of a number by addition.
+ """
+ sum_so_far = 0
+ for counter in range(x):
+ sum_so_far = sum_so_far + x
+ return sum_so_far
+```
+
+**Результат (Python 2.x):**
+
+```py
+>>> square(10)
+10
+```
+
+Shouldn't that be 100?
+
+**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.
+
+#### 💡 Объяснение
+
+* **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.
+* This is how Python handles tabs:
+
+ > 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 <...>
+* So the "tab" at the last line of `square` function is replaced with eight spaces, and it gets into the loop.
+* Python 3 is kind enough to throw an error for such cases automatically.
+
+ **Результат (Python 3.x):**
+ ```py
+ TabError: inconsistent use of tabs and spaces in indentation
+ ```
+
+---
+---
+
+## Section: Miscellaneous
+
+
+### ▶ `+=` is faster
+
+
+```py
+# using "+", three strings:
+>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.25748300552368164
+# using "+=", three strings:
+>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.012188911437988281
+```
+
+#### 💡 Объяснение:
++ `+=` 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.
+
+---
+
+### ▶ Let's make a giant string!
+
+```py
+def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s += "xyz"
+ assert len(s) == 3*iters
+
+def add_bytes_with_plus(iters):
+ s = b""
+ for i in range(iters):
+ s += b"xyz"
+ assert len(s) == 3*iters
+
+def add_string_with_format(iters):
+ fs = "{}"*iters
+ s = fs.format(*(["xyz"]*iters))
+ assert len(s) == 3*iters
+
+def add_string_with_join(iters):
+ l = []
+ for i in range(iters):
+ l.append("xyz")
+ s = "".join(l)
+ assert len(s) == 3*iters
+
+def convert_list_to_string(l, iters):
+ s = "".join(l)
+ assert len(s) == 3*iters
+```
+
+**Результат:**
+
+```py
+# Executed in ipython shell using %timeit for better readability of results.
+# You can also use the timeit module in normal python shell/scriptm=, example usage below
+# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
+
+>>> NUM_ITERS = 1000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS)
+124 µs ± 4.73 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS)
+211 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS)
+61 µs ± 2.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS)
+117 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS)
+10.1 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+Let's increase the number of iterations by a factor of 10.
+
+```py
+>>> NUM_ITERS = 10000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linear increase in execution time
+1.26 ms ± 76.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS) # Quadratic increase
+6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS) # Linear increase
+645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS) # Linear increase
+1.17 ms ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS) # Linear increase
+86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+#### 💡 Объяснение
+- You can read more about [timeit](https://docs.python.org/3/library/timeit.html) or [%timeit](https://ipython.org/ipython-doc/dev/interactive/magics.html#magic-timeit) on these links. They are used to measure the execution time of code pieces.
+- 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)
+- Therefore, it's advised to use `.format.` or `%` syntax (however, they are slightly slower than `+` for very short strings).
+- Or better, if already you've contents available in the form of an iterable object, then use `''.join(iterable_object)` which is much faster.
+- Unlike `add_bytes_with_plus` because of the `+=` optimizations discussed in the previous example, `add_string_with_plus` didn't show a quadratic increase in execution time. Had the statement been `s = s + "x" + "y" + "z"` instead of `s += "xyz"`, the increase would have been quadratic.
+ ```py
+ def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s = s + "x" + "y" + "z"
+ assert len(s) == 3*iters
+
+ >>> %timeit -n100 add_string_with_plus(1000)
+ 388 µs ± 22.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+ >>> %timeit -n100 add_string_with_plus(10000) # Quadratic increase in execution time
+ 9 ms ± 298 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+ ```
+- So many ways to format and create a giant string are somewhat in contrast to the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), according to which,
+
+ > There should be one-- and preferably only one --obvious way to do it.
+
+---
+
+### ▶ Slowing down `dict` lookups *
+
+```py
+some_dict = {str(i): 1 for i in range(1_000_000)}
+another_dict = {str(i): 1 for i in range(1_000_000)}
+```
+
+**Результат:**
+```py
+>>> %timeit some_dict['5']
+28.6 ns ± 0.115 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> some_dict[1] = 1
+>>> %timeit some_dict['5']
+37.2 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+
+>>> %timeit another_dict['5']
+28.5 ns ± 0.142 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> another_dict[1] # Trying to access a key that doesn't exist
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 1
+>>> %timeit another_dict['5']
+38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+```
+Why are same lookups becoming slower?
+
+#### 💡 Объяснение:
++ CPython has a generic dictionary lookup function that handles all types of keys (`str`, `int`, any object ...), and a specialized one for the common case of dictionaries composed of `str`-only keys.
++ The specialized function (named `lookdict_unicode` in CPython's [source](https://github.com/python/cpython/blob/522691c46e2ae51faaad5bbbce7d959dd61770df/Objects/dictobject.c#L841)) knows all existing keys (including the looked-up key) are strings, and uses the faster & simpler string comparison to compare keys, instead of calling the `__eq__` method.
++ The first time a `dict` instance is accessed with a non-`str` key, it's modified so future lookups use the generic function.
++ This process is not reversible for the particular `dict` instance, and the key doesn't even have to exist in the dictionary. That's why attempting a failed lookup has the same effect.
+
+
+### ▶ Bloating instance `dict`s *
+
+```py
+import sys
+
+class SomeClass:
+ def __init__(self):
+ self.some_attr1 = 1
+ self.some_attr2 = 2
+ self.some_attr3 = 3
+ self.some_attr4 = 4
+
+
+def dict_size(o):
+ return sys.getsizeof(o.__dict__)
+
+```
+
+**Результат:** (Python 3.8, other Python 3 versions may vary a little)
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104
+>>> dict_size(o2)
+104
+>>> del o1.some_attr1
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+>>> dict_size(o1)
+232
+```
+
+Let's try again... In a new interpreter:
+
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104 # as expected
+>>> o1.some_attr5 = 5
+>>> o1.some_attr6 = 6
+>>> dict_size(o1)
+360
+>>> dict_size(o2)
+272
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+```
+
+What makes those dictionaries become bloated? And why are newly created objects bloated as well?
+
+#### 💡 Объяснение:
++ CPython is able to reuse the same "keys" object in multiple dictionaries. This was added in [PEP 412](https://www.python.org/dev/peps/pep-0412/) with the motivation to reduce memory usage, specifically in dictionaries of instances - where keys (instance attributes) tend to be common to all instances.
++ This optimization is entirely seamless for instance dictionaries, but it is disabled if certain assumptions are broken.
++ Key-sharing dictionaries do not support deletion; if an instance attribute is deleted, the dictionary is "unshared", and key-sharing is disabled for all future instances of the same class.
++ Additionaly, if the dictionary keys have been resized (because new keys are inserted), they are kept shared *only* if they are used by a exactly single dictionary (this allows adding many attributes in the `__init__` of the very first created instance, without causing an "unshare"). If multiple instances exist when a resize happens, key-sharing is disabled for all future instances of the same class: CPython can't tell if your instances are using the same set of attributes anymore, and decides to bail out on attempting to share their keys.
++ A small tip, if you aim to lower your program's memory footprint: don't delete instance attributes, and make sure to initialize all attributes in your `__init__`!
+
+
+### ▶ Minor Ones *
+
+* `join()` is a string operation instead of list operation. (sort of counter-intuitive at first usage)
+
+ **💡 Объяснение:** 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.
+
+* Few weird looking but semantically correct statements:
+ + `[] = ()` is a semantically correct statement (unpacking an empty `tuple` into an empty `list`)
+ + `'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.
+ + `3 --0-- 5 == 8` and `--5 == 5` are both semantically correct statements and evaluate to `True`.
+
+* 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.
+ ```py
+ >>> a = 5
+ >>> a
+ 5
+ >>> ++a
+ 5
+ >>> --a
+ 5
+ ```
+
+ **💡 Объяснение:**
+ + There is no `++` operator in Python grammar. It is actually two `+` operators.
+ + `++a` parses as `+(+a)` which translates to `a`. Similarly, the output of the statement `--a` can be justified.
+ + 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.
+
+* You must be aware of the Walrus operator in Python. But have you ever heard about *the space-invader operator*?
+ ```py
+ >>> a = 42
+ >>> a -=- 1
+ >>> a
+ 43
+ ```
+ It is used as an alternative incrementation operator, together with another one
+ ```py
+ >>> a +=+ 1
+ >>> a
+ >>> 44
+ ```
+ **💡 Объяснение:** This prank comes from [Raymond Hettinger's tweet](https://twitter.com/raymondh/status/1131103570856632321?lang=en). The space invader operator is actually just a malformatted `a -= (-1)`. Which is equivalent to `a = a - (- 1)`. Similar for the `a += (+ 1)` case.
+
+* Python has an undocumented [converse implication](https://en.wikipedia.org/wiki/Converse_implication) operator.
+
+ ```py
+ >>> False ** False == True
+ True
+ >>> False ** True == False
+ True
+ >>> True ** False == True
+ True
+ >>> True ** True == True
+ True
+ ```
+
+ **💡 Объяснение:** If you replace `False` and `True` by 0 and 1 and do the maths, the truth table is equivalent to a converse implication operator. ([Source](https://github.com/cosmologicon/pywat/blob/master/explanation.md#the-undocumented-converse-implication-operator))
+
+* Since we are talking operators, there's also `@` operator for matrix multiplication (don't worry, this time it's for real).
+
+ ```py
+ >>> import numpy as np
+ >>> np.array([2, 2, 2]) @ np.array([7, 8, 8])
+ 46
+ ```
+
+ **💡 Объяснение:** The `@` operator was added in Python 3.5 keeping the scientific community in mind. Any object can overload `__matmul__` magic method to define behavior for this operator.
+
+* From Python 3.8 onwards you can use a typical f-string syntax like `f'{some_var=}` for quick debugging. Example,
+ ```py
+ >>> some_string = "wtfpython"
+ >>> f'{some_string=}'
+ "some_string='wtfpython'"
+ ```
+
+* 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!):
+
+ ```py
+ import dis
+ exec("""
+ def f():
+ """ + """
+ """.join(["X" + str(x) + "=" + str(x) for x in range(65539)]))
+
+ f()
+
+ print(dis.dis(f))
+ ```
+
+* 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/3/library/multiprocessing.html) module.
+
+* Sometimes, the `print` method might not print values immediately. For example,
+
+ ```py
+ # File some_file.py
+ import time
+
+ print("wtfpython", end="_")
+ time.sleep(3)
+ ```
+
+ This will print the `wtfpython` after 3 seconds due to the `end` argument because the output buffer is flushed either after encountering `\n` or when the program finishes execution. We can force the buffer to flush by passing `flush=True` argument.
+
+* List slicing with out of the bounds indices throws no errors
+ ```py
+ >>> some_list = [1, 2, 3, 4, 5]
+ >>> some_list[111:]
+ []
+ ```
+
+* Slicing an iterable not always creates a new object. For example,
+ ```py
+ >>> some_str = "wtfpython"
+ >>> some_list = ['w', 't', 'f', 'p', 'y', 't', 'h', 'o', 'n']
+ >>> some_list is some_list[:] # False expected because a new object is created.
+ False
+ >>> some_str is some_str[:] # True because strings are immutable, so making a new object is of not much use.
+ True
+ ```
+
+* `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](https://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/) related to this behavior of Python.
+
+* You can separate numeric literals with underscores (for better readability) from Python 3 onwards.
+
+ ```py
+ >>> six_million = 6_000_000
+ >>> six_million
+ 6000000
+ >>> hex_address = 0xF00D_CAFE
+ >>> hex_address
+ 4027435774
+ ```
+
+* `'abc'.count('') == 4`. Here's an approximate implementation of `count` method, which would make the things more clear
+ ```py
+ def count(s, sub):
+ result = 0
+ for i in range(len(s) + 1 - len(sub)):
+ result += (s[i:i + len(sub)] == sub)
+ return result
+ ```
+ The behavior is due to the matching of empty substring(`''`) with slices of length 0 in the original string.
+
+---
+---
+
+# Contributing
+
+A few ways in which you can contribute to wtfpython,
+
+- Suggesting new examples
+- Helping with translation (See [issues labeled translation](https://github.com/satwikkansal/wtfpython/issues?q=is%3Aissue+is%3Aopen+label%3Atranslation))
+- Minor corrections like pointing out outdated snippets, typos, formatting errors, etc.
+- Identifying gaps (things like inadequate explanation, redundant examples, etc.)
+- Any creative suggestions to make this project more fun and useful
+
+Please see [CONTRIBUTING.md](/CONTRIBUTING.md) for more details. Feel free to create a new [issue](https://github.com/satwikkansal/wtfpython/issues/new) to discuss things.
+
+PS: Please don't reach out with backlinking requests, no links will be added unless they're highly relevant to the project.
+
+# Acknowledgements
+
+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 Pythonistas gave it the shape it is in right now.
+
+#### Some nice Links!
+* https://www.youtube.com/watch?v=sH4XF6pKKmk
+* https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python
+* https://sopython.com/wiki/Common_Gotchas_In_Python
+* https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines
+* https://stackoverflow.com/questions/1011431/common-pitfalls-in-python
+* https://www.python.org/doc/humor/
+* https://github.com/cosmologicon/pywat#the-undocumented-converse-implication-operator
+* https://www.codementor.io/satwikkansal/python-practices-for-efficient-code-performance-memory-and-usability-aze6oiq65
+* https://github.com/wemake-services/wemake-python-styleguide/search?q=wtfpython&type=Issues
+* WFTPython discussion threads on [Hacker News](https://news.ycombinator.com/item?id=21862073) and [Reddit](https://www.reddit.com/r/programming/comments/edsh3q/what_the_fck_python_30_exploring_and/).
+
+# 🎓 License
+
+[![WTFPL 2.0][license-image]][license-url]
+
+© [Satwik Kansal](https://satwikkansal.xyz)
+
+[license-url]: http://www.wtfpl.net
+[license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square
+
+## Surprise your friends as well!
+
+If you like wtfpython, you can use these quick links to share it with your friends,
+
+[Twitter](https://twitter.com/intent/tweet?url=https://github.com/satwikkansal/wtfpython&text=If%20you%20really%20think%20you%20know%20Python,%20think%20once%20more!%20Check%20out%20wtfpython&hashtags=python,wtfpython) | [Linkedin](https://www.linkedin.com/shareArticle?url=https://github.com/satwikkansal&title=What%20the%20f*ck%20Python!&summary=If%20you%20really%20thing%20you%20know%20Python,%20think%20once%20more!) | [Facebook](https://www.facebook.com/dialog/share?app_id=536779657179021&display=page&href=https%3A%2F%2Fgithub.com%2Fsatwikkansal%2Fwtfpython"e=If%20you%20really%20think%20you%20know%20Python%2C%20think%20once%20more!)
+
+## Need a pdf version?
+
+I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details [here](https://satwikkansal.xyz/wtfpython-pdf/) to get them as soon as they are finished.
+
+
+**That's all folks!** For upcoming content like this, you can add your email [here](https://www.satwikkansal.xyz/content-like-wtfpython/).
diff --git a/docs/USA.md b/docs/USA.md
new file mode 100644
index 0000000..581eb9e
--- /dev/null
+++ b/docs/USA.md
@@ -0,0 +1,3855 @@
+---
+hide:
+ - navigation
+ - toc
+---
+
+
+What the f*ck Python! 😱
+Exploring and understanding Python through surprising snippets.
+
+
+Translations: [Chinese 中文](https://github.com/leisurelicht/wtfpython-cn) | [Vietnamese Tiếng Việt](https://github.com/vuduclyunitn/wtfptyhon-vi) | [Spanish Español](https://web.archive.org/web/20220511161045/https://github.com/JoseDeFreitas/wtfpython-es) | [Korean 한국어](https://github.com/buttercrab/wtfpython-ko) | [Russian Русский](https://github.com/frontdevops/wtfpython) | [German Deutsch](https://github.com/BenSt099/wtfpython) | [Add translation](https://github.com/satwikkansal/wtfpython/issues/new?title=Add%20translation%20for%20[LANGUAGE]&body=Expected%20time%20to%20finish:%20[X]%20weeks.%20I%27ll%20start%20working%20on%20it%20from%20[Y].)
+
+Other modes: [Interactive Website](https://wtfpython-interactive.vercel.app) | [Interactive Notebook](https://colab.research.google.com/github/satwikkansal/wtfpython/blob/master/irrelevant/wtf.ipynb) | [CLI](https://pypi.python.org/pypi/wtfpython)
+
+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 at first sight.
+
+Here's a fun project attempting to explain what exactly is happening under the hood for some counter-intuitive snippets and lesser-known features in Python.
+
+While some of the examples you see below may not be WTFs in the truest sense, but they'll reveal some of the interesting parts of Python that you might be unaware of. I find it a nice way to learn the internals of a programming language, and I believe that you'll find it interesting too!
+
+If you're an experienced Python programmer, you can take it as a challenge to get most of them right in the first attempt. You may have already experienced some of them before, and I might be able to revive sweet old memories of yours! :sweat_smile:
+
+PS: If you're a returning reader, you can learn about the new modifications [here](https://github.com/satwikkansal/wtfpython/releases/) (the examples marked with asterisk are the ones added in the latest major revision).
+
+So, here we go...
+
+# Table of Contents
+
+
+
+
+
+- [Structure of the Examples](#structure-of-the-examples)
+ + [▶ Some fancy Title](#-some-fancy-title)
+- [Usage](#usage)
+- [👀 Examples](#-examples)
+ * [Section: Strain your brain!](#section-strain-your-brain)
+ + [▶ First things first! *](#-first-things-first-)
+ + [▶ Strings can be tricky sometimes](#-strings-can-be-tricky-sometimes)
+ + [▶ Be careful with chained operations](#-be-careful-with-chained-operations)
+ + [▶ How not to use `is` operator](#-how-not-to-use-is-operator)
+ + [▶ Hash brownies](#-hash-brownies)
+ + [▶ Deep down, we're all the same.](#-deep-down-were-all-the-same)
+ + [▶ Disorder within order *](#-disorder-within-order-)
+ + [▶ Keep trying... *](#-keep-trying-)
+ + [▶ For what?](#-for-what)
+ + [▶ Evaluation time discrepancy](#-evaluation-time-discrepancy)
+ + [▶ `is not ...` is not `is (not ...)`](#-is-not--is-not-is-not-)
+ + [▶ A tic-tac-toe where X wins in the first attempt!](#-a-tic-tac-toe-where-x-wins-in-the-first-attempt)
+ + [▶ Schrödinger's variable](#-schrödingers-variable-)
+ + [▶ The chicken-egg problem *](#-the-chicken-egg-problem-)
+ + [▶ Subclass relationships](#-subclass-relationships)
+ + [▶ Methods equality and identity](#-methods-equality-and-identity)
+ + [▶ All-true-ation *](#-all-true-ation-)
+ + [▶ The surprising comma](#-the-surprising-comma)
+ + [▶ Strings and the backslashes](#-strings-and-the-backslashes)
+ + [▶ not knot!](#-not-knot)
+ + [▶ Half triple-quoted strings](#-half-triple-quoted-strings)
+ + [▶ What's wrong with booleans?](#-whats-wrong-with-booleans)
+ + [▶ Class attributes and instance attributes](#-class-attributes-and-instance-attributes)
+ + [▶ yielding None](#-yielding-none)
+ + [▶ Yielding from... return! *](#-yielding-from-return-)
+ + [▶ Nan-reflexivity *](#-nan-reflexivity-)
+ + [▶ Mutating the immutable!](#-mutating-the-immutable)
+ + [▶ The disappearing variable from outer scope](#-the-disappearing-variable-from-outer-scope)
+ + [▶ The mysterious key type conversion](#-the-mysterious-key-type-conversion)
+ + [▶ Let's see if you can guess this?](#-lets-see-if-you-can-guess-this)
+ + [▶ Exceeds the limit for integer string conversion](#-exceeds-the-limit-for-integer-string-conversion)
+ * [Section: Slippery Slopes](#section-slippery-slopes)
+ + [▶ Modifying a dictionary while iterating over it](#-modifying-a-dictionary-while-iterating-over-it)
+ + [▶ Stubborn `del` operation](#-stubborn-del-operation)
+ + [▶ The out of scope variable](#-the-out-of-scope-variable)
+ + [▶ Deleting a list item while iterating](#-deleting-a-list-item-while-iterating)
+ + [▶ Lossy zip of iterators *](#-lossy-zip-of-iterators-)
+ + [▶ Loop variables leaking out!](#-loop-variables-leaking-out)
+ + [▶ Beware of default mutable arguments!](#-beware-of-default-mutable-arguments)
+ + [▶ Catching the Exceptions](#-catching-the-exceptions)
+ + [▶ Same operands, different story!](#-same-operands-different-story)
+ + [▶ Name resolution ignoring class scope](#-name-resolution-ignoring-class-scope)
+ + [▶ Rounding like a banker *](#-rounding-like-a-banker-)
+ + [▶ Needles in a Haystack *](#-needles-in-a-haystack-)
+ + [▶ Splitsies *](#-splitsies-)
+ + [▶ Wild imports *](#-wild-imports-)
+ + [▶ All sorted? *](#-all-sorted-)
+ + [▶ Midnight time doesn't exist?](#-midnight-time-doesnt-exist)
+ * [Section: The Hidden treasures!](#section-the-hidden-treasures)
+ + [▶ Okay Python, Can you make me fly?](#-okay-python-can-you-make-me-fly)
+ + [▶ `goto`, but why?](#-goto-but-why)
+ + [▶ Brace yourself!](#-brace-yourself)
+ + [▶ Let's meet Friendly Language Uncle For Life](#-lets-meet-friendly-language-uncle-for-life)
+ + [▶ Even Python understands that love is complicated](#-even-python-understands-that-love-is-complicated)
+ + [▶ Yes, it exists!](#-yes-it-exists)
+ + [▶ Ellipsis *](#-ellipsis-)
+ + [▶ Inpinity](#-inpinity)
+ + [▶ Let's mangle](#-lets-mangle)
+ * [Section: Appearances are deceptive!](#section-appearances-are-deceptive)
+ + [▶ Skipping lines?](#-skipping-lines)
+ + [▶ Teleportation](#-teleportation)
+ + [▶ Well, something is fishy...](#-well-something-is-fishy)
+ * [Section: Miscellaneous](#section-miscellaneous)
+ + [▶ `+=` is faster](#--is-faster)
+ + [▶ Let's make a giant string!](#-lets-make-a-giant-string)
+ + [▶ Slowing down `dict` lookups *](#-slowing-down-dict-lookups-)
+ + [▶ Bloating instance `dict`s *](#-bloating-instance-dicts-)
+ + [▶ Minor Ones *](#-minor-ones-)
+- [Contributing](#contributing)
+- [Acknowledgements](#acknowledgements)
+- [🎓 License](#-license)
+ * [Surprise your friends as well!](#surprise-your-friends-as-well)
+ * [More content like this?](#more-content-like-this)
+
+
+
+# Structure of the Examples
+
+All the examples are structured like below:
+
+> ### ▶ Some fancy Title
+>
+> ```py
+> # Set up the code.
+> # Preparation for the magic...
+> ```
+>
+> **Output (Python version(s)):**
+>
+> ```py
+> >>> triggering_statement
+> Some unexpected output
+> ```
+> (Optional): One line describing the unexpected output.
+>
+>
+> #### 💡 Explanation:
+>
+> * Brief explanation of what's happening and why is it happening.
+> ```py
+> # Set up code
+> # More examples for further clarification (if necessary)
+> ```
+> **Output (Python version(s)):**
+>
+> ```py
+> >>> trigger # some example that makes it easy to unveil the magic
+> # some justified output
+> ```
+
+**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 before the output.
+
+# Usage
+
+A nice way to get the most out of these examples, in my opinion, is to read them in sequential order, and for every example:
+- Carefully read the initial code for setting up the example. If you're an experienced Python programmer, you'll successfully anticipate what's going to happen next most of the time.
+- Read the output snippets and,
+ + Check if the outputs are the same as you'd expect.
+ + Make sure if you know the exact reason behind the output being the way it is.
+ - If the answer is no (which is perfectly okay), 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/issues/new)).
+ - If yes, give a gentle pat on your back, and you may skip to the next example.
+
+PS: You can also read WTFPython at the command line using the [pypi package](https://pypi.python.org/pypi/wtfpython),
+```sh
+$ pip install wtfpython -U
+$ wtfpython
+```
+---
+
+# 👀 Examples
+
+## Section: Strain your brain!
+
+### ▶ First things first! *
+
+
+
+
+For some reason, the Python 3.8's "Walrus" operator (`:=`) has become quite popular. Let's check it out,
+
+1\.
+
+```py
+# Python version 3.8+
+
+>>> a = "wtf_walrus"
+>>> a
+'wtf_walrus'
+
+>>> a := "wtf_walrus"
+File "", line 1
+ a := "wtf_walrus"
+ ^
+SyntaxError: invalid syntax
+
+>>> (a := "wtf_walrus") # This works though
+'wtf_walrus'
+>>> a
+'wtf_walrus'
+```
+
+2 \.
+
+```py
+# Python version 3.8+
+
+>>> a = 6, 9
+>>> a
+(6, 9)
+
+>>> (a := 6, 9)
+(6, 9)
+>>> a
+6
+
+>>> a, b = 6, 9 # Typical unpacking
+>>> a, b
+(6, 9)
+>>> (a, b = 16, 19) # Oops
+ File "", line 1
+ (a, b = 16, 19)
+ ^
+SyntaxError: invalid syntax
+
+>>> (a, b := 16, 19) # This prints out a weird 3-tuple
+(6, 16, 19)
+
+>>> a # a is still unchanged?
+6
+
+>>> b
+16
+```
+
+
+
+#### 💡 Explanation
+
+**Quick walrus operator refresher**
+
+The Walrus operator (`:=`) was introduced in Python 3.8, it can be useful in situations where you'd want to assign values to variables within an expression.
+
+```py
+def some_func():
+ # Assume some expensive computation here
+ # time.sleep(1000)
+ return 5
+
+# So instead of,
+if some_func():
+ print(some_func()) # Which is bad practice since computation is happening twice
+
+# or
+a = some_func()
+if a:
+ print(a)
+
+# Now you can concisely write
+if a := some_func():
+ print(a)
+```
+
+**Output (> 3.8):**
+
+```py
+5
+5
+5
+```
+
+This saved one line of code, and implicitly prevented invoking `some_func` twice.
+
+- Unparenthesized "assignment expression" (use of walrus operator), is restricted at the top level, hence the `SyntaxError` in the `a := "wtf_walrus"` statement of the first snippet. Parenthesizing it worked as expected and assigned `a`.
+
+- As usual, parenthesizing of an expression containing `=` operator is not allowed. Hence the syntax error in `(a, b = 6, 9)`.
+
+- The syntax of the Walrus operator is of the form `NAME:= expr`, where `NAME` is a valid identifier, and `expr` is a valid expression. Hence, iterable packing and unpacking are not supported which means,
+
+ - `(a := 6, 9)` is equivalent to `((a := 6), 9)` and ultimately `(a, 9) ` (where `a`'s value is 6')
+
+ ```py
+ >>> (a := 6, 9) == ((a := 6), 9)
+ True
+ >>> x = (a := 696, 9)
+ >>> x
+ (696, 9)
+ >>> x[0] is a # Both reference same memory location
+ True
+ ```
+
+ - Similarly, `(a, b := 16, 19)` is equivalent to `(a, (b := 16), 19)` which is nothing but a 3-tuple.
+
+---
+
+### ▶ Strings can be tricky sometimes
+
+
+1\.
+
+```py
+>>> a = "some_string"
+>>> id(a)
+140420665652016
+>>> id("some" + "_" + "string") # Notice that both the ids are same.
+140420665652016
+```
+
+2\.
+```py
+>>> a = "wtf"
+>>> b = "wtf"
+>>> a is b
+True
+
+>>> a = "wtf!"
+>>> b = "wtf!"
+>>> a is b
+False
+
+```
+
+3\.
+
+```py
+>>> a, b = "wtf!", "wtf!"
+>>> a is b # All versions except 3.7.x
+True
+
+>>> a = "wtf!"; b = "wtf!"
+>>> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
+False
+```
+
+```py
+# This time in file some_file.py
+a = "wtf!"
+b = "wtf!"
+print(a is b)
+
+# prints True when the module is invoked!
+```
+
+4\.
+
+**Output (< Python3.7 )**
+
+```py
+>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
+True
+>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
+False
+```
+
+Makes sense, right?
+
+#### 💡 Explanation:
++ The behavior in first and second snippets is due to a CPython optimization (called string interning) that tries to use existing immutable objects in some cases rather than creating a new object every time.
++ After being "interned," many variables may reference the same string object in memory (saving memory thereby).
++ In the snippets above, strings are implicitly interned. The decision of when to implicitly intern a string is implementation-dependent. There are some rules that can be used to guess if a string will be interned or not:
+ * All length 0 and length 1 strings are interned.
+ * Strings are interned at compile time (`'wtf'` will be interned but `''.join(['w', 't', 'f'])` will not be interned)
+ * 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)
+ ![image](../images/string-intern/string_intern.png)
++ 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 compile-time optimization. This optimization doesn't apply to 3.7.x versions of CPython (check this [issue](https://github.com/satwikkansal/wtfpython/issues/100) for more discussion).
++ A compile unit in an interactive environment like IPython consists of a single statement, whereas it consists of the entire module in case of modules. `a, b = "wtf!", "wtf!"` is single statement, whereas `a = "wtf!"; b = "wtf!"` are two statements in a single line. This explains why the identities are different in `a = "wtf!"; b = "wtf!"`, and also explain why they are same when invoked in `some_file.py`
++ The abrupt change in the output of the fourth snippet is due to a [peephole optimization](https://en.wikipedia.org/wiki/Peephole_optimization) technique known as Constant folding. This means the expression `'a'*20` is replaced by `'aaaaaaaaaaaaaaaaaaaa'` during compilation to save a few clock cycles during runtime. Constant folding only occurs for strings having a length of less than 21. (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.
++ Note: In Python 3.7, Constant folding was moved out from peephole optimizer to the new AST optimizer with some change in logic as well, so the fourth snippet doesn't work for Python 3.7. You can read more about the change [here](https://bugs.python.org/issue11549).
+
+---
+
+
+### ▶ Be careful with chained operations
+
+```py
+>>> (False == False) in [False] # makes sense
+False
+>>> False == (False in [False]) # makes sense
+False
+>>> False == False in [False] # now what?
+True
+
+>>> True is False == False
+False
+>>> False is False is False
+True
+
+>>> 1 > 0 < 1
+True
+>>> (1 > 0) < 1
+False
+>>> 1 > (0 < 1)
+False
+```
+
+#### 💡 Explanation:
+
+As per https://docs.python.org/3/reference/expressions.html#comparisons
+
+> 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.
+
+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`.
+
+* `False is False is False` is equivalent to `(False is False) and (False is False)`
+* `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`.
+* `1 > 0 < 1` is equivalent to `(1 > 0) and (0 < 1)` which evaluates to `True`.
+* The expression `(1 > 0) < 1` is equivalent to `True < 1` and
+ ```py
+ >>> int(True)
+ 1
+ >>> True + 1 #not relevant for this example, but just for fun
+ 2
+ ```
+ So, `1 < 1` evaluates to `False`
+
+---
+
+### ▶ How not to use `is` operator
+
+The following is a very famous example present all over the internet.
+
+1\.
+
+```py
+>>> a = 256
+>>> b = 256
+>>> a is b
+True
+
+>>> a = 257
+>>> b = 257
+>>> a is b
+False
+```
+
+2\.
+
+```py
+>>> a = []
+>>> b = []
+>>> a is b
+False
+
+>>> a = tuple()
+>>> b = tuple()
+>>> a is b
+True
+```
+
+3\.
+**Output**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+True
+```
+
+**Output (Python 3.7.x specifically)**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+False
+```
+
+#### 💡 Explanation:
+
+**The difference between `is` and `==`**
+
+* `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).
+* `==` operator compares the values of both the operands and checks if they are the same.
+* So `is` is for reference equality and `==` is for value equality. An example to clear things up,
+ ```py
+ >>> class A: pass
+ >>> A() is A() # These are two empty objects at two different memory locations.
+ False
+ ```
+
+**`256` is an existing object but `257` isn't**
+
+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.
+
+Quoting from https://docs.python.org/3/c-api/long.html
+> 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. :-)
+
+```py
+>>> id(256)
+10922528
+>>> a = 256
+>>> b = 256
+>>> id(a)
+10922528
+>>> id(b)
+10922528
+>>> id(257)
+140084850247312
+>>> x = 257
+>>> y = 257
+>>> id(x)
+140084850247440
+>>> id(y)
+140084850247344
+```
+
+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.
+
+Similar optimization applies to other **immutable** objects like empty tuples as well. Since lists are mutable, that's why `[] is []` will return `False` and `() is ()` will return `True`. This explains our second snippet. Let's move on to the third one,
+
+**Both `a` and `b` refer to the same object when initialized with same value in the same line.**
+
+**Output**
+
+```py
+>>> a, b = 257, 257
+>>> id(a)
+140640774013296
+>>> id(b)
+140640774013296
+>>> a = 257
+>>> b = 257
+>>> id(a)
+140640774013392
+>>> id(b)
+140640774013488
+```
+
+* 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.
+
+* 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. This optimization is not limited to integers, it works for other immutable data types like strings (check the "Strings are tricky example") and floats as well,
+
+ ```py
+ >>> a, b = 257.0, 257.0
+ >>> a is b
+ True
+ ```
+
+* Why didn't this work for Python 3.7? The abstract reason is because such compiler optimizations are implementation specific (i.e. may change with version, OS, etc). I'm still figuring out what exact implementation change cause the issue, you can check out this [issue](https://github.com/satwikkansal/wtfpython/issues/100) for updates.
+
+---
+
+
+### ▶ Hash brownies
+
+1\.
+```py
+some_dict = {}
+some_dict[5.5] = "JavaScript"
+some_dict[5.0] = "Ruby"
+some_dict[5] = "Python"
+```
+
+**Output:**
+
+```py
+>>> some_dict[5.5]
+"JavaScript"
+>>> some_dict[5.0] # "Python" destroyed the existence of "Ruby"?
+"Python"
+>>> some_dict[5]
+"Python"
+
+>>> complex_five = 5 + 0j
+>>> type(complex_five)
+complex
+>>> some_dict[complex_five]
+"Python"
+```
+
+So, why is Python all over the place?
+
+
+#### 💡 Explanation
+
+* Uniqueness of keys in a Python dictionary is by *equivalence*, not identity. So even though `5`, `5.0`, and `5 + 0j` are distinct objects of different types, since they're equal, they can't both be in the same `dict` (or `set`). As soon as you insert any one of them, attempting to look up any distinct but equivalent key will succeed with the original mapped value (rather than failing with a `KeyError`):
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> 5 is not 5.0 is not 5 + 0j
+ True
+ >>> some_dict = {}
+ >>> some_dict[5.0] = "Ruby"
+ >>> 5.0 in some_dict
+ True
+ >>> (5 in some_dict) and (5 + 0j in some_dict)
+ True
+ ```
+* This applies when setting an item as well. So when you do `some_dict[5] = "Python"`, Python finds the existing item with equivalent key `5.0 -> "Ruby"`, overwrites its value in place, and leaves the original key alone.
+ ```py
+ >>> some_dict
+ {5.0: 'Ruby'}
+ >>> some_dict[5] = "Python"
+ >>> some_dict
+ {5.0: 'Python'}
+ ```
+* So how can we update the key to `5` (instead of `5.0`)? We can't actually do this update in place, but what we can do is first delete the key (`del some_dict[5.0]`), and then set it (`some_dict[5]`) to get the integer `5` as the key instead of floating `5.0`, though this should be needed in rare cases.
+
+* How did Python find `5` in a dictionary containing `5.0`? Python does this in constant time without having to scan through every item by using hash functions. When Python looks up a key `foo` in a dict, it first computes `hash(foo)` (which runs in constant-time). Since in Python it is required that objects that compare equal also have the same hash value ([docs](https://docs.python.org/3/reference/datamodel.html#object.__hash__) here), `5`, `5.0`, and `5 + 0j` have the same hash value.
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> hash(5) == hash(5.0) == hash(5 + 0j)
+ True
+ ```
+ **Note:** The inverse is not necessarily true: Objects with equal hash values may themselves be unequal. (This causes what's known as a [hash collision](https://en.wikipedia.org/wiki/Collision_(computer_science)), and degrades the constant-time performance that hashing usually provides.)
+
+---
+
+### ▶ Deep down, we're all the same.
+
+```py
+class WTF:
+ pass
+```
+
+**Output:**
+```py
+>>> WTF() == WTF() # two different instances can't be equal
+False
+>>> WTF() is WTF() # identities are also different
+False
+>>> hash(WTF()) == hash(WTF()) # hashes _should_ be different as well
+True
+>>> id(WTF()) == id(WTF())
+True
+```
+
+#### 💡 Explanation:
+
+* 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.
+* 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.
+* So, the 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.
+* But why did the `is` operator evaluate to `False`? Let's see with this snippet.
+ ```py
+ class WTF(object):
+ def __init__(self): print("I")
+ def __del__(self): print("D")
+ ```
+
+ **Output:**
+ ```py
+ >>> WTF() is WTF()
+ I
+ I
+ D
+ D
+ False
+ >>> id(WTF()) == id(WTF())
+ I
+ D
+ I
+ D
+ True
+ ```
+ As you may observe, the order in which the objects are destroyed is what made all the difference here.
+
+---
+
+### ▶ Disorder within order *
+
+```py
+from collections import OrderedDict
+
+dictionary = dict()
+dictionary[1] = 'a'; dictionary[2] = 'b';
+
+ordered_dict = OrderedDict()
+ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+
+another_ordered_dict = OrderedDict()
+another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+
+class DictWithHash(dict):
+ """
+ A dict that also implements __hash__ magic.
+ """
+ __hash__ = lambda self: 0
+
+class OrderedDictWithHash(OrderedDict):
+ """
+ An OrderedDict that also implements __hash__ magic.
+ """
+ __hash__ = lambda self: 0
+```
+
+**Output**
+```py
+>>> dictionary == ordered_dict # If a == b
+True
+>>> dictionary == another_ordered_dict # and b == c
+True
+>>> ordered_dict == another_ordered_dict # then why isn't c == a ??
+False
+
+# We all know that a set consists of only unique elements,
+# let's try making a set of these dictionaries and see what happens...
+
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unhashable type: 'dict'
+
+# Makes sense since dict don't have __hash__ implemented, let's use
+# our wrapper classes.
+>>> dictionary = DictWithHash()
+>>> dictionary[1] = 'a'; dictionary[2] = 'b';
+>>> ordered_dict = OrderedDictWithHash()
+>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+>>> another_ordered_dict = OrderedDictWithHash()
+>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+1
+>>> len({ordered_dict, another_ordered_dict, dictionary}) # changing the order
+2
+```
+
+What is going on here?
+
+#### 💡 Explanation:
+
+- The reason why intransitive equality didn't hold among `dictionary`, `ordered_dict` and `another_ordered_dict` is because of the way `__eq__` method is implemented in `OrderedDict` class. From the [docs](https://docs.python.org/3/library/collections.html#ordereddict-objects)
+
+ > Equality tests between OrderedDict objects are order-sensitive and are implemented as `list(od1.items())==list(od2.items())`. Equality tests between `OrderedDict` objects and other Mapping objects are order-insensitive like regular dictionaries.
+- The reason for this equality in behavior is that it allows `OrderedDict` objects to be directly substituted anywhere a regular dictionary is used.
+- Okay, so why did changing the order affect the length of the generated `set` object? The answer is the lack of intransitive equality only. Since sets are "unordered" collections of unique elements, the order in which elements are inserted shouldn't matter. But in this case, it does matter. Let's break it down a bit,
+ ```py
+ >>> some_set = set()
+ >>> some_set.add(dictionary) # these are the mapping objects from the snippets above
+ >>> ordered_dict in some_set
+ True
+ >>> some_set.add(ordered_dict)
+ >>> len(some_set)
+ 1
+ >>> another_ordered_dict in some_set
+ True
+ >>> some_set.add(another_ordered_dict)
+ >>> len(some_set)
+ 1
+
+ >>> another_set = set()
+ >>> another_set.add(ordered_dict)
+ >>> another_ordered_dict in another_set
+ False
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ >>> dictionary in another_set
+ True
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ ```
+ So the inconsistency is due to `another_ordered_dict in another_set` being `False` because `ordered_dict` was already present in `another_set` and as observed before, `ordered_dict == another_ordered_dict` is `False`.
+
+---
+
+### ▶ Keep trying... *
+
+```py
+def some_func():
+ try:
+ return 'from_try'
+ finally:
+ return 'from_finally'
+
+def another_func():
+ for _ in range(3):
+ try:
+ continue
+ finally:
+ print("Finally!")
+
+def one_more_func(): # A gotcha!
+ try:
+ for i in range(3):
+ try:
+ 1 / i
+ except ZeroDivisionError:
+ # Let's throw it here and handle it outside for loop
+ raise ZeroDivisionError("A trivial divide by zero error")
+ finally:
+ print("Iteration", i)
+ break
+ except ZeroDivisionError as e:
+ print("Zero division error occurred", e)
+```
+
+**Output:**
+
+```py
+>>> some_func()
+'from_finally'
+
+>>> another_func()
+Finally!
+Finally!
+Finally!
+
+>>> 1 / 0
+Traceback (most recent call last):
+ File "", line 1, in
+ZeroDivisionError: division by zero
+
+>>> one_more_func()
+Iteration 0
+
+```
+
+#### 💡 Explanation:
+
+- 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.
+- 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.
+- The caveat here is, if the finally clause executes a `return` or `break` statement, the temporarily saved exception is discarded.
+
+---
+
+
+### ▶ For what?
+
+```py
+some_string = "wtf"
+some_dict = {}
+for i, some_dict[i] in enumerate(some_string):
+ i = 10
+```
+
+**Output:**
+```py
+>>> some_dict # An indexed dict appears.
+{0: 'w', 1: 't', 2: 'f'}
+```
+
+#### 💡 Explanation:
+
+* A `for` statement is defined in the [Python grammar](https://docs.python.org/3/reference/grammar.html) as:
+ ```
+ for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
+ ```
+ Where `exprlist` is the assignment target. This means that the equivalent of `{exprlist} = {next_value}` is **executed for each item** in the iterable.
+ An interesting example that illustrates this:
+ ```py
+ for i in range(4):
+ print(i)
+ i = 10
+ ```
+
+ **Output:**
+ ```
+ 0
+ 1
+ 2
+ 3
+ ```
+
+ Did you expect the loop to run just once?
+
+ **💡 Explanation:**
+
+ - 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)` in this case) is unpacked and assigned the target list variables (`i` in this case).
+
+* 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:
+ ```py
+ >>> i, some_dict[i] = (0, 'w')
+ >>> i, some_dict[i] = (1, 't')
+ >>> i, some_dict[i] = (2, 'f')
+ >>> some_dict
+ ```
+
+---
+
+### ▶ Evaluation time discrepancy
+
+1\.
+```py
+array = [1, 8, 15]
+# A typical generator expression
+gen = (x for x in array if array.count(x) > 0)
+array = [2, 8, 22]
+```
+
+**Output:**
+
+```py
+>>> print(list(gen)) # Where did the other values go?
+[8]
+```
+
+2\.
+
+```py
+array_1 = [1,2,3,4]
+gen_1 = (x for x in array_1)
+array_1 = [1,2,3,4,5]
+
+array_2 = [1,2,3,4]
+gen_2 = (x for x in array_2)
+array_2[:] = [1,2,3,4,5]
+```
+
+**Output:**
+```py
+>>> print(list(gen_1))
+[1, 2, 3, 4]
+
+>>> print(list(gen_2))
+[1, 2, 3, 4, 5]
+```
+
+3\.
+
+```py
+array_3 = [1, 2, 3]
+array_4 = [10, 20, 30]
+gen = (i + j for i in array_3 for j in array_4)
+
+array_3 = [4, 5, 6]
+array_4 = [400, 500, 600]
+```
+
+**Output:**
+```py
+>>> print(list(gen))
+[401, 501, 601, 402, 502, 602, 403, 503, 603]
+```
+
+#### 💡 Explanation
+
+- 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.
+- 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`.
+- 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.
+- In the first case, `array_1` is bound 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).
+- 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]`).
+- Okay, going by the logic discussed so far, shouldn't be the value of `list(gen)` in the third snippet be `[11, 21, 31, 12, 22, 32, 13, 23, 33]`? (because `array_3` and `array_4` are going to behave just like `array_1`). The reason why (only) `array_4` values got updated is explained in [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details)
+
+ > Only the outermost for-expression is evaluated immediately, the other expressions are deferred until the generator is run.
+
+---
+
+
+### ▶ `is not ...` is not `is (not ...)`
+
+```py
+>>> 'something' is not None
+True
+>>> 'something' is (not None)
+False
+```
+
+#### 💡 Explanation
+
+- `is not` is a single binary operator, and has behavior different than using `is` and `not` separated.
+- `is not` evaluates to `False` if the variables on either side of the operator point to the same object and `True` otherwise.
+- In the example, `(not None)` evaluates to `True` since the value `None` is `False` in a boolean context, so the expression becomes `'something' is True`.
+
+---
+
+### ▶ A tic-tac-toe where X wins in the first attempt!
+
+
+```py
+# Let's initialize a row
+row = [""] * 3 #row i['', '', '']
+# Let's make a board
+board = [row] * 3
+```
+
+**Output:**
+
+```py
+>>> board
+[['', '', ''], ['', '', ''], ['', '', '']]
+>>> board[0]
+['', '', '']
+>>> board[0][0]
+''
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['X', '', ''], ['X', '', '']]
+```
+
+We didn't assign three `"X"`s, did we?
+
+#### 💡 Explanation:
+
+When we initialize `row` variable, this visualization explains what happens in the memory
+
+![image](../images/tic-tac-toe/after_row_initialized.png)
+
+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`)
+
+![image](../images/tic-tac-toe/after_board_initialized.png)
+
+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).
+
+```py
+>>> board = [['']*3 for _ in range(3)]
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['', '', ''], ['', '', '']]
+```
+
+---
+
+### ▶ Schrödinger's variable *
+
+
+
+```py
+funcs = []
+results = []
+for x in range(7):
+ def some_func():
+ return x
+ funcs.append(some_func)
+ results.append(some_func()) # note the function call here
+
+funcs_results = [func() for func in funcs]
+```
+
+**Output (Python version):**
+```py
+>>> results
+[0, 1, 2, 3, 4, 5, 6]
+>>> funcs_results
+[6, 6, 6, 6, 6, 6, 6]
+```
+
+The values of `x` were different in every iteration prior to appending `some_func` to `funcs`, but all the functions return 6 when they're evaluated after the loop completes.
+
+2.
+
+```py
+>>> powers_of_x = [lambda x: x**i for i in range(10)]
+>>> [f(2) for f in powers_of_x]
+[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
+```
+
+#### 💡 Explanation:
+* 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*. The function looks up `x` in the surrounding context, rather than using the value of `x` at the time the function is created. So all of the functions use the latest value assigned to the variable for computation. We can see that it's using the `x` from the surrounding context (i.e. *not* a local variable) with:
+```py
+>>> import inspect
+>>> inspect.getclosurevars(funcs[0])
+ClosureVars(nonlocals={}, globals={'x': 6}, builtins={}, unbound=set())
+```
+Since `x` is a global value, we can change the value that the `funcs` will lookup and return by updating `x`:
+
+```py
+>>> x = 42
+>>> [func() for func in funcs]
+[42, 42, 42, 42, 42, 42, 42]
+```
+
+* To get the desired behavior you can pass in the loop variable as a named variable to the function. **Why does this work?** Because this will define the variable *inside* the function's scope. It will no longer go to the surrounding (global) scope to look up the variables value but will create a local variable that stores the value of `x` at that point in time.
+
+```py
+funcs = []
+for x in range(7):
+ def some_func(x=x):
+ return x
+ funcs.append(some_func)
+```
+
+**Output:**
+
+```py
+>>> funcs_results = [func() for func in funcs]
+>>> funcs_results
+[0, 1, 2, 3, 4, 5, 6]
+```
+
+It is not longer using the `x` in the global scope:
+
+```py
+>>> inspect.getclosurevars(funcs[0])
+ClosureVars(nonlocals={}, globals={}, builtins={}, unbound=set())
+```
+
+---
+
+### ▶ The chicken-egg problem *
+
+1\.
+```py
+>>> isinstance(3, int)
+True
+>>> isinstance(type, object)
+True
+>>> isinstance(object, type)
+True
+```
+
+So which is the "ultimate" base class? There's more to the confusion by the way,
+
+2\.
+
+```py
+>>> class A: pass
+>>> isinstance(A, A)
+False
+>>> isinstance(type, type)
+True
+>>> isinstance(object, object)
+True
+```
+
+3\.
+
+```py
+>>> issubclass(int, object)
+True
+>>> issubclass(type, object)
+True
+>>> issubclass(object, type)
+False
+```
+
+
+#### 💡 Explanation
+
+- `type` is a [metaclass](https://realpython.com/python-metaclasses/) in Python.
+- **Everything** is an `object` in Python, which includes classes as well as their objects (instances).
+- class `type` is the metaclass of class `object`, and every class (including `type`) has inherited directly or indirectly from `object`.
+- There is no real base class among `object` and `type`. The confusion in the above snippets is arising because we're thinking about these relationships (`issubclass` and `isinstance`) in terms of Python classes. The relationship between `object` and `type` can't be reproduced in pure python. To be more precise the following relationships can't be reproduced in pure Python,
+ + class A is an instance of class B, and class B is an instance of class A.
+ + class A is an instance of itself.
+- These relationships between `object` and `type` (both being instances of each other as well as themselves) exist in Python because of "cheating" at the implementation level.
+
+---
+
+### ▶ Subclass relationships
+
+**Output:**
+```py
+>>> from collections.abc import Hashable
+>>> issubclass(list, object)
+True
+>>> issubclass(object, Hashable)
+True
+>>> issubclass(list, Hashable)
+False
+```
+
+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`)
+
+#### 💡 Explanation:
+
+* Subclass relationships are not necessarily transitive in Python. Anyone is allowed to define their own, arbitrary `__subclasscheck__` in a metaclass.
+* When `issubclass(cls, Hashable)` is called, it simply looks for non-Falsey "`__hash__`" method in `cls` or anything it inherits from.
+* Since `object` is hashable, but `list` is non-hashable, it breaks the transitivity relation.
+* More detailed explanation can be found [here](https://www.naftaliharris.com/blog/python-subclass-intransitivity/).
+
+---
+
+### ▶ Methods equality and identity
+
+
+1.
+```py
+class SomeClass:
+ def method(self):
+ pass
+
+ @classmethod
+ def classm(cls):
+ pass
+
+ @staticmethod
+ def staticm():
+ pass
+```
+
+**Output:**
+```py
+>>> print(SomeClass.method is SomeClass.method)
+True
+>>> print(SomeClass.classm is SomeClass.classm)
+False
+>>> print(SomeClass.classm == SomeClass.classm)
+True
+>>> print(SomeClass.staticm is SomeClass.staticm)
+True
+```
+
+Accessing `classm` twice, we get an equal object, but not the *same* one? Let's see what happens
+with instances of `SomeClass`:
+
+2.
+```py
+o1 = SomeClass()
+o2 = SomeClass()
+```
+
+**Output:**
+```py
+>>> print(o1.method == o2.method)
+False
+>>> print(o1.method == o1.method)
+True
+>>> print(o1.method is o1.method)
+False
+>>> print(o1.classm is o1.classm)
+False
+>>> print(o1.classm == o1.classm == o2.classm == SomeClass.classm)
+True
+>>> print(o1.staticm is o1.staticm is o2.staticm is SomeClass.staticm)
+True
+```
+
+Accessing `classm` or `method` twice, creates equal but not *same* objects for the same instance of `SomeClass`.
+
+#### 💡 Explanation
+* Functions are [descriptors](https://docs.python.org/3/howto/descriptor.html). Whenever a function is accessed as an
+attribute, the descriptor is invoked, creating a method object which "binds" the function with the object owning the
+attribute. If called, the method calls the function, implicitly passing the bound object as the first argument
+(this is how we get `self` as the first argument, despite not passing it explicitly).
+```py
+>>> o1.method
+>
+```
+* Accessing the attribute multiple times creates a method object every time! Therefore `o1.method is o1.method` is
+never truthy. Accessing functions as class attributes (as opposed to instance) does not create methods, however; so
+`SomeClass.method is SomeClass.method` is truthy.
+```py
+>>> SomeClass.method
+
+```
+* `classmethod` transforms functions into class methods. Class methods are descriptors that, when accessed, create
+a method object which binds the *class* (type) of the object, instead of the object itself.
+```py
+>>> o1.classm
+>
+```
+* Unlike functions, `classmethod`s will create a method also when accessed as class attributes (in which case they
+bind the class, not to the type of it). So `SomeClass.classm is SomeClass.classm` is falsy.
+```py
+>>> SomeClass.classm
+>
+```
+* A method object compares equal when both the functions are equal, and the bound objects are the same. So
+`o1.method == o1.method` is truthy, although not the same object in memory.
+* `staticmethod` transforms functions into a "no-op" descriptor, which returns the function as-is. No method
+objects are ever created, so comparison with `is` is truthy.
+```py
+>>> o1.staticm
+
+>>> SomeClass.staticm
+
+```
+* Having to create new "method" objects every time Python calls instance methods and having to modify the arguments
+every time in order to insert `self` affected performance badly.
+CPython 3.7 [solved it](https://bugs.python.org/issue26110) by introducing new opcodes that deal with calling methods
+without creating the temporary method objects. This is used only when the accessed function is actually called, so the
+snippets here are not affected, and still generate methods :)
+
+### ▶ All-true-ation *
+
+
+
+```py
+>>> all([True, True, True])
+True
+>>> all([True, True, False])
+False
+
+>>> all([])
+True
+>>> all([[]])
+False
+>>> all([[[]]])
+True
+```
+
+Why's this True-False alteration?
+
+#### 💡 Explanation:
+
+- The implementation of `all` function is equivalent to
+
+- ```py
+ def all(iterable):
+ for element in iterable:
+ if not element:
+ return False
+ return True
+ ```
+
+- `all([])` returns `True` since the iterable is empty.
+- `all([[]])` returns `False` because the passed array has one element, `[]`, and in python, an empty list is falsy.
+- `all([[[]]])` and higher recursive variants are always `True`. This is because the passed array's single element (`[[...]]`) is no longer empty, and lists with values are truthy.
+
+---
+
+### ▶ The surprising comma
+
+**Output (< 3.6):**
+
+```py
+>>> def f(x, y,):
+... print(x, y)
+...
+>>> def g(x=4, y=5,):
+... print(x, y)
+...
+>>> def h(x, **kwargs,):
+ File "", line 1
+ def h(x, **kwargs,):
+ ^
+SyntaxError: invalid syntax
+
+>>> def h(*args,):
+ File "", line 1
+ def h(*args,):
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Explanation:
+
+- Trailing comma is not always legal in formal parameters list of a Python function.
+- 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.
+- **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.
+
+---
+
+### ▶ Strings and the backslashes
+
+**Output:**
+```py
+>>> print("\"")
+"
+
+>>> print(r"\"")
+\"
+
+>>> print(r"\")
+File "", line 1
+ print(r"\")
+ ^
+SyntaxError: EOL while scanning string literal
+
+>>> r'\'' == "\\'"
+True
+```
+
+#### 💡 Explanation
+
+- In a usual python string, the backslash is used to escape characters that may have a special meaning (like single-quote, double-quote, and the backslash itself).
+ ```py
+ >>> "wt\"f"
+ 'wt"f'
+ ```
+- In a raw string literal (as indicated by the prefix `r`), the backslashes pass themselves as is along with the behavior of escaping the following character.
+ ```py
+ >>> r'wt\"f' == 'wt\\"f'
+ True
+ >>> print(repr(r'wt\"f')
+ 'wt\\"f'
+
+ >>> print("\n")
+
+ >>> print(r"\\n")
+ '\\n'
+ ```
+- This means when a parser encounters a backslash in a raw string, it expects another character following it. And in our case (`print(r"\")`), the backslash escaped the trailing quote, leaving the parser without a terminating quote (hence the `SyntaxError`). That's why backslashes don't work at the end of a raw string.
+
+---
+
+### ▶ not knot!
+
+```py
+x = True
+y = False
+```
+
+**Output:**
+```py
+>>> not x == y
+True
+>>> x == not y
+ File "", line 1
+ x == not y
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Explanation:
+
+* Operator precedence affects how an expression is evaluated, and `==` operator has higher precedence than `not` operator in Python.
+* So `not x == y` is equivalent to `not (x == y)` which is equivalent to `not (True == False)` finally evaluating to `True`.
+* 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.
+* 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`.
+
+---
+
+### ▶ Half triple-quoted strings
+
+**Output:**
+```py
+>>> print('wtfpython''')
+wtfpython
+>>> print("wtfpython""")
+wtfpython
+>>> # The following statements raise `SyntaxError`
+>>> # print('''wtfpython')
+>>> # print("""wtfpython")
+ File "", line 3
+ print("""wtfpython")
+ ^
+SyntaxError: EOF while scanning triple-quoted string literal
+```
+
+#### 💡 Explanation:
++ Python supports implicit [string literal concatenation](https://docs.python.org/3/reference/lexical_analysis.html#string-literal-concatenation), Example,
+ ```
+ >>> print("wtf" "python")
+ wtfpython
+ >>> print("wtf" "") # or "wtf"""
+ wtf
+ ```
++ `'''` 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.
+
+---
+
+### ▶ What's wrong with booleans?
+
+1\.
+
+```py
+# A simple example to count the number of booleans and
+# integers in an iterable of mixed data types.
+mixed_list = [False, 1.0, "some_string", 3, True, [], False]
+integers_found_so_far = 0
+booleans_found_so_far = 0
+
+for item in mixed_list:
+ if isinstance(item, int):
+ integers_found_so_far += 1
+ elif isinstance(item, bool):
+ booleans_found_so_far += 1
+```
+
+**Output:**
+```py
+>>> integers_found_so_far
+4
+>>> booleans_found_so_far
+0
+```
+
+
+2\.
+```py
+>>> some_bool = True
+>>> "wtf" * some_bool
+'wtf'
+>>> some_bool = False
+>>> "wtf" * some_bool
+''
+```
+
+3\.
+
+```py
+def tell_truth():
+ True = False
+ if True == False:
+ print("I have lost faith in truth!")
+```
+
+**Output (< 3.x):**
+
+```py
+>>> tell_truth()
+I have lost faith in truth!
+```
+
+
+
+#### 💡 Explanation:
+
+* `bool` is a subclass of `int` in Python
+
+ ```py
+ >>> issubclass(bool, int)
+ True
+ >>> issubclass(int, bool)
+ False
+ ```
+
+* And thus, `True` and `False` are instances of `int`
+ ```py
+ >>> isinstance(True, int)
+ True
+ >>> isinstance(False, int)
+ True
+ ```
+
+* The integer value of `True` is `1` and that of `False` is `0`.
+ ```py
+ >>> int(True)
+ 1
+ >>> int(False)
+ 0
+ ```
+
+* See this StackOverflow [answer](https://stackoverflow.com/a/8169049/4354153) for the rationale behind it.
+
+* Initially, Python used to have no `bool` type (people used 0 for false and non-zero value like 1 for true). `True`, `False`, and a `bool` type was added in 2.x versions, but, for backward compatibility, `True` and `False` couldn't be made constants. They just were built-in variables, and it was possible to reassign them
+
+* Python 3 was backward-incompatible, the issue was finally fixed, and thus the last snippet won't work with Python 3.x!
+
+---
+
+### ▶ Class attributes and instance attributes
+
+1\.
+```py
+class A:
+ x = 1
+
+class B(A):
+ pass
+
+class C(A):
+ pass
+```
+
+**Output:**
+```py
+>>> A.x, B.x, C.x
+(1, 1, 1)
+>>> B.x = 2
+>>> A.x, B.x, C.x
+(1, 2, 1)
+>>> A.x = 3
+>>> A.x, B.x, C.x # C.x changed, but B.x didn't
+(3, 2, 3)
+>>> a = A()
+>>> a.x, A.x
+(3, 3)
+>>> a.x += 1
+>>> a.x, A.x
+(4, 3)
+```
+
+2\.
+```py
+class SomeClass:
+ some_var = 15
+ some_list = [5]
+ another_list = [5]
+ def __init__(self, x):
+ self.some_var = x + 1
+ self.some_list = self.some_list + [x]
+ self.another_list += [x]
+```
+
+**Output:**
+
+```py
+>>> some_obj = SomeClass(420)
+>>> some_obj.some_list
+[5, 420]
+>>> some_obj.another_list
+[5, 420]
+>>> another_obj = SomeClass(111)
+>>> another_obj.some_list
+[5, 111]
+>>> another_obj.another_list
+[5, 420, 111]
+>>> another_obj.another_list is SomeClass.another_list
+True
+>>> another_obj.another_list is some_obj.another_list
+True
+```
+
+#### 💡 Explanation:
+
+* 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.
+* 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.
+
+---
+
+### ▶ yielding None
+
+```py
+some_iterable = ('a', 'b')
+
+def some_func(val):
+ return "something"
+```
+
+**Output (<= 3.7.x):**
+
+```py
+>>> [x for x in some_iterable]
+['a', 'b']
+>>> [(yield x) for x in some_iterable]
+ at 0x7f70b0a4ad58>
+>>> list([(yield x) for x in some_iterable])
+['a', 'b']
+>>> list((yield x) for x in some_iterable)
+['a', None, 'b', None]
+>>> list(some_func((yield x)) for x in some_iterable)
+['a', 'something', 'b', 'something']
+```
+
+#### 💡 Explanation:
+- This is a bug in CPython's handling of `yield` in generators and comprehensions.
+- Source and explanation can be found here: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions
+- Related bug report: https://bugs.python.org/issue10544
+- Python 3.8+ no longer allows `yield` inside list comprehension and will throw a `SyntaxError`.
+
+---
+
+
+### ▶ Yielding from... return! *
+
+1\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ yield from range(x)
+```
+
+**Output (> 3.3):**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+Where did the `"wtf"` go? Is it due to some special effect of `yield from`? Let's validate that,
+
+2\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ for i in range(x):
+ yield i
+```
+
+**Output:**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+The same result, this didn't work either.
+
+#### 💡 Explanation:
+
++ From Python 3.3 onwards, it became possible to use `return` statement with values inside generators (See [PEP380](https://www.python.org/dev/peps/pep-0380/)). The [official docs](https://www.python.org/dev/peps/pep-0380/#enhancements-to-stopiteration) say that,
+
+> "... `return expr` in a generator causes `StopIteration(expr)` to be raised upon exit from the generator."
+
++ In the case of `some_func(3)`, `StopIteration` is raised at the beginning because of `return` statement. The `StopIteration` exception is automatically caught inside the `list(...)` wrapper and the `for` loop. Therefore, the above two snippets result in an empty list.
+
++ To get `["wtf"]` from the generator `some_func` we need to catch the `StopIteration` exception,
+
+ ```py
+ try:
+ next(some_func(3))
+ except StopIteration as e:
+ some_string = e.value
+ ```
+
+ ```py
+ >>> some_string
+ ["wtf"]
+ ```
+
+---
+
+### ▶ Nan-reflexivity *
+
+
+
+1\.
+
+```py
+a = float('inf')
+b = float('nan')
+c = float('-iNf') # These strings are case-insensitive
+d = float('nan')
+```
+
+**Output:**
+
+```py
+>>> a
+inf
+>>> b
+nan
+>>> c
+-inf
+>>> float('some_other_string')
+ValueError: could not convert string to float: some_other_string
+>>> a == -c # inf==inf
+True
+>>> None == None # None == None
+True
+>>> b == d # but nan!=nan
+False
+>>> 50 / a
+0.0
+>>> a / a
+nan
+>>> 23 + b
+nan
+```
+
+2\.
+
+```py
+>>> x = float('nan')
+>>> y = x / x
+>>> y is y # identity holds
+True
+>>> y == y # equality fails of y
+False
+>>> [y] == [y] # but the equality succeeds for the list containing y
+True
+```
+
+
+
+#### 💡 Explanation:
+
+- `'inf'` and `'nan'` are special strings (case-insensitive), which, when explicitly typecast-ed to `float` type, are used to represent mathematical "infinity" and "not a number" respectively.
+
+- Since according to IEEE standards ` NaN != NaN`, obeying this rule breaks the reflexivity assumption of a collection element in Python i.e. if `x` is a part of a collection like `list`, the implementations like comparison are based on the assumption that `x == x`. Because of this assumption, the identity is compared first (since it's faster) while comparing two elements, and the values are compared only when the identities mismatch. The following snippet will make things clearer,
+
+ ```py
+ >>> x = float('nan')
+ >>> x == x, [x] == [x]
+ (False, True)
+ >>> y = float('nan')
+ >>> y == y, [y] == [y]
+ (False, True)
+ >>> x == y, [x] == [y]
+ (False, False)
+ ```
+
+ Since the identities of `x` and `y` are different, the values are considered, which are also different; hence the comparison returns `False` this time.
+
+- Interesting read: [Reflexivity, and other pillars of civilization](https://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/)
+
+---
+
+### ▶ Mutating the immutable!
+
+
+
+This might seem trivial if you know how references work in Python.
+
+```py
+some_tuple = ("A", "tuple", "with", "values")
+another_tuple = ([1, 2], [3, 4], [5, 6])
+```
+
+**Output:**
+```py
+>>> some_tuple[2] = "change this"
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple[2].append(1000) #This throws no error
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000])
+>>> another_tuple[2] += [99, 999]
+TypeError: 'tuple' object does not support item assignment
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000, 99, 999])
+```
+
+But I thought tuples were immutable...
+
+#### 💡 Explanation:
+
+* Quoting from https://docs.python.org/3/reference/datamodel.html
+
+ > Immutable sequences
+ 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.)
+
+* `+=` 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.
+* There's also an explanation in [official Python FAQ](https://docs.python.org/3/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works).
+
+---
+
+### ▶ The disappearing variable from outer scope
+
+
+```py
+e = 7
+try:
+ raise Exception()
+except Exception as e:
+ pass
+```
+
+**Output (Python 2.x):**
+```py
+>>> print(e)
+# prints nothing
+```
+
+**Output (Python 3.x):**
+```py
+>>> print(e)
+NameError: name 'e' is not defined
+```
+
+#### 💡 Explanation:
+
+* Source: https://docs.python.org/3/reference/compound_stmts.html#except
+
+ When an exception has been assigned using `as` target, it is cleared at the end of the `except` clause. This is as if
+
+ ```py
+ except E as N:
+ foo
+ ```
+
+ was translated into
+
+ ```py
+ except E as N:
+ try:
+ foo
+ finally:
+ del N
+ ```
+
+ 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.
+
+* 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 that have their separate inner-scopes. The example below illustrates this:
+
+ ```py
+ def f(x):
+ del(x)
+ print(x)
+
+ x = 5
+ y = [5, 4, 3]
+ ```
+
+ **Output:**
+ ```py
+ >>> f(x)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> f(y)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> x
+ 5
+ >>> y
+ [5, 4, 3]
+ ```
+
+* In Python 2.x, the variable name `e` gets assigned to `Exception()` instance, so when you try to print, it prints nothing.
+
+ **Output (Python 2.x):**
+ ```py
+ >>> e
+ Exception()
+ >>> print e
+ # Nothing is printed!
+ ```
+
+---
+
+
+### ▶ The mysterious key type conversion
+
+```py
+class SomeClass(str):
+ pass
+
+some_dict = {'s': 42}
+```
+
+**Output:**
+```py
+>>> type(list(some_dict.keys())[0])
+str
+>>> s = SomeClass('s')
+>>> some_dict[s] = 40
+>>> some_dict # expected: Two different keys-value pairs
+{'s': 40}
+>>> type(list(some_dict.keys())[0])
+str
+```
+
+#### 💡 Explanation:
+
+* Both the object `s` and the string `"s"` hash to the same value because `SomeClass` inherits the `__hash__` method of `str` class.
+* `SomeClass("s") == "s"` evaluates to `True` because `SomeClass` also inherits `__eq__` method from `str` class.
+* Since both the objects hash to the same value and are equal, they are represented by the same key in the dictionary.
+* For the desired behavior, we can redefine the `__eq__` method in `SomeClass`
+ ```py
+ class SomeClass(str):
+ def __eq__(self, other):
+ return (
+ type(self) is SomeClass
+ and type(other) is SomeClass
+ and super().__eq__(other)
+ )
+
+ # When we define a custom __eq__, Python stops automatically inheriting the
+ # __hash__ method, so we need to define it as well
+ __hash__ = str.__hash__
+
+ some_dict = {'s':42}
+ ```
+
+ **Output:**
+ ```py
+ >>> s = SomeClass('s')
+ >>> some_dict[s] = 40
+ >>> some_dict
+ {'s': 40, 's': 42}
+ >>> keys = list(some_dict.keys())
+ >>> type(keys[0]), type(keys[1])
+ (__main__.SomeClass, str)
+ ```
+
+---
+
+### ▶ Let's see if you can guess this?
+
+```py
+a, b = a[b] = {}, 5
+```
+
+**Output:**
+```py
+>>> a
+{5: ({...}, 5)}
+```
+
+#### 💡 Explanation:
+
+* According to [Python language reference](https://docs.python.org/3/reference/simple_stmts.html#assignment-statements), assignment statements have the form
+ ```
+ (target_list "=")+ (expression_list | yield_expression)
+ ```
+ and
+
+> 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.
+
+* 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`).
+
+* After the expression list is evaluated, its 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`.
+
+* `a` is now assigned to `{}`, which is a mutable object.
+
+* 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`).
+
+* 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
+ ```py
+ >>> some_list = some_list[0] = [0]
+ >>> some_list
+ [[...]]
+ >>> some_list[0]
+ [[...]]
+ >>> some_list is some_list[0]
+ True
+ >>> some_list[0][0][0][0][0][0] == some_list
+ True
+ ```
+ Similar is the case in our example (`a[b][0]` is the same object as `a`)
+
+* So to sum it up, you can break the example down to
+ ```py
+ a, b = {}, 5
+ a[b] = a, b
+ ```
+ And the circular reference can be justified by the fact that `a[b][0]` is the same object as `a`
+ ```py
+ >>> a[b][0] is a
+ True
+ ```
+
+
+---
+
+### ▶ Exceeds the limit for integer string conversion
+```py
+>>> # Python 3.10.6
+>>> int("2" * 5432)
+
+>>> # Python 3.10.8
+>>> int("2" * 5432)
+```
+
+**Output:**
+```py
+>>> # Python 3.10.6
+222222222222222222222222222222222222222222222222222222222222222...
+
+>>> # Python 3.10.8
+Traceback (most recent call last):
+ ...
+ValueError: Exceeds the limit (4300) for integer string conversion:
+ value has 5432 digits; use sys.set_int_max_str_digits()
+ to increase the limit.
+```
+
+#### 💡 Explanation:
+This call to `int()` works fine in Python 3.10.6 and raises a ValueError in Python 3.10.8. Note that Python can still work with large integers. The error is only raised when converting between integers and strings.
+
+Fortunately, you can increase the limit for the allowed number of digits when you expect an operation to exceed it. To do this, you can use one of the following:
+- The -X int_max_str_digits command-line flag
+- The set_int_max_str_digits() function from the sys module
+- The PYTHONINTMAXSTRDIGITS environment variable
+
+[Check the documentation](https://docs.python.org/3/library/stdtypes.html#int-max-str-digits) for more details on changing the default limit if you expect your code to exceed this value.
+
+
+---
+
+
+## Section: Slippery Slopes
+
+### ▶ Modifying a dictionary while iterating over it
+
+```py
+x = {0: None}
+
+for i in x:
+ del x[i]
+ x[i+1] = None
+ print(i)
+```
+
+**Output (Python 2.7- Python 3.5):**
+
+```
+0
+1
+2
+3
+4
+5
+6
+7
+```
+
+Yes, it runs for exactly **eight** times and stops.
+
+#### 💡 Explanation:
+
+* Iteration over a dictionary that you edit at the same time is not supported.
+* 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.
+* How deleted keys are handled and when the resize occurs might be different for different Python implementations.
+* So for Python versions other than Python 2.7 - Python 3.5, the count might be different from 8 (but whatever the count is, it's going to be the same every time you run it). You can find some discussion around this [here](https://github.com/satwikkansal/wtfpython/issues/53) or in [this](https://stackoverflow.com/questions/44763802/bug-in-python-dict) StackOverflow thread.
+* Python 3.7.6 onwards, you'll see `RuntimeError: dictionary keys changed during iteration` exception if you try to do this.
+
+---
+
+### ▶ Stubborn `del` operation
+
+
+
+```py
+class SomeClass:
+ def __del__(self):
+ print("Deleted!")
+```
+
+**Output:**
+1\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x # this should print "Deleted!"
+>>> del y
+Deleted!
+```
+
+Phew, deleted at last. You might have guessed what saved `__del__` from being called in our first attempt to delete `x`. Let's add more twists to the example.
+
+2\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x
+>>> y # check if y exists
+<__main__.SomeClass instance at 0x7f98a1a67fc8>
+>>> del y # Like previously, this should print "Deleted!"
+>>> globals() # oh, it didn't. Let's check all our global variables and confirm
+Deleted!
+{'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
+```
+
+Okay, now it's deleted :confused:
+
+#### 💡 Explanation:
++ `del x` doesn’t directly call `x.__del__()`.
++ When `del x` is encountered, Python deletes the name `x` from current scope and decrements by 1 the reference count of the object `x` referenced. `__del__()` is called only when the object's reference count reaches zero.
++ In the second output snippet, `__del__()` was not called because the previous statement (`>>> y`) in the interactive interpreter created another reference to the same object (specifically, the `_` magic variable which references the result value of the last non `None` expression on the REPL), thus preventing the reference count from reaching zero when `del y` was encountered.
++ Calling `globals` (or really, executing anything that will have a non `None` result) caused `_` to reference the new result, dropping the existing reference. Now the reference count reached 0 and we can see "Deleted!" being printed (finally!).
+
+---
+
+### ▶ The out of scope variable
+
+
+1\.
+```py
+a = 1
+def some_func():
+ return a
+
+def another_func():
+ a += 1
+ return a
+```
+
+2\.
+```py
+def some_closure_func():
+ a = 1
+ def some_inner_func():
+ return a
+ return some_inner_func()
+
+def another_closure_func():
+ a = 1
+ def another_inner_func():
+ a += 1
+ return a
+ return another_inner_func()
+```
+
+**Output:**
+```py
+>>> some_func()
+1
+>>> another_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+
+>>> some_closure_func()
+1
+>>> another_closure_func()
+UnboundLocalError: local variable 'a' referenced before assignment
+```
+
+#### 💡 Explanation:
+* 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.
+* To modify the outer scope variable `a` in `another_func`, we have to use the `global` keyword.
+ ```py
+ def another_func()
+ global a
+ a += 1
+ return a
+ ```
+
+ **Output:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+* In `another_closure_func`, `a` becomes local to the scope of `another_inner_func`, but it has not been initialized previously in the same scope, which is why it throws an error.
+* To modify the outer scope variable `a` in `another_inner_func`, use the `nonlocal` keyword. The nonlocal statement is used to refer to variables defined in the nearest outer (excluding the global) scope.
+ ```py
+ def another_func():
+ a = 1
+ def another_inner_func():
+ nonlocal a
+ a += 1
+ return a
+ return another_inner_func()
+ ```
+
+ **Output:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+* The keywords `global` and `nonlocal` tell the python interpreter to not declare new variables and look them up in the corresponding outer scopes.
+* 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.
+
+---
+
+### ▶ Deleting a list item while iterating
+
+```py
+list_1 = [1, 2, 3, 4]
+list_2 = [1, 2, 3, 4]
+list_3 = [1, 2, 3, 4]
+list_4 = [1, 2, 3, 4]
+
+for idx, item in enumerate(list_1):
+ del item
+
+for idx, item in enumerate(list_2):
+ list_2.remove(item)
+
+for idx, item in enumerate(list_3[:]):
+ list_3.remove(item)
+
+for idx, item in enumerate(list_4):
+ list_4.pop(idx)
+```
+
+**Output:**
+```py
+>>> list_1
+[1, 2, 3, 4]
+>>> list_2
+[2, 4]
+>>> list_3
+[]
+>>> list_4
+[2, 4]
+```
+
+Can you guess why the output is `[2, 4]`?
+
+#### 💡 Explanation:
+
+* 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.
+
+ ```py
+ >>> some_list = [1, 2, 3, 4]
+ >>> id(some_list)
+ 139798789457608
+ >>> id(some_list[:]) # Notice that python creates new object for sliced list.
+ 139798779601192
+ ```
+
+**Difference between `del`, `remove`, and `pop`:**
+* `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).
+* `remove` removes the first matching value, not a specific index, raises `ValueError` if the value is not found.
+* `pop` removes the element at a specific index and returns it, raises `IndexError` if an invalid index is specified.
+
+**Why the output is `[2, 4]`?**
+- 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.
+
+* 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
+* 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.
+
+---
+
+
+### ▶ Lossy zip of iterators *
+
+
+```py
+>>> numbers = list(range(7))
+>>> numbers
+[0, 1, 2, 3, 4, 5, 6]
+>>> first_three, remaining = numbers[:3], numbers[3:]
+>>> first_three, remaining
+([0, 1, 2], [3, 4, 5, 6])
+>>> numbers_iter = iter(numbers)
+>>> list(zip(numbers_iter, first_three))
+[(0, 0), (1, 1), (2, 2)]
+# so far so good, let's zip the remaining
+>>> list(zip(numbers_iter, remaining))
+[(4, 3), (5, 4), (6, 5)]
+```
+Where did element `3` go from the `numbers` list?
+
+#### 💡 Explanation:
+
+- From Python [docs](https://docs.python.org/3.3/library/functions.html#zip), here's an approximate implementation of zip function,
+ ```py
+ def zip(*iterables):
+ sentinel = object()
+ iterators = [iter(it) for it in iterables]
+ while iterators:
+ result = []
+ for it in iterators:
+ elem = next(it, sentinel)
+ if elem is sentinel: return
+ result.append(elem)
+ yield tuple(result)
+ ```
+- So the function takes in arbitrary number of iterable objects, adds each of their items to the `result` list by calling the `next` function on them, and stops whenever any of the iterable is exhausted.
+- The caveat here is when any iterable is exhausted, the existing elements in the `result` list are discarded. That's what happened with `3` in the `numbers_iter`.
+- The correct way to do the above using `zip` would be,
+ ```py
+ >>> numbers = list(range(7))
+ >>> numbers_iter = iter(numbers)
+ >>> list(zip(first_three, numbers_iter))
+ [(0, 0), (1, 1), (2, 2)]
+ >>> list(zip(remaining, numbers_iter))
+ [(3, 3), (4, 4), (5, 5), (6, 6)]
+ ```
+ The first argument of zip should be the one with fewest elements.
+
+---
+
+### ▶ Loop variables leaking out!
+
+1\.
+```py
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Output:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+But `x` was never defined outside the scope of for loop...
+
+2\.
+```py
+# This time let's initialize x first
+x = -1
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Output:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+3\.
+
+**Output (Python 2.x):**
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+4
+```
+
+**Output (Python 3.x):**
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+1
+```
+
+#### 💡 Explanation:
+
+- 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.
+
+- 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) changelog:
+
+ > "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."
+
+---
+
+### ▶ Beware of default mutable arguments!
+
+
+```py
+def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+```
+
+**Output:**
+```py
+>>> some_func()
+['some_string']
+>>> some_func()
+['some_string', 'some_string']
+>>> some_func([])
+['some_string']
+>>> some_func()
+['some_string', 'some_string', 'some_string']
+```
+
+#### 💡 Explanation:
+
+- 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.
+
+ ```py
+ def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+ **Output:**
+ ```py
+ >>> some_func.__defaults__ #This will show the default argument values for the function
+ ([],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string'],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ >>> some_func([])
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ ```
+
+- 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:
+
+ ```py
+ def some_func(default_arg=None):
+ if default_arg is None:
+ default_arg = []
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+---
+
+### ▶ Catching the Exceptions
+
+```py
+some_list = [1, 2, 3]
+try:
+ # This should raise an ``IndexError``
+ print(some_list[4])
+except IndexError, ValueError:
+ print("Caught!")
+
+try:
+ # This should raise a ``ValueError``
+ some_list.remove(4)
+except IndexError, ValueError:
+ print("Caught again!")
+```
+
+**Output (Python 2.x):**
+```py
+Caught!
+
+ValueError: list.remove(x): x not in list
+```
+
+**Output (Python 3.x):**
+```py
+ File "", line 3
+ except IndexError, ValueError:
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Explanation
+
+* 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,
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ # This should raise a ``ValueError``
+ some_list.remove(4)
+ except (IndexError, ValueError), e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Output (Python 2.x):**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+ **Output (Python 3.x):**
+ ```py
+ File "", line 4
+ except (IndexError, ValueError), e:
+ ^
+ IndentationError: unindent does not match any outer indentation level
+ ```
+
+* 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,
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ some_list.remove(4)
+
+ except (IndexError, ValueError) as e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Output:**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+
+---
+
+### ▶ Same operands, different story!
+
+1\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a = a + [5, 6, 7, 8]
+```
+
+**Output:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4]
+```
+
+2\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a += [5, 6, 7, 8]
+```
+
+**Output:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4, 5, 6, 7, 8]
+```
+
+#### 💡 Explanation:
+
+* `a += b` doesn't always behave the same way as `a = a + b`. Classes *may* implement the *`op=`* operators differently, and lists do this.
+
+* The expression `a = a + [5,6,7,8]` generates a new list and sets `a`'s reference to that new list, leaving `b` unchanged.
+
+* 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.
+
+---
+
+### ▶ Name resolution ignoring class scope
+
+1\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = (x for i in range(10))
+```
+
+**Output:**
+```py
+>>> list(SomeClass.y)[0]
+5
+```
+
+2\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = [x for i in range(10)]
+```
+
+**Output (Python 2.x):**
+```py
+>>> SomeClass.y[0]
+17
+```
+
+**Output (Python 3.x):**
+```py
+>>> SomeClass.y[0]
+5
+```
+
+#### 💡 Explanation
+- Scopes nested inside class definition ignore names bound at the class level.
+- A generator expression has its own scope.
+- Starting from Python 3.X, list comprehensions also have their own scope.
+
+---
+
+### ▶ Rounding like a banker *
+
+Let's implement a naive function to get the middle element of a list:
+```py
+def get_middle(some_list):
+ mid_index = round(len(some_list) / 2)
+ return some_list[mid_index - 1]
+```
+
+**Python 3.x:**
+```py
+>>> get_middle([1]) # looks good
+1
+>>> get_middle([1,2,3]) # looks good
+2
+>>> get_middle([1,2,3,4,5]) # huh?
+2
+>>> len([1,2,3,4,5]) / 2 # good
+2.5
+>>> round(len([1,2,3,4,5]) / 2) # why?
+2
+```
+It seems as though Python rounded 2.5 to 2.
+
+#### 💡 Explanation:
+
+- This is not a float precision error, in fact, this behavior is intentional. Since Python 3.0, `round()` uses [banker's rounding](https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even) where .5 fractions are rounded to the nearest **even** number:
+
+```py
+>>> round(0.5)
+0
+>>> round(1.5)
+2
+>>> round(2.5)
+2
+>>> import numpy # numpy does the same
+>>> numpy.round(0.5)
+0.0
+>>> numpy.round(1.5)
+2.0
+>>> numpy.round(2.5)
+2.0
+```
+
+- This is the recommended way to round .5 fractions as described in [IEEE 754](https://en.wikipedia.org/wiki/IEEE_754#Rounding_rules). However, the other way (round away from zero) is taught in school most of the time, so banker's rounding is likely not that well known. Furthermore, some of the most popular programming languages (for example: JavaScript, Java, C/C++, Ruby, Rust) do not use banker's rounding either. Therefore, this is still quite special to Python and may result in confusion when rounding fractions.
+- See the [round() docs](https://docs.python.org/3/library/functions.html#round) or [this stackoverflow thread](https://stackoverflow.com/questions/10825926/python-3-x-rounding-behavior) for more information.
+- Note that `get_middle([1])` only returned 1 because the index was `round(0.5) - 1 = 0 - 1 = -1`, returning the last element in the list.
+
+---
+
+### ▶ Needles in a Haystack *
+
+
+
+I haven't met even a single experience Pythonist till date who has not come across one or more of the following scenarios,
+
+1\.
+
+```py
+x, y = (0, 1) if True else None, None
+```
+
+**Output:**
+
+```py
+>>> x, y # expected (0, 1)
+((0, 1), None)
+```
+
+2\.
+
+```py
+t = ('one', 'two')
+for i in t:
+ print(i)
+
+t = ('one')
+for i in t:
+ print(i)
+
+t = ()
+print(t)
+```
+
+**Output:**
+
+```py
+one
+two
+o
+n
+e
+tuple()
+```
+
+3\.
+
+```
+ten_words_list = [
+ "some",
+ "very",
+ "big",
+ "list",
+ "that"
+ "consists",
+ "of",
+ "exactly",
+ "ten",
+ "words"
+]
+```
+
+**Output**
+
+```py
+>>> len(ten_words_list)
+9
+```
+
+4\. Not asserting strongly enough
+
+```py
+a = "python"
+b = "javascript"
+```
+
+**Output:**
+
+```py
+# An assert statement with an assertion failure message.
+>>> assert(a == b, "Both languages are different")
+# No AssertionError is raised
+```
+
+5\.
+
+```py
+some_list = [1, 2, 3]
+some_dict = {
+ "key_1": 1,
+ "key_2": 2,
+ "key_3": 3
+}
+
+some_list = some_list.append(4)
+some_dict = some_dict.update({"key_4": 4})
+```
+
+**Output:**
+
+```py
+>>> print(some_list)
+None
+>>> print(some_dict)
+None
+```
+
+6\.
+
+```py
+def some_recursive_func(a):
+ if a[0] == 0:
+ return
+ a[0] -= 1
+ some_recursive_func(a)
+ return a
+
+def similar_recursive_func(a):
+ if a == 0:
+ return a
+ a -= 1
+ similar_recursive_func(a)
+ return a
+```
+
+**Output:**
+
+```py
+>>> some_recursive_func([5, 0])
+[0, 0]
+>>> similar_recursive_func(5)
+4
+```
+
+#### 💡 Explanation:
+
+* For 1, the correct statement for expected behavior is `x, y = (0, 1) if True else (None, None)`.
+
+* 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.
+
+* `()` is a special token and denotes empty `tuple`.
+
+* In 3, as you might have already figured out, there's a missing comma after 5th element (`"that"`) in the list. So by implicit string literal concatenation,
+
+ ```py
+ >>> ten_words_list
+ ['some', 'very', 'big', 'list', 'thatconsists', 'of', 'exactly', 'ten', 'words']
+ ```
+
+* No `AssertionError` was raised in 4th snippet because instead of asserting the individual expression `a == b`, we're asserting entire tuple. The following snippet will clear things up,
+
+ ```py
+ >>> a = "python"
+ >>> b = "javascript"
+ >>> assert a == b
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError
+
+ >>> assert (a == b, "Values are not equal")
+ :1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
+
+ >>> assert a == b, "Values are not equal"
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError: Values are not equal
+ ```
+
+* As for the fifth snippet, 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](https://docs.python.org/3/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list)).
+
+* Last one should be fairly obvious, mutable object (like `list`) can be altered in the function, and the reassignment of an immutable (`a -= 1`) is not an alteration of the value.
+
+* Being aware of these nitpicks can save you hours of debugging effort in the long run.
+
+---
+
+
+### ▶ Splitsies *
+
+```py
+>>> 'a'.split()
+['a']
+
+# is same as
+>>> 'a'.split(' ')
+['a']
+
+# but
+>>> len(''.split())
+0
+
+# isn't the same as
+>>> len(''.split(' '))
+1
+```
+
+#### 💡 Explanation:
+
+- It might appear at first that the default separator for split is a single space `' '`, but as per the [docs](https://docs.python.org/3/library/stdtypes.html#str.split)
+ > If sep is not specified or is `None`, a different splitting algorithm is applied: runs of consecutive whitespace are regarded as a single separator, and the result will contain no empty strings at the start or end if the string has leading or trailing whitespace. Consequently, splitting an empty string or a string consisting of just whitespace with a None separator returns `[]`.
+ > If sep is given, consecutive delimiters are not grouped together and are deemed to delimit empty strings (for example, `'1,,2'.split(',')` returns `['1', '', '2']`). Splitting an empty string with a specified separator returns `['']`.
+- Noticing how the leading and trailing whitespaces are handled in the following snippet will make things clear,
+ ```py
+ >>> ' a '.split(' ')
+ ['', 'a', '']
+ >>> ' a '.split()
+ ['a']
+ >>> ''.split(' ')
+ ['']
+ ```
+
+---
+
+### ▶ Wild imports *
+
+
+
+```py
+# File: module.py
+
+def some_weird_name_func_():
+ print("works!")
+
+def _another_weird_name_func():
+ print("works!")
+
+```
+
+**Output**
+
+```py
+>>> from module import *
+>>> some_weird_name_func_()
+"works!"
+>>> _another_weird_name_func()
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name '_another_weird_name_func' is not defined
+```
+
+#### 💡 Explanation:
+
+- It is often advisable to not use wildcard imports. The first obvious reason for this is, in wildcard imports, the names with a leading underscore don't get imported. This may lead to errors during runtime.
+- Had we used `from ... import a, b, c` syntax, the above `NameError` wouldn't have occurred.
+ ```py
+ >>> from module import some_weird_name_func_, _another_weird_name_func
+ >>> _another_weird_name_func()
+ works!
+ ```
+- If you really want to use wildcard imports, then you'd have to define the list `__all__` in your module that will contain a list of public objects that'll be available when we do wildcard imports.
+ ```py
+ __all__ = ['_another_weird_name_func']
+
+ def some_weird_name_func_():
+ print("works!")
+
+ def _another_weird_name_func():
+ print("works!")
+ ```
+ **Output**
+
+ ```py
+ >>> _another_weird_name_func()
+ "works!"
+ >>> some_weird_name_func_()
+ Traceback (most recent call last):
+ File "", line 1, in
+ NameError: name 'some_weird_name_func_' is not defined
+ ```
+
+---
+
+### ▶ All sorted? *
+
+
+
+```py
+>>> x = 7, 8, 9
+>>> sorted(x) == x
+False
+>>> sorted(x) == sorted(x)
+True
+
+>>> y = reversed(x)
+>>> sorted(y) == sorted(y)
+False
+```
+
+#### 💡 Explanation:
+
+- The `sorted` method always returns a list, and comparing lists and tuples always returns `False` in Python.
+
+- ```py
+ >>> [] == tuple()
+ False
+ >>> x = 7, 8, 9
+ >>> type(x), type(sorted(x))
+ (tuple, list)
+ ```
+
+- Unlike `sorted`, the `reversed` method returns an iterator. Why? Because sorting requires the iterator to be either modified in-place or use an extra container (a list), whereas reversing can simply work by iterating from the last index to the first.
+
+- So during comparison `sorted(y) == sorted(y)`, the first call to `sorted()` will consume the iterator `y`, and the next call will just return an empty list.
+
+ ```py
+ >>> x = 7, 8, 9
+ >>> y = reversed(x)
+ >>> sorted(y), sorted(y)
+ ([7, 8, 9], [])
+ ```
+
+---
+
+### ▶ Midnight time doesn't exist?
+
+```py
+from datetime import datetime
+
+midnight = datetime(2018, 1, 1, 0, 0)
+midnight_time = midnight.time()
+
+noon = datetime(2018, 1, 1, 12, 0)
+noon_time = noon.time()
+
+if midnight_time:
+ print("Time at midnight is", midnight_time)
+
+if noon_time:
+ print("Time at noon is", noon_time)
+```
+
+**Output (< 3.5):**
+
+```py
+('Time at noon is', datetime.time(12, 0))
+```
+The midnight time is not printed.
+
+#### 💡 Explanation:
+
+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."
+
+---
+---
+
+
+
+## Section: The Hidden treasures!
+
+This section contains a few lesser-known and interesting things about Python that most beginners like me are unaware of (well, not anymore).
+
+### ▶ Okay Python, Can you make me fly?
+
+Well, here you go
+
+```py
+import antigravity
+```
+
+**Output:**
+Sshh... It's a super-secret.
+
+#### 💡 Explanation:
++ `antigravity` module is one of the few easter eggs released by Python developers.
++ `import antigravity` opens up a web browser pointing to the [classic XKCD comic](https://xkcd.com/353/) about Python.
++ 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/).
+
+---
+
+### ▶ `goto`, but why?
+
+
+```py
+from goto import goto, label
+for i in range(9):
+ for j in range(9):
+ for k in range(9):
+ print("I am trapped, please rescue!")
+ if k == 2:
+ goto .breakout # breaking out from a deeply nested loop
+label .breakout
+print("Freedom!")
+```
+
+**Output (Python 2.3):**
+```py
+I am trapped, please rescue!
+I am trapped, please rescue!
+Freedom!
+```
+
+#### 💡 Explanation:
+- 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.
+- Current versions of Python do not have this module.
+- 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.
+
+---
+
+### ▶ Brace yourself!
+
+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,
+
+```py
+from __future__ import braces
+```
+
+**Output:**
+```py
+ File "some_file.py", line 1
+ from __future__ import braces
+SyntaxError: not a chance
+```
+
+Braces? No way! If you think that's disappointing, use Java. Okay, another surprising thing, can you find where's the `SyntaxError` raised in `__future__` module [code](https://github.com/python/cpython/blob/master/Lib/__future__.py)?
+
+#### 💡 Explanation:
++ The `__future__` module is normally used to provide features from future versions of Python. The "future" in this specific context is however, ironic.
++ This is an easter egg concerned with the community's feelings on this issue.
++ The code is actually present [here](https://github.com/python/cpython/blob/025eb98dc0c1dc27404df6c544fc2944e0fa9f3a/Python/future.c#L49) in `future.c` file.
++ When the CPython compiler encounters a [future statement](https://docs.python.org/3.3/reference/simple_stmts.html#future-statements), it first runs the appropriate code in `future.c` before treating it as a normal import statement.
+
+---
+
+### ▶ Let's meet Friendly Language Uncle For Life
+
+**Output (Python 3.x)**
+```py
+>>> from __future__ import barry_as_FLUFL
+>>> "Ruby" != "Python" # there's no doubt about it
+ File "some_file.py", line 1
+ "Ruby" != "Python"
+ ^
+SyntaxError: invalid syntax
+
+>>> "Ruby" <> "Python"
+True
+```
+
+There we go.
+
+#### 💡 Explanation:
+- 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).
+- Quoting from the PEP-401
+
+ > 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.
+- 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/).
+- It works well in an interactive environment, but it will raise a `SyntaxError` when you run via python file (see this [issue](https://github.com/satwikkansal/wtfpython/issues/94)). However, you can wrap the statement inside an `eval` or `compile` to get it working,
+ ```py
+ from __future__ import barry_as_FLUFL
+ print(eval('"Ruby" <> "Python"'))
+ ```
+
+---
+
+### ▶ Even Python understands that love is complicated
+
+```py
+import this
+```
+
+Wait, what's **this**? `this` is love :heart:
+
+**Output:**
+```
+The Zen of Python, by Tim Peters
+
+Beautiful is better than ugly.
+Explicit is better than implicit.
+Simple is better than complex.
+Complex is better than complicated.
+Flat is better than nested.
+Sparse is better than dense.
+Readability counts.
+Special cases aren't special enough to break the rules.
+Although practicality beats purity.
+Errors should never pass silently.
+Unless explicitly silenced.
+In the face of ambiguity, refuse the temptation to guess.
+There should be one-- and preferably only one --obvious way to do it.
+Although that way may not be obvious at first unless you're Dutch.
+Now is better than never.
+Although never is often better than *right* now.
+If the implementation is hard to explain, it's a bad idea.
+If the implementation is easy to explain, it may be a good idea.
+Namespaces are one honking great idea -- let's do more of those!
+```
+
+It's the Zen of Python!
+
+```py
+>>> love = this
+>>> this is love
+True
+>>> love is True
+False
+>>> love is False
+False
+>>> love is not True or False
+True
+>>> love is not True or False; love is love # Love is complicated
+True
+```
+
+#### 💡 Explanation:
+
+* `this` module in Python is an easter egg for The Zen Of Python ([PEP 20](https://www.python.org/dev/peps/pep-0020)).
+* 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).
+* Regarding the statement `love is not True or False; love is love`, ironic but it's self-explanatory (if not, please see the examples related to `is` and `is not` operators).
+
+---
+
+### ▶ Yes, it exists!
+
+**The `else` clause for loops.** One typical example might be:
+
+```py
+ def does_exists_num(l, to_find):
+ for num in l:
+ if num == to_find:
+ print("Exists!")
+ break
+ else:
+ print("Does not exist")
+```
+
+**Output:**
+```py
+>>> some_list = [1, 2, 3, 4, 5]
+>>> does_exists_num(some_list, 4)
+Exists!
+>>> does_exists_num(some_list, -1)
+Does not exist
+```
+
+**The `else` clause in exception handling.** An example,
+
+```py
+try:
+ pass
+except:
+ print("Exception occurred!!!")
+else:
+ print("Try block executed successfully...")
+```
+
+**Output:**
+```py
+Try block executed successfully...
+```
+
+#### 💡 Explanation:
+- The `else` clause after a loop is executed only when there's no explicit `break` after all the iterations. You can think of it as a "nobreak" clause.
+- `else` clause after a try block is also called "completion clause" as reaching the `else` clause in a `try` statement means that the try block actually completed successfully.
+
+---
+### ▶ Ellipsis *
+
+```py
+def some_func():
+ Ellipsis
+```
+
+**Output**
+```py
+>>> some_func()
+# No output, No Error
+
+>>> SomeRandomString
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'SomeRandomString' is not defined
+
+>>> Ellipsis
+Ellipsis
+```
+
+#### 💡 Explanation
+- In Python, `Ellipsis` is a globally available built-in object which is equivalent to `...`.
+ ```py
+ >>> ...
+ Ellipsis
+ ```
+- Ellipsis can be used for several purposes,
+ + As a placeholder for code that hasn't been written yet (just like `pass` statement)
+ + In slicing syntax to represent the full slices in remaining direction
+ ```py
+ >>> import numpy as np
+ >>> three_dimensional_array = np.arange(8).reshape(2, 2, 2)
+ array([
+ [
+ [0, 1],
+ [2, 3]
+ ],
+
+ [
+ [4, 5],
+ [6, 7]
+ ]
+ ])
+ ```
+ So our `three_dimensional_array` is an array of array of arrays. Let's say we want to print the second element (index `1`) of all the innermost arrays, we can use Ellipsis to bypass all the preceding dimensions
+ ```py
+ >>> three_dimensional_array[:,:,1]
+ array([[1, 3],
+ [5, 7]])
+ >>> three_dimensional_array[..., 1] # using Ellipsis.
+ array([[1, 3],
+ [5, 7]])
+ ```
+ Note: this will work for any number of dimensions. You can even select slice in first and last dimension and ignore the middle ones this way (`n_dimensional_array[firs_dim_slice, ..., last_dim_slice]`)
+ + In [type hinting](https://docs.python.org/3/library/typing.html) to indicate only a part of the type (like `(Callable[..., int]` or `Tuple[str, ...]`))
+ + You may also use Ellipsis as a default function argument (in the cases when you want to differentiate between the "no argument passed" and "None value passed" scenarios).
+
+---
+
+### ▶ Inpinity
+
+The spelling is intended. Please, don't submit a patch for this.
+
+**Output (Python 3.x):**
+```py
+>>> infinity = float('infinity')
+>>> hash(infinity)
+314159
+>>> hash(float('-inf'))
+-314159
+```
+
+#### 💡 Explanation:
+- Hash of infinity is 10⁵ x π.
+- Interestingly, the hash of `float('-inf')` is "-10⁵ x π" in Python 3, whereas "-10⁵ x e" in Python 2.
+
+---
+
+### ▶ Let's mangle
+
+1\.
+```py
+class Yo(object):
+ def __init__(self):
+ self.__honey = True
+ self.bro = True
+```
+
+**Output:**
+```py
+>>> Yo().bro
+True
+>>> Yo().__honey
+AttributeError: 'Yo' object has no attribute '__honey'
+>>> Yo()._Yo__honey
+True
+```
+
+2\.
+```py
+class Yo(object):
+ def __init__(self):
+ # Let's try something symmetrical this time
+ self.__honey__ = True
+ self.bro = True
+```
+
+**Output:**
+```py
+>>> Yo().bro
+True
+
+>>> Yo()._Yo__honey__
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'Yo' object has no attribute '_Yo__honey__'
+```
+
+Why did `Yo()._Yo__honey` work?
+
+3\.
+
+```py
+_A__variable = "Some value"
+
+class A(object):
+ def some_func(self):
+ return __variable # not initialized anywhere yet
+```
+
+**Output:**
+```py
+>>> A().__variable
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'A' object has no attribute '__variable'
+
+>>> A().some_func()
+'Some value'
+```
+
+
+#### 💡 Explanation:
+
+* [Name Mangling](https://en.wikipedia.org/wiki/Name_mangling) is used to avoid naming collisions between different namespaces.
+* In Python, the interpreter modifies (mangles) the class member names starting with `__` (double underscore a.k.a "dunder") and not ending with more than one trailing underscore by adding `_NameOfTheClass` in front.
+* So, to access `__honey` attribute in the first snippet, we had to append `_Yo` to the front, which would prevent conflicts with the same name attribute defined in any other class.
+* But then why didn't it work in the second snippet? Because name mangling excludes the names ending with double underscores.
+* The third snippet was also a consequence of name mangling. The name `__variable` in the statement `return __variable` was mangled to `_A__variable`, which also happens to be the name of the variable we declared in the outer scope.
+* Also, if the mangled name is longer than 255 characters, truncation will happen.
+
+---
+---
+
+## Section: Appearances are deceptive!
+
+### ▶ Skipping lines?
+
+**Output:**
+```py
+>>> value = 11
+>>> valuе = 32
+>>> value
+11
+```
+
+Wut?
+
+**Note:** The easiest way to reproduce this is to simply copy the statements from the above snippet and paste them into your file/shell.
+
+#### 💡 Explanation
+
+Some non-Western characters look identical to letters in the English alphabet but are considered distinct by the interpreter.
+
+```py
+>>> ord('е') # cyrillic 'e' (Ye)
+1077
+>>> ord('e') # latin 'e', as used in English and typed using standard keyboard
+101
+>>> 'е' == 'e'
+False
+
+>>> value = 42 # latin e
+>>> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
+>>> value
+42
+```
+
+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.
+
+---
+
+### ▶ Teleportation
+
+
+
+```py
+# `pip install numpy` first.
+import numpy as np
+
+def energy_send(x):
+ # Initializing a numpy array
+ np.array([float(x)])
+
+def energy_receive():
+ # Return an empty numpy array
+ return np.empty((), dtype=np.float).tolist()
+```
+
+**Output:**
+```py
+>>> energy_send(123.456)
+>>> energy_receive()
+123.456
+```
+
+Where's the Nobel Prize?
+
+#### 💡 Explanation:
+
+* Notice that the numpy array created in the `energy_send` function is not returned, so that memory space is free to reallocate.
+* `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).
+
+---
+
+### ▶ Well, something is fishy...
+
+```py
+def square(x):
+ """
+ A simple function to calculate the square of a number by addition.
+ """
+ sum_so_far = 0
+ for counter in range(x):
+ sum_so_far = sum_so_far + x
+ return sum_so_far
+```
+
+**Output (Python 2.x):**
+
+```py
+>>> square(10)
+10
+```
+
+Shouldn't that be 100?
+
+**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.
+
+#### 💡 Explanation
+
+* **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.
+* This is how Python handles tabs:
+
+ > 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 <...>
+* So the "tab" at the last line of `square` function is replaced with eight spaces, and it gets into the loop.
+* Python 3 is kind enough to throw an error for such cases automatically.
+
+ **Output (Python 3.x):**
+ ```py
+ TabError: inconsistent use of tabs and spaces in indentation
+ ```
+
+---
+---
+
+## Section: Miscellaneous
+
+
+### ▶ `+=` is faster
+
+
+```py
+# using "+", three strings:
+>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.25748300552368164
+# using "+=", three strings:
+>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.012188911437988281
+```
+
+#### 💡 Explanation:
++ `+=` 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.
+
+---
+
+### ▶ Let's make a giant string!
+
+```py
+def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s += "xyz"
+ assert len(s) == 3*iters
+
+def add_bytes_with_plus(iters):
+ s = b""
+ for i in range(iters):
+ s += b"xyz"
+ assert len(s) == 3*iters
+
+def add_string_with_format(iters):
+ fs = "{}"*iters
+ s = fs.format(*(["xyz"]*iters))
+ assert len(s) == 3*iters
+
+def add_string_with_join(iters):
+ l = []
+ for i in range(iters):
+ l.append("xyz")
+ s = "".join(l)
+ assert len(s) == 3*iters
+
+def convert_list_to_string(l, iters):
+ s = "".join(l)
+ assert len(s) == 3*iters
+```
+
+**Output:**
+
+```py
+# Executed in ipython shell using %timeit for better readability of results.
+# You can also use the timeit module in normal python shell/scriptm=, example usage below
+# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
+
+>>> NUM_ITERS = 1000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS)
+124 µs ± 4.73 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS)
+211 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS)
+61 µs ± 2.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS)
+117 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS)
+10.1 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+Let's increase the number of iterations by a factor of 10.
+
+```py
+>>> NUM_ITERS = 10000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linear increase in execution time
+1.26 ms ± 76.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS) # Quadratic increase
+6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS) # Linear increase
+645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS) # Linear increase
+1.17 ms ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS) # Linear increase
+86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+#### 💡 Explanation
+- You can read more about [timeit](https://docs.python.org/3/library/timeit.html) or [%timeit](https://ipython.org/ipython-doc/dev/interactive/magics.html#magic-timeit) on these links. They are used to measure the execution time of code pieces.
+- 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)
+- Therefore, it's advised to use `.format.` or `%` syntax (however, they are slightly slower than `+` for very short strings).
+- Or better, if already you've contents available in the form of an iterable object, then use `''.join(iterable_object)` which is much faster.
+- Unlike `add_bytes_with_plus` because of the `+=` optimizations discussed in the previous example, `add_string_with_plus` didn't show a quadratic increase in execution time. Had the statement been `s = s + "x" + "y" + "z"` instead of `s += "xyz"`, the increase would have been quadratic.
+ ```py
+ def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s = s + "x" + "y" + "z"
+ assert len(s) == 3*iters
+
+ >>> %timeit -n100 add_string_with_plus(1000)
+ 388 µs ± 22.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+ >>> %timeit -n100 add_string_with_plus(10000) # Quadratic increase in execution time
+ 9 ms ± 298 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+ ```
+- So many ways to format and create a giant string are somewhat in contrast to the [Zen of Python](https://www.python.org/dev/peps/pep-0020/), according to which,
+
+ > There should be one-- and preferably only one --obvious way to do it.
+
+---
+
+### ▶ Slowing down `dict` lookups *
+
+```py
+some_dict = {str(i): 1 for i in range(1_000_000)}
+another_dict = {str(i): 1 for i in range(1_000_000)}
+```
+
+**Output:**
+```py
+>>> %timeit some_dict['5']
+28.6 ns ± 0.115 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> some_dict[1] = 1
+>>> %timeit some_dict['5']
+37.2 ns ± 0.265 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+
+>>> %timeit another_dict['5']
+28.5 ns ± 0.142 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+>>> another_dict[1] # Trying to access a key that doesn't exist
+Traceback (most recent call last):
+ File "", line 1, in
+KeyError: 1
+>>> %timeit another_dict['5']
+38.5 ns ± 0.0913 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
+```
+Why are same lookups becoming slower?
+
+#### 💡 Explanation:
++ CPython has a generic dictionary lookup function that handles all types of keys (`str`, `int`, any object ...), and a specialized one for the common case of dictionaries composed of `str`-only keys.
++ The specialized function (named `lookdict_unicode` in CPython's [source](https://github.com/python/cpython/blob/522691c46e2ae51faaad5bbbce7d959dd61770df/Objects/dictobject.c#L841)) knows all existing keys (including the looked-up key) are strings, and uses the faster & simpler string comparison to compare keys, instead of calling the `__eq__` method.
++ The first time a `dict` instance is accessed with a non-`str` key, it's modified so future lookups use the generic function.
++ This process is not reversible for the particular `dict` instance, and the key doesn't even have to exist in the dictionary. That's why attempting a failed lookup has the same effect.
+
+
+### ▶ Bloating instance `dict`s *
+
+```py
+import sys
+
+class SomeClass:
+ def __init__(self):
+ self.some_attr1 = 1
+ self.some_attr2 = 2
+ self.some_attr3 = 3
+ self.some_attr4 = 4
+
+
+def dict_size(o):
+ return sys.getsizeof(o.__dict__)
+
+```
+
+**Output:** (Python 3.8, other Python 3 versions may vary a little)
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104
+>>> dict_size(o2)
+104
+>>> del o1.some_attr1
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+>>> dict_size(o1)
+232
+```
+
+Let's try again... In a new interpreter:
+
+```py
+>>> o1 = SomeClass()
+>>> o2 = SomeClass()
+>>> dict_size(o1)
+104 # as expected
+>>> o1.some_attr5 = 5
+>>> o1.some_attr6 = 6
+>>> dict_size(o1)
+360
+>>> dict_size(o2)
+272
+>>> o3 = SomeClass()
+>>> dict_size(o3)
+232
+```
+
+What makes those dictionaries become bloated? And why are newly created objects bloated as well?
+
+#### 💡 Explanation:
++ CPython is able to reuse the same "keys" object in multiple dictionaries. This was added in [PEP 412](https://www.python.org/dev/peps/pep-0412/) with the motivation to reduce memory usage, specifically in dictionaries of instances - where keys (instance attributes) tend to be common to all instances.
++ This optimization is entirely seamless for instance dictionaries, but it is disabled if certain assumptions are broken.
++ Key-sharing dictionaries do not support deletion; if an instance attribute is deleted, the dictionary is "unshared", and key-sharing is disabled for all future instances of the same class.
++ Additionally, if the dictionary keys have been resized (because new keys are inserted), they are kept shared *only* if they are used by a exactly single dictionary (this allows adding many attributes in the `__init__` of the very first created instance, without causing an "unshare"). If multiple instances exist when a resize happens, key-sharing is disabled for all future instances of the same class: CPython can't tell if your instances are using the same set of attributes anymore, and decides to bail out on attempting to share their keys.
++ A small tip, if you aim to lower your program's memory footprint: don't delete instance attributes, and make sure to initialize all attributes in your `__init__`!
+
+
+### ▶ Minor Ones *
+
+* `join()` is a string operation instead of list operation. (sort of counter-intuitive at first usage)
+
+ **💡 Explanation:** 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.
+
+* Few weird looking but semantically correct statements:
+ + `[] = ()` is a semantically correct statement (unpacking an empty `tuple` into an empty `list`)
+ + `'a'[0][0][0][0][0]` is also semantically correct, because Python doesn't have a character data type like other languages branched from C. So selecting a single character from a string returns a single-character string.
+ + `3 --0-- 5 == 8` and `--5 == 5` are both semantically correct statements and evaluate to `True`.
+
+* 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.
+ ```py
+ >>> a = 5
+ >>> a
+ 5
+ >>> ++a
+ 5
+ >>> --a
+ 5
+ ```
+
+ **💡 Explanation:**
+ + There is no `++` operator in Python grammar. It is actually two `+` operators.
+ + `++a` parses as `+(+a)` which translates to `a`. Similarly, the output of the statement `--a` can be justified.
+ + 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.
+
+* You must be aware of the Walrus operator in Python. But have you ever heard about *the space-invader operator*?
+ ```py
+ >>> a = 42
+ >>> a -=- 1
+ >>> a
+ 43
+ ```
+ It is used as an alternative incrementation operator, together with another one
+ ```py
+ >>> a +=+ 1
+ >>> a
+ >>> 44
+ ```
+ **💡 Explanation:** This prank comes from [Raymond Hettinger's tweet](https://twitter.com/raymondh/status/1131103570856632321?lang=en). The space invader operator is actually just a malformatted `a -= (-1)`. Which is equivalent to `a = a - (- 1)`. Similar for the `a += (+ 1)` case.
+
+* Python has an undocumented [converse implication](https://en.wikipedia.org/wiki/Converse_implication) operator.
+
+ ```py
+ >>> False ** False == True
+ True
+ >>> False ** True == False
+ True
+ >>> True ** False == True
+ True
+ >>> True ** True == True
+ True
+ ```
+
+ **💡 Explanation:** If you replace `False` and `True` by 0 and 1 and do the maths, the truth table is equivalent to a converse implication operator. ([Source](https://github.com/cosmologicon/pywat/blob/master/explanation.md#the-undocumented-converse-implication-operator))
+
+* Since we are talking operators, there's also `@` operator for matrix multiplication (don't worry, this time it's for real).
+
+ ```py
+ >>> import numpy as np
+ >>> np.array([2, 2, 2]) @ np.array([7, 8, 8])
+ 46
+ ```
+
+ **💡 Explanation:** The `@` operator was added in Python 3.5 keeping the scientific community in mind. Any object can overload `__matmul__` magic method to define behavior for this operator.
+
+* From Python 3.8 onwards you can use a typical f-string syntax like `f'{some_var=}` for quick debugging. Example,
+ ```py
+ >>> some_string = "wtfpython"
+ >>> f'{some_string=}'
+ "some_string='wtfpython'"
+ ```
+
+* 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!):
+
+ ```py
+ import dis
+ exec("""
+ def f():
+ """ + """
+ """.join(["X" + str(x) + "=" + str(x) for x in range(65539)]))
+
+ f()
+
+ print(dis.dis(f))
+ ```
+
+* 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/3/library/multiprocessing.html) module.
+
+* Sometimes, the `print` method might not print values immediately. For example,
+
+ ```py
+ # File some_file.py
+ import time
+
+ print("wtfpython", end="_")
+ time.sleep(3)
+ ```
+
+ This will print the `wtfpython` after 3 seconds due to the `end` argument because the output buffer is flushed either after encountering `\n` or when the program finishes execution. We can force the buffer to flush by passing `flush=True` argument.
+
+* List slicing with out of the bounds indices throws no errors
+ ```py
+ >>> some_list = [1, 2, 3, 4, 5]
+ >>> some_list[111:]
+ []
+ ```
+
+* Slicing an iterable not always creates a new object. For example,
+ ```py
+ >>> some_str = "wtfpython"
+ >>> some_list = ['w', 't', 'f', 'p', 'y', 't', 'h', 'o', 'n']
+ >>> some_list is some_list[:] # False expected because a new object is created.
+ False
+ >>> some_str is some_str[:] # True because strings are immutable, so making a new object is of not much use.
+ True
+ ```
+
+* `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](https://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/) related to this behavior of Python.
+
+* You can separate numeric literals with underscores (for better readability) from Python 3 onwards.
+
+ ```py
+ >>> six_million = 6_000_000
+ >>> six_million
+ 6000000
+ >>> hex_address = 0xF00D_CAFE
+ >>> hex_address
+ 4027435774
+ ```
+
+* `'abc'.count('') == 4`. Here's an approximate implementation of `count` method, which would make the things more clear
+ ```py
+ def count(s, sub):
+ result = 0
+ for i in range(len(s) + 1 - len(sub)):
+ result += (s[i:i + len(sub)] == sub)
+ return result
+ ```
+ The behavior is due to the matching of empty substring(`''`) with slices of length 0 in the original string.
+
+---
+---
+
+# Contributing
+
+A few ways in which you can contribute to wtfpython,
+
+- Suggesting new examples
+- Helping with translation (See [issues labeled translation](https://github.com/satwikkansal/wtfpython/issues?q=is%3Aissue+is%3Aopen+label%3Atranslation))
+- Minor corrections like pointing out outdated snippets, typos, formatting errors, etc.
+- Identifying gaps (things like inadequate explanation, redundant examples, etc.)
+- Any creative suggestions to make this project more fun and useful
+
+Please see [CONTRIBUTING.md](/CONTRIBUTING.md) for more details. Feel free to create a new [issue](https://github.com/satwikkansal/wtfpython/issues/new) to discuss things.
+
+PS: Please don't reach out with backlinking requests, no links will be added unless they're highly relevant to the project.
+
+# Acknowledgements
+
+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 Pythonistas gave it the shape it is in right now.
+
+#### Some nice Links!
+* https://www.youtube.com/watch?v=sH4XF6pKKmk
+* https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python
+* https://sopython.com/wiki/Common_Gotchas_In_Python
+* https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines
+* https://stackoverflow.com/questions/1011431/common-pitfalls-in-python
+* https://www.python.org/doc/humor/
+* https://github.com/cosmologicon/pywat#the-undocumented-converse-implication-operator
+* https://github.com/wemake-services/wemake-python-styleguide/search?q=wtfpython&type=Issues
+* WFTPython discussion threads on [Hacker News](https://news.ycombinator.com/item?id=21862073) and [Reddit](https://www.reddit.com/r/programming/comments/edsh3q/what_the_fck_python_30_exploring_and/).
+
+# 🎓 License
+
+[![WTFPL 2.0][license-image]][license-url]
+
+© [Satwik Kansal](https://satwikkansal.xyz)
+
+[license-url]: http://www.wtfpl.net
+[license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square
+
+## Surprise your friends as well!
+
+If you like wtfpython, you can use these quick links to share it with your friends,
+
+[Twitter](https://twitter.com/intent/tweet?url=https://github.com/satwikkansal/wtfpython&text=If%20you%20really%20think%20you%20know%20Python,%20think%20once%20more!%20Check%20out%20wtfpython&hashtags=python,wtfpython) | [Linkedin](https://www.linkedin.com/shareArticle?url=https://github.com/satwikkansal&title=What%20the%20f*ck%20Python!&summary=If%20you%20really%20thing%20you%20know%20Python,%20think%20once%20more!) | [Facebook](https://www.facebook.com/dialog/share?app_id=536779657179021&display=page&href=https%3A%2F%2Fgithub.com%2Fsatwikkansal%2Fwtfpython"e=If%20you%20really%20think%20you%20know%20Python%2C%20think%20once%20more!)
+
+## Need a pdf version?
+
+I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details [here](https://form.jotform.com/221593245656057) to get them as soon as they are finished.
+
+
+**That's all folks!** For upcoming content like this, you can add your email [here](https://form.jotform.com/221593598380062).
diff --git a/docs/VI.md b/docs/VI.md
new file mode 100644
index 0000000..989f684
--- /dev/null
+++ b/docs/VI.md
@@ -0,0 +1,3531 @@
+---
+hide:
+ - toc
+---
+
+
+What the f*ck Python! 😱
+
+English
+| Tiếng Việt
+
+Cùng khám phá và tìm hiểu Python thông qua các đoạn mã khiến bạn bất ngờ.
+
+
+
+Các bản dịch tiếng nước ngoài khác: [Tiếng Trung 中文](https://github.com/leisurelicht/wtfpython-cn) | [Thêm bản dịch](https://github.com/satwikkansal/wtfpython/issues/new?title=Add%20translation%20for%20[LANGUAGE]&body=Expected%20time%20to%20finish:%20[X]%20weeks.%20I%27ll%20start%20working%20on%20it%20from%20[Y].)
+
+Bạn có thể tham khảo các đoạn mã với: [Chế độ trực quan](https://colab.research.google.com/github/vuduclyunitn/wtfptyhon-vi/blob/master/irrelevant/wtf.ipynb) | [Giao diện dòng lệnh](https://pypi.python.org/pypi/wtfpython)
+
+
+Python là một ngôn ngữ cấp cao, với các mã được thông dịch thay vì biên dịch như các ngôn ngữ khác như C hay Java. Python có rất nhiều các tính năng giúp việc lập trình dễ dàng, thuận tiện. Tuy nhiên, các đoạn mã viết bằng Python thỉnh thoảng cho ra kết quả không rõ ràng, gây khó hiểu khi mới nhìn vào.
+
+
+wtfpython được tạo ra với mong muốn giải thích chính xác cách hoạt động của các đoạn mã thoạt nhìn khó hiểu và các tính năng ít được biết tới trong Python.
+
+
+Một vài ví dụ có thể không làm bạn quá ngạc nhiên, tuy vậy bạn sẽ khám phá được những điều hay ho về Python mà có thể bạn chưa từng biết tới. Học lập trình thông qua những ví dụ như vậy giúp bạn hiểu sâu hơn những thứ nằm bên trong của một ngôn ngữ lập trình, khi đó bạn sẽ thấy hứng thú hơn trong quá trình học.
+
+
+Nếu độc giả là một lập trình viên có thâm niên, hãy thử thức mình với các đoạn mã sắp tới, cố gắng làm đúng mỗi thử thách ngay trong lần đầu tiên. Độc giả có thể đã thử quả một vài trong số các bài toán trước đó, đọc và làm các bài toán dưới đây có thể giúp bạn ôn lại chúng.
+
+
+PS: Nếu bạn đã đọc bài này trước đó, bạn có thể muốn xem những thay đổi mới [ở đây](https://github.com/satwikkansal/wtfpython/releases/).
+
+
+
+Nào ta bắt đầu ...
+
+# Những nội dung chính
+
+
+
+
+- [Cấu trúc của các ví dụ](#structure-of-the-examples)
+ + [▶ Some fancy Title](#-some-fancy-title)
+- [Cách sử dụng](#usage)
+- [👀 Các ví dụ](#-examples)
+ * [Section: Strain your brain!](#section-strain-your-brain)
+ + [▶ First things first! *](#-first-things-first-)
+ + [▶ Strings can be tricky sometimes](#-strings-can-be-tricky-sometimes)
+ + [▶ Be careful with chained operations](#-be-careful-with-chained-operations)
+ + [▶ How not to use `is` operator](#-how-not-to-use-is-operator)
+ + [▶ Hash brownies](#-hash-brownies)
+ + [▶ Deep down, we're all the same.](#-deep-down-were-all-the-same)
+ + [▶ Disorder within order *](#-disorder-within-order-)
+ + [▶ Keep trying... *](#-keep-trying-)
+ + [▶ For what?](#-for-what)
+ + [▶ Evaluation time discrepancy](#-evaluation-time-discrepancy)
+ + [▶ `is not ...` is not `is (not ...)`](#-is-not--is-not-is-not-)
+ + [▶ A tic-tac-toe where X wins in the first attempt!](#-a-tic-tac-toe-where-x-wins-in-the-first-attempt)
+ + [▶ The sticky output function](#-the-sticky-output-function)
+ + [▶ The chicken-egg problem *](#-the-chicken-egg-problem-)
+ + [▶ Subclass relationships](#-subclass-relationships)
+ + [▶ All-true-ation *](#-all-true-ation-)
+ + [▶ The surprising comma](#-the-surprising-comma)
+ + [▶ Strings and the backslashes](#-strings-and-the-backslashes)
+ + [▶ not knot!](#-not-knot)
+ + [▶ Half triple-quoted strings](#-half-triple-quoted-strings)
+ + [▶ What's wrong with booleans?](#-whats-wrong-with-booleans)
+ + [▶ Class attributes and instance attributes](#-class-attributes-and-instance-attributes)
+ + [▶ Non-reflexive class method *](#-non-reflexive-class-method-)
+ + [▶ yielding None](#-yielding-none)
+ + [▶ Yielding from... return! *](#-yielding-from-return-)
+ + [▶ Nan-reflexivity *](#-nan-reflexivity-)
+ + [▶ Mutating the immutable!](#-mutating-the-immutable)
+ + [▶ The disappearing variable from outer scope](#-the-disappearing-variable-from-outer-scope)
+ + [▶ The mysterious key type conversion](#-the-mysterious-key-type-conversion)
+ + [▶ Let's see if you can guess this?](#-lets-see-if-you-can-guess-this)
+ * [Section: Slippery Slopes](#section-slippery-slopes)
+ + [▶ Modifying a dictionary while iterating over it](#-modifying-a-dictionary-while-iterating-over-it)
+ + [▶ Stubborn `del` operation](#-stubborn-del-operation)
+ + [▶ The out of scope variable](#-the-out-of-scope-variable)
+ + [▶ Deleting a list item while iterating](#-deleting-a-list-item-while-iterating)
+ + [▶ Lossy zip of iterators *](#-lossy-zip-of-iterators-)
+ + [▶ Loop variables leaking out!](#-loop-variables-leaking-out)
+ + [▶ Beware of default mutable arguments!](#-beware-of-default-mutable-arguments)
+ + [▶ Catching the Exceptions](#-catching-the-exceptions)
+ + [▶ Same operands, different story!](#-same-operands-different-story)
+ + [▶ Name resolution ignoring class scope](#-name-resolution-ignoring-class-scope)
+ + [▶ Needles in a Haystack *](#-needles-in-a-haystack-)
+ + [▶ Splitsies *](#-splitsies-)
+ + [▶ Wild imports *](#-wild-imports-)
+ + [▶ All sorted? *](#-all-sorted-)
+ + [▶ Midnight time doesn't exist?](#-midnight-time-doesnt-exist)
+ * [Section: The Hidden treasures!](#section-the-hidden-treasures)
+ + [▶ Okay Python, Can you make me fly?](#-okay-python-can-you-make-me-fly)
+ + [▶ `goto`, but why?](#-goto-but-why)
+ + [▶ Brace yourself!](#-brace-yourself)
+ + [▶ Let's meet Friendly Language Uncle For Life](#-lets-meet-friendly-language-uncle-for-life)
+ + [▶ Even Python understands that love is complicated](#-even-python-understands-that-love-is-complicated)
+ + [▶ Yes, it exists!](#-yes-it-exists)
+ + [▶ Ellipsis *](#-ellipsis-)
+ + [▶ Inpinity](#-inpinity)
+ + [▶ Let's mangle](#-lets-mangle)
+ * [Section: Appearances are deceptive!](#section-appearances-are-deceptive)
+ + [▶ Skipping lines?](#-skipping-lines)
+ + [▶ Teleportation](#-teleportation)
+ + [▶ Well, something is fishy...](#-well-something-is-fishy)
+ * [Section: Miscellaneous](#section-miscellaneous)
+ + [▶ `+=` is faster](#--is-faster)
+ + [▶ Let's make a giant string!](#-lets-make-a-giant-string)
+ + [▶ Minor Ones *](#-minor-ones-)
+- [Contributing](#contributing)
+- [Acknowledgements](#acknowledgements)
+- [🎓 License](#-license)
+ * [Surprise your friends as well!](#surprise-your-friends-as-well)
+ * [More content like this?](#more-content-like-this)
+
+
+
+# Cấu trúc của các ví dụ
+
+Tất cả các các ví dụ được trình bày với cấu trúc như sau:
+> ### ▶ Một tiêu đề hấp dẫn
+>
+> ```py
+> # Đoạn mã tạo dựng ví dụ.
+> # Đoạn mã chủ thể cần khám phá...
+> ```
+>
+> **Kết quả (Các phiên bản Python):**
+>
+> ```py
+> >>> câu lệnh kích hoạt?
+> Một vài kết quả bất ngờ, không như mong đợi
+> ```
+> (Có thể có hay không): Một dòng mô tả kết quả
+>
+>
+> #### 💡 Giải thích:
+>
+> * Giải thích những điều đang diễn ra và tại sao.
+> ```py
+> # Đoạn mã tạo dựng ví dụ
+> # Trong trường hợp cần thiết, chúng tôi liệt kê thêm nhiều ví dụ khác để giúp bạn hiểu rõ hơn
+> ```
+> **Kết quả (Các phiên bản Python):**
+>
+> ```py
+> >>> trigger # some example that makes it easy to unveil the magic
+> >>> trigger # Một vài ví dụ giúp bạn hiểu các đoạn mã
+> # some justified output
+> ```
+
+**Lưu ý:** Tất cả các ví dụ đã được chứng minh chạy thành công trên trình thông dịch Python 3.5.2 chế độ tương tác, với các phiên bản Python khác các ví dụ sẽ vẫn chạy bình thường, ngoại trừ một số ví dụ chúng tôi sẽ lưu ý trước phần kết quả.
+# Cách dùng các ví dụ
+
+Theo tôi, để học các ví dụ trong bài, bạn nên đọc theo trình tự thời gian, và đối với mỗi ví dụ hãy:
+- Đọc kĩ đoạn mã tạo dựng nên ví dụ. Nếu bạn đã lập trình lâu rồi, bạn sẽ đoán được những điều sắp tới ngay.
+- Đọc kết quả của các ví dụ và thực hiện hai việc sau:
+ + Kiểm tra xem kết quả có giống như bạn nghĩ hay không.
+ + Một khi đọc xong, hãy hỏi chính bạn xem mình đã hiểu thông suốt lý do mà có kết quả như vậy chưa.
+ - Nếu câu trả lời là "chưa, tôi chưa hiểu" (không sao cả), hít một hơi thật sau, và đọc phần giải thích (nếu bạn vẫn chưa hiểu, hãy tạo một issue [ở dây](https://github.com/satwikkansal/wtfpython/issues/new)).
+ - Nếu câu trả lời là "có, tôi đã hiểu", bạn có thể đọc ví dụ tiếp theo.
+PS: Bạn có thể đọc WTFPython dùng chế độ dòng lệnh sử dụng [pypi package](https://pypi.python.org/pypi/wtfpython),
+```sh
+$ pip install wtfpython -U
+$ wtfpython
+```
+---
+
+# 👀 Các ví dụ
+
+## Chương 1: Hack não!
+
+### ▶ Món khai vị! *
+
+
+
+
+Kí hiệu "con hà mã" ("Walrus" operator), được giới thiệu trong phiên bản Python 3.8 đã trở nên khá phổ biến vì một vài lý do. Hãy thử qua nó xem
+1\.
+
+```py
+# Phiên bản Python 3.8+
+
+>>> a = "wtf_walrus"
+>>> a
+'wtf_walrus'
+
+>>> a := "wtf_walrus"
+File "", line 1
+ a := "wtf_walrus"
+ ^
+SyntaxError: invalid syntax (Lỗi về cú pháp: Cú pháp không hợp lệ)
+
+>>> (a := "wtf_walrus") # This works though
+>>> a
+'wtf_walrus'
+```
+
+2 \.
+
+```py
+# Phiên bản Python 3.8+
+
+>>> a = 6, 9
+>>> a
+(6, 9)
+
+>>> (a := 6, 9)
+>>> a
+6
+
+>>> a, b = 6, 9 # Phân rã (unpacking) các giá trị, hay còn gọi là câu lệnh gán đa giá trị (multiple assignments)
+>>> a, b
+(6, 9)
+>>> (a, b = 16, 19) # Có
+ File "", line 1
+ (a, b = 6, 9)
+ ^
+SyntaxError: invalid syntax (Lỗi cú pháp: cú pháp không hợp lệ)
+
+>>> (a, b := 16, 19) # Câu lệnh này in ra một tuple có 3 phần tử không như mong đợi (đáng lẽ là 2 phần tử 16 và 19)
+(6, 16, 19)
+
+>>> a # a được được gán lại giá trị trước đó, nhưng giá trị phía dưới vẫn giữ nguyên, là sao?
+6
+
+>>> b
+16
+```
+
+
+
+#### 💡 Giải thích
+
+**Ôn lại một chút về kí hiệu "con hà mã"**
+
+Kí hiệu con hà mã (`:=`) lần đầu tiên được giới thiệu trong phiên bản Python 3.8, nó hữu dụng khi bạn muốn gán giá trị cho các biến bên trong một biểu diễn (expression).
+
+```py
+def some_func():
+ # Giả định rằng ta thực hiện một vài phép tính tốn nhiều tài nguyên (thời gian, I/O) ở đây
+ # time.sleep(1000)
+ return 5
+
+# Thay vì thực hiện việc kiểm tra kết quả trả về của hàm trên,
+if some_func():
+ print(some_func()) # Và gọi lại hàm đó trong thân điều kiện, nghĩa là thực hiện các tính toán trong hàm 2 lần.
+# Hay tốt hơn, ta có thể tiết kiệm một lời gọi hàm thông qua việc lấy về giá trị trả về ở một lần gọi và thực hiện so sánh trên giá trị đó:
+a = some_func()
+if a:
+ print(a)
+
+# Dùng kĩ hiệu con hà mã bạn có thể viết ngắn gọn hơn như dưới đây, phép gán được sử dụng như mệnh đề điều kiện và ta có thể sử dụng biến được gán giá trị trong thân câu điều kiện if:
+if a := some_func():
+ print(a)
+```
+
+**Kết quả (> 3.8):**
+
+```py
+5
+5
+5
+```
+
+Sử dụng kí hiệu con hà mã giúp ta rút ngắn được đoạn mã đi một dòng và tránh được việc gọi `some_func` hai lần.
+
+- Ta chỉ được sử dụng phép gán có kí hiệu hà mã ở cấp độ cao nhất do đó lỗi cú pháp (`SyntaxError`) trong câu lệnh `a := "wtf_walrus"` xảy ra. Khi ta cho phép gán này vào hai dấu ngoặc đơn `()` thì sẽ không bị lỗi nữa.
+
+- Thông thường, câu lệnh có dấu bằng `=` sẽ không được phép đặt trong dấu ngoặc đơn. Do vậy câu lệnh `(a, b = 6, 9)` bị lỗi cú pháp.
+
+- Cú pháp của kí hiệu gán con hà mã như sau: `NAME:= expr`, ở đó `NAME` là một tên biến hợp lệ, và `expr` là một biểu diễn hợp lệ. Do vậy, việc sử dụng các phép gộp (packing) hay phân rã trong trường hợp này sẽ không được hỗ trợ, nghãi là
+ - `(a := 6, 9)` tương đương với `((a := 6), 9)` và buổi diễn cuối cùng là `(a, 9) ` (ở đó giá trị của `a` là 6). Bạn có thể kiểm tra lại với các dòng lệnh dưới đây
+
+ ```py
+ >>> (a := 6, 9) == ((a := 6), 9)
+ True # Biểu diễn bên trái bằng bên phải do phép phân rã không được phép (như đã giải thích phía trên)
+ >>> x = (a := 696, 9)
+ >>> x
+ (696, 9)
+ >>> x[0] is a # Cả x[0] và a cùng trỏ về chung một địa chỉ trong bộ nhớ
+ True
+ ```
+
+ - Tương tự, `(a, b := 16, 19)` tương đương với `(a, (b := 16), 19)` khi ta có 3 giá trị trong một tuple.
+
+---
+
+### ▶ Strings thỉnh thoảng có thể khá oái oăm
+
+
+1\.
+
+```py
+>>> a = "some_string"
+>>> id(a)
+140420665652016
+>>> id("some" + "_" + "string") # Để ý rằng cả hai giá trị id đều giống nhau (140420665652016).
+140420665652016
+```
+
+2\.
+```py
+>>> a = "wtf"
+>>> b = "wtf"
+>>> a is b
+True # a và b cùng trỏ tới một địa chỉ trong bộ nhớ
+
+>>> a = "wtf!"
+>>> b = "wtf!"
+>>> a is b
+False # a và b không cùng trỏ tới một địa chỉ trong bộ nhớ
+
+```
+
+3\.
+
+```py
+>>> a, b = "wtf!", "wtf!"
+>>> a is b # Áp dụng cho tất cả các phiên bản Python, ngoại trừ các phiên bản 3.7.x
+True # a và b cùng trỏ tới một địa chỉ trong bộ nhớ
+
+>>> a = "wtf!"; b = "wtf!"
+>>> a is b # Kết quả là True hoặc False tuỳ thuộc vào môi trường bên chạy đoạn mã (python shell / ipython / as a script)
+False
+```
+
+```py
+# Tạo một file tên some_file.py, chứa ba dòng code dưới đây:
+a = "wtf!"
+b = "wtf!"
+print(a is b)
+
+# Khi file này được chạy kết quả in ra là True
+
+```
+
+4\.
+
+**Kết quả (< Python3.7 )**
+
+```py
+>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
+True
+>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
+False
+```
+
+Có gì đó sai sai?
+
+#### 💡Giải thích:
++ Những điều xảy ra trong các đoạn mã đầu tiên và thứ hai đến từ một tối ưu của CPython (được gọi là string interning), tối ưu này cố gắng sử dụng các đối tượng không thể thay đổi giá trị hiện tồn tại trong một vài trường hợp thay vì việc tạo ra một đối tượng mới mỗi lần.
++ Sau khi được "interned," nhiều biến có thể tham chiếu tới cùng một đối tượng string trong bộ nhớ (do vậy mà tiết kiệm bộ nhớ).
++ Trong các đoạn mã phía trên, các strings được interned một cách ngầm. Quyết định ki nào thì intern một cách ngầm là phụ thuộc vào mỗi triển khai Python. Có một vài luật có thể được sử dụng để đoán biết một string sẽ được intern hay không:
+ * Tất cả strings có độ dài bằng 0 và 1 được intern.
+ * Các strings được intern tại thời điểm biên dịch (`'wtf'` sẽ được intern nhưng `''.join(['w', 't', 'f'])` sẽ không được intern).
+ * Các strings không được tạo thành từ các mẫu tự ASCII, số, hoặc dấu gạch chân, sẽ không được intern. Điều đó giải thích tại sao `'wtf!'` không được intern bởi vì kí tự `!`. Triển khai Cpython cho luật này có thể tìm thấy [ở đây](https://github.com/python/cpython/blob/3.6/Objects/codeobject.c#L19)
+ ![image](../images/string-intern/string_intern.png)
++ Khi `a` and `b` được cho nhận giá trị `"wtf!"` trên cùng một dòng, trình thông dịch Python sẽ tạo một đối tượng mới, sau đó tham chiếu tới biến thứ hai tại cùng một thời điểm. Nếu bạn làm các phép gán trên các dòng riêng biệt, trình thông dịch sẽ không biết rằng đã có `"wtf!"` tồn tại như một đối tượng (bởi vì `"wtf!"` không được intern ngầm như điều được trình bày trên). Đây là một tối ưu tại thời điểm biên dịch. Tối ưu này không áp dụng cho các phiên bản CPython 3.7.x (xem thêm về vấn đề này [ở đây] (https://github.com/satwikkansal/wtfpython/issues/100)).
++ Một đơn vị biên dịch trong môi trường tương tác như IPython bao gồm chỉ một câu lệnh đơn, trái lại `a = "wtf!"; b = "wtf!"`là hai câu lệnh. Điều này giải thích tại sao các danh định (identities) lại khác nhau trong `a = "wtf!"; b = "wtf!"`, và cũng giải thích tại sao chúng lại giống nhau trong `some_file.py`.
++ Sự thay đổi đột ngột trong đoạn mã thứ tư là do một kĩ thuật [Tối ưu peephole](https://en.wikipedia.org/wiki/Peephole_optimization) hay còn được biết đến như sự gấp hằng số. Điều này có nghĩa là biểu diễn `'a'*20` được thay thế bở `'aaaaaaaaaaaaaaaaaaaa'` trong quá trình biên dịch tiết kiệm một vài chu kì đồng hồ trong suốt thời gian chạy . Sự gấp hằng số chỉ xảy ra đối với các strings có độ dài nhỏ hơn 20 (Tại sao? tưởng tượng kích thước của file `.pyc` sinh ra bởi biểu diễn `'a'*10**10`). [Link này](https://github.com/python/cpython/blob/3.6/Python/peephole.c#L288) dẫn tới triển khai của tối ưu được đề cập.
++ Chú ý rằng: trong Python 3.7, gấp hằng số được chuyển từ bộ tối ưu peephole sang bộ tối ưu mới AST với một vài thay đổi luận lý, nên đoạn mã thứ tư không chạy được trong Python 3.7. Bạn có thể đọc thêm về sự thay đổi này [ở ](https://bugs.python.org/issue11549).
+
+---
+
+
+### ▶ Be careful with chained operations
+
+```py
+>>> (False == False) in [False] # makes sense
+False
+>>> False == (False in [False]) # makes sense
+False
+>>> False == False in [False] # now what?
+True
+
+>>> True is False == False
+False
+>>> False is False is False
+True
+
+>>> 1 > 0 < 1
+True
+>>> (1 > 0) < 1
+False
+>>> 1 > (0 < 1)
+False
+```
+
+#### 💡 Giải thích:
+
+As per https://docs.python.org/2/reference/expressions.html#not-in
+
+
+> Nếu a, b, c, ..., y, z là các biểu diễn (expressions) và op1, op2, ..., opN là các phép so sánh, khi đó op1 b op2 c ... y opN z tương đương với op1 b and b op2 c and ... y opN, ngoại trì việc mỗi biểu diễn được thực hiện hay đánh giá nhiều nhât một lần
+
+Trong khi những điều ta thấy phía trên có thể hơi ngớ ngẩn đối với bạn, ta có thể làm những thứ thú vị hơn như `a == b == c` và `0 <= x <= 100`.
+
+* `False is False is False` tương đương `(False is False) and (False is False)`
+* `True is False == False` tương đương với `True is False and False == False` và do phần so sánh đầu tiên (`True is False`) cho ra kết quả `False`, do đó kết quả cuối cùng là `False`.
+* `1 > 0 < 1` tương đương với `1 > 0 and 0 < 1` và cho ra kết quả là `True`.
+* Biểu diễn `(1 > 0) < 1` tương đương với `True < 1` và
+ ```py
+ >>> int(True)
+ 1
+ >>> True + 1 #làm cho vui thôi, chứ không liên quan
+ 2
+ ```
+ do đó, `1 < 1` cho ra kết quả`False`
+
+---
+
+### ▶ How not to use `is` operator
+
+Ví dụ dươi đây rất nổi tiếng trên Internet
+1\.
+
+```py
+>>> a = 256
+>>> b = 256
+>>> a is b
+True
+
+>>> a = 257
+>>> b = 257
+>>> a is b
+False
+```
+
+2\.
+
+```py
+>>> a = []
+>>> b = []
+>>> a is b
+False
+
+>>> a = tuple()
+>>> b = tuple()
+>>> a is b
+True
+```
+
+3\.
+**Kết quả**
+
+```py
+>>> a, b = 257, 257
+>>> a is b
+True
+```
+
+**Kết qủa (Chỉ áp dụng cho Python 3.7.x )**
+
+```py
+>>> a, b = 257, 257
+>> a is b
+False
+```
+
+#### 💡 Giải thích:
+
+**Sự khác biệt giữa `is` và `==`**
+
+* `is` kiểm tra xem cả hai phần tử so sánh có trỏ về cùng một đôi tượng (ví dụ, `is` kiểm tra định danh của cả hai thành phần được so sánh có khớp với nhau hay không)
+* `==` so sánh giá trị của hai phần tử xem chúng có bằng nhau hay không
+* Vì thế `is` được dùng cho việc so sánh tham chiếu và `==` được dùng cho so sánh tham trị. Hãy xem ví dụ dưới đây để hiểu rõ hơn
+ ```py
+ >>> class A: pass
+ >>> A() is A() # Hai đối tượng rỗng nhưng nằm ở hai vị trí khác nhau trong bộ nhớ.
+ False
+ ```
+** `256` là một đối tượng hiện hữu nhưng `257` lại không phải là một đối tượng hiện hữu.
+
+Khi bạn khởi chạy python các số từ `-5` tới `256` sẽ được cấp phát. Những sô nay được sử dụng rất nhiều, do đó việc cấp phát này là hợp lý.
+Tham khảo từ https://docs.python.org/3/c-api/long.html
+
+> Cách triển khai hiện hành của Python duy trì một mảng các đối tượng sô nguyên từ -5 tới 256, khi bạn tạo một số nguyên trong dải này bạn sẽ quay trở về lại một tham chiếu tới một đôi tượng tồn tại. Do vậy ta vẫn có thể thay đổi giá trị của 1.
+
+```py
+>>> id(256)
+10922528
+>>> a = 256
+>>> b = 256
+>>> id(a)
+10922528
+>>> id(b)
+10922528
+>>> id(257)
+140084850247312
+>>> x = 257
+>>> y = 257
+>>> id(x)
+140084850247440
+>>> id(y)
+140084850247344
+```
+
+
+Ở đây trình thông dịch không đủ thông minh khi thực thi `y = 257` và nhận ra rằng chúng ta đã tạo một số nguyên có giá trị là `257,` rồi, do đó nó tiếp tục tạo một đôi tượng khác trong bộ nhớ.
+
+Một tối ưu tương tự áp dụng cho các đối tượng bất biến (**immutable**) khác như là các tuples. Bởi vì lists có thể biến đổi được, do đó ta hiểu tại sao `[] is []` sẽ trả về `False` và `() is ()` sẽ trả về `True`. Điều này giải thích đoạn mã thứ hai. Nào hãy cùng đi qua ví dụ thứ 3.
+
+** Cả `a` và `b` đều trỏ về cùng một đối tượng khi được khởi tạo với cung một giá trị và trên cùng một dòng code**
+**Kết quả**
+
+```py
+>>> a, b = 257, 257
+>>> id(a)
+140640774013296
+>>> id(b)
+140640774013296
+>>> a = 257
+>>> b = 257
+>>> id(a)
+140640774013392
+>>> id(b)
+140640774013488
+```
+
+
+* Trên cùng một dòng code nơi cả a va b được gán cho giá trị `257`, trinh thông dịch Python tạo một đôi tượng mới, sau đó trỏ tới biến thứ hai cùng một lúc. Nếu bạn thực hiện việc gán trên các dòng riêng biệt, Python sẽ không biết rằng đã có săn một đối tượng `257`
+
+* Đây là một tối ưu của trình biên dịch, và áp dụng cụ thể cho môi trường tương tác (interactive environment). Khi bạn nhập hai dòng code trong phiên thôn dịch động, chúng được biên dịch riêng, do đó được tối ưu riêng. Nếu bạn thử ví dụ này trong một file `.py` bạn sẽ không thấy điều trên xảy ra do file code được biên dịch một lần. Tối ưu nay không chỉ giơi hạn cho các số nguyên, nó còn hoạt động được với các kiểu dữ liệu bất biến khác như strings (xem "Strings are tricky example") và floats.
+ ```py
+ >>> a, b = 257.0, 257.0
+ >>> a is b
+ True
+ ```
+
+* Tại sao ví dụ nay lại không chạy được trên Python 3.7? Đại khái lý do là bởi vì các tối ưu của trình biên dịch áp dụng cho các trường hợp cụ thể (ví dụ. một cách tối ưu có thể thay đổi tuỳ theo phiên bản, hệ điều hành, vân vân). Tôi vân đang tìm hiểu các thay đổi cụ thể trong code triểu khai, bạn có thể xem thêm [tại đây](https://github.com/satwikkansal/wtfpython/issues/100)
+---
+
+
+### ▶ Hash brownies
+
+1\.
+```py
+some_dict = {}
+some_dict[5.5] = "JavaScript"
+some_dict[5.0] = "Ruby"
+some_dict[5] = "Python"
+```
+
+**Kết quả:**
+
+```py
+>>> some_dict[5.5]
+"JavaScript"
+>>> some_dict[5.0] # "Python" chiếm lấy khoá (key) của "Ruby"?
+"Python"
+>>> some_dict[5]
+"Python"
+
+>>> complex_five = 5 + 0j
+>>> type(complex_five)
+complex
+>>> some_dict[complex_five]
+"Python"
+```
+
+Thế quái nào mà toàn in ra Python?
+
+
+#### 💡 Giải thích
+
+* Tính duy nhất của các khoá (keys) trong cấu trúc dữ liệu từ điển của được xác định bởi *sự tương đương*, chứ không phải dựa trên danh tính. Do đó dẫu cho `5`, `5.0`, và `5 + 0j` là các đối tượng riêng biệt có kiểu khác nhau, nhưng bởi vì chúng băng nhau nên không thể tồn tại như một khoá riêng của `dict` (hoặc `set`). Khi bạn thêm các khoá này vào từ điển sau đó tra giá trị của khoá đó dựa trên giả định về sự tương đương thì Python chỉ trả về giá trị của khoá được chèn vào ban đầu (thay vì trả lại lỗi về truy xuất khoá `KeyError`):
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> 5 is not 5.0 is not 5 + 0j
+ True
+ >>> some_dict = {}
+ >>> some_dict[5.0] = "Ruby"
+ >>> 5.0 in some_dict
+ True
+ >>> (5 in some_dict) and (5 + 0j in some_dict)
+ True
+ ```
+* Nguyên lý trên cũng áp dụng khi bạn gán giá trị cho khoá. Khi bạn thực hiện phép gán `some_dict[5] = "Python"`, Python tìm phần tử có sẵn trong từ điển có khoá tương đương, trong trường hợp này nè `5.0 -> "Ruby"`, ghi đè lên giá trị của khoá này ngay, và lờ đi khoá tương đương mà bạn mới cung cấp.
+ ```py
+ >>> some_dict
+ {5.0: 'Ruby'}
+ >>> some_dict[5] = "Python"
+ >>> some_dict
+ {5.0: 'Python'}
+ ```
+
+* Vậy làm sao để cập nhật khoá `5` vào từ điển (thay vì `5.0`)? Thực sự là chúng ta không thể làm điều đó vơi một thao tác, nhưng ta có thể xoá đi khoá cũ (`del some_dict[5.0]`), và sau đó thiết lập khoá mới(`some_dict[5]`) để có thể lấy được khoá `5` thay vì `5.0`, tuy nhiên cách này ít ai xài tới lắm.
+
+
+* Làm thế nào Python tìm khoá `5` trong từ điển có chứa sẵn khoá `5.0`? Nó làm được điều đó mà không phải dò qua mọi phần tử trong từ điển thông qua sử dụng các hàm băm (hash functions), do đó tốn thời gian chạy hằng số (constant time). Khi Python tra khoá tên `foo` trong một tư điển, đầu tiên nó thực hiện hàm băm `hash(foo)` (với thời gian chạy hằng số). Bởi vì trong Python các đối tượng băng nhau khi chúng có chung một giá trị băm ([đọc thêm ở đây](https://docs.python.org/3/reference/datamodel.html#object.__hash__) here), `5`, `5.0`, and `5 + 0j` have the same hash value.
+ ```py
+ >>> 5 == 5.0 == 5 + 0j
+ True
+ >>> hash(5) == hash(5.0) == hash(5 + 0j)
+ True
+ ```
+ **Ghi chú:** Các đối tượng có giá trị băm băng nhau chưa chắc đã bằng nhau. (Vẫn đề này được biết tới với tên gọi [hash collision](https://en.wikipedia.org/wiki/Collision_(computer_science)), và giảm đi hiệu năng với thời gian hằng số qua việc dùng hàm băm.)
+
+---
+
+### ▶ Deep down, we're all the same.
+
+```py
+class WTF:
+ pass
+```
+
+**Kết quả:**
+```py
+>>> WTF() == WTF() # two different instances can't be equal
+False
+>>> WTF() is WTF() # identities are also different
+False
+>>> hash(WTF()) == hash(WTF()) # hashes _should_ be different as well
+True
+>>> id(WTF()) == id(WTF())
+True
+```
+
+#### 💡 Giải thích:
+
+* Khi `id` được gọi, Python tạo một đối tượng lớp `WTF` và truyền đối tượng này cho hàm `id`. Hàm `id` lấy `id` của đối tượng (vùng nhớ của đối tượng), và vứt đối tượng này đi. Do đó đối tượng bị tiêu huỷ.
+* Khi chúng ta gọi `id` hai lần, Python cấp phát cùng một vùng nhơ cho đối tượng thư hai. Bởi vi (trong CPython) `id` sử dụng vùng nhớ cho id của đối tượng, id của hai đối tượng này là giống nhau.
+* Vì vậy, id của đối tượng chỉ duy nhất trong vòng đời của đối tượng đó. Sau khi đối tượng bị tiêu hiểu, hay trước khi nó được tạo, những thứ khác có thể có cùng id với nó.
+* Nhưng tại sao phép `is` lại cho ra kết quả `False`? Hãy nhìn ví dụ dưới đây
+```py
+ class WTF(object):
+ def __init__(self): print("I")
+ def __del__(self): print("D")
+ ```
+ **Kết quả:**
+ ```py
+ >>> WTF() is WTF()
+ I
+ I
+ D
+ D
+ False
+ >>> id(WTF()) == id(WTF())
+ I
+ D
+ I
+ D
+ True
+ ```
+ Như bạn quan sát thấy, có sự khác biệt ở thứ tự tiêu hiểu các đối tượng, và đó tạo ra sự khác biệt.
+
+---
+
+### ▶ Vô trật tự trong trật tự *
+
+```py
+from collections import OrderedDict
+
+
+dictionary[1] = 'a'; dictionary[2] = 'b';
+
+ordered_dict = OrderedDict()
+ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+
+another_ordered_dict = OrderedDict()
+another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+
+class DictWithHash(dict):
+ """
+ A dict that also implements __hash__ magic.
+ """
+ __hash__ = lambda self: 0
+
+class OrderedDictWithHash(OrderedDict):
+ """
+ An OrderedDict that also implements __hash__ magic.
+ """
+ __hash__ = lambda self: 0
+```
+
+**Kết quả**
+```py
+>>> dictionary == ordered_dict # Nếu a == b
+True
+>>> dictionary == another_ordered_dict # and b == c
+True
+>>> ordered_dict == another_ordered_dict # thế sao c != a ??
+False
+
+# ta biết răng set chỉ chứa các phần tử độc nhất,
+# thử tạo một set chứa 3 từ điển phía trên xem sao...
+
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+Traceback (most recent call last):
+ File "", line 1, in
+TypeError: unhashable type: 'dict' (Lỗi về kiểu: kiểu không thể hash được)
+
+# Lỗi trên xảy ra là điều dê hiểu do từ điển không được tran bị __hash__,
+# sử dụng các lớp bọc (wrapper classes) ta xây dựng phía trên thử xem.
+>>> dictionary = DictWithHash()
+>>> dictionary[1] = 'a'; dictionary[2] = 'b';
+>>> ordered_dict = OrderedDictWithHash()
+>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
+>>> another_ordered_dict = OrderedDictWithHash()
+>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
+>>> len({dictionary, ordered_dict, another_ordered_dict})
+1
+>>> len({ordered_dict, another_ordered_dict, dictionary}) # xáo trộn thứ tự cá phần tử trong set
+2
+```
+
+Cái quái gì đang xảy ra?
+
+#### 💡 Giải thích:
+
+- Lý do tại sao quy tắc so sánh bắc cầu không áp dụng được khi so sánh `dictionary`, `ordered_dict` và `another_ordered_dict` là do cách triển khai phương thức `__eq__` trong lớp `OrderedDict` . Xem thêm [tài liệu](https://docs.python.org/3/library/collections.html#ordereddict-objects)
+
+ > Các phép so sánh bằng giữa các đối tượng OrderedDict tuôn theo thư tự và được thực hiện như sau. Còn phép so sánh băng giữa cá đối tượng `OrderedDict` và các đối tượng ánh xạ khác (mapping objects) thì không tuân theo thứ tự như là các từ điển thông thường..
+
+- Lý do ở đây là các đối tượng `OrderedDict` được cho phép bị thay thế trực tiếp tại bất cứ vị trí nao một từ điển thông thường được sử dụng.
+- Vậy tại sao thay đổi thư tự của các từ điển lại ảnh hưởng tới đối tượng `set` được sinh ra? Câu trả lời là do thiếu sự so sánh nội đối tượng (intrasitive) . Do sets là các tập hợp không có thứ tự của các phần tử độc nhất, thứ tự các phần tử khi chèn vào không có nghĩa lý gì cả. Nhưng trong trường hợp này, nó lại có vấn đề. Hãy tìm hiểu thêm xem sao
+
+ ```py
+ >>> some_set = set()
+ >>> some_set.add(dictionary) # these are the mapping objects from the snippets above
+ >>> ordered_dict in some_set
+ True
+ >>> some_set.add(ordered_dict)
+ >>> len(some_set)
+ 1
+ >>> another_ordered_dict in some_set
+ True
+ >>> some_set.add(another_ordered_dict)
+ >>> len(some_set)
+ 1
+
+ >>> another_set = set()
+ >>> another_set.add(ordered_dict)
+ >>> another_ordered_dict in another_set
+ False
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ >>> dictionary in another_set
+ True
+ >>> another_set.add(another_ordered_dict)
+ >>> len(another_set)
+ 2
+ ```
+ Sự bất nhất ở đây `another_ordered_dict in another_set` cho kết quả là `False` bởi vì `ordered_dict` đã tồn tại trong `another_set` trước đó, `ordered_dict == another_ordered_dict` trở thành `False`.
+
+
+---
+
+### ▶ Cố thêm chút nữa... *
+
+```py
+def some_func():
+ try:
+ return 'from_try'
+ finally:
+ return 'from_finally'
+
+def another_func():
+ for _ in range(3):
+ try:
+ continue
+ finally:
+ print("Finally!")
+
+def one_more_func(): # A gotcha!
+ try:
+ for i in range(3):
+ try:
+ 1 / i
+ except ZeroDivisionError:
+ # Let's throw it here and handle it outside for loop
+ raise ZeroDivisionError("A trivial divide by zero error")
+ finally:
+ print("Iteration", i)
+ break
+ except ZeroDivisionError as e:
+ print("Zero division error occurred", e)
+```
+
+**Kết quả:**
+
+```py
+>>> some_func()
+'from_finally'
+
+>>> another_func()
+Finally!
+Finally!
+Finally!
+
+>>> 1 / 0
+Traceback (most recent call last):
+ File "", line 1, in
+ZeroDivisionError: division by zero (Ngoại lệ sinh ra bởi chia một số cho số 0)
+
+>>> one_more_func()
+Iteration 0
+
+```
+
+#### 💡 Giải thích:
+
+- Khi câu lệnh `return`, `break` hay `continue` được thực hiện trong phần `try` của khối "try…finally", phần `finally` cũng được thực hiện.
+- Giá trị trả về của một hàm được xác định bởi câu lệnh `return` cuối cung được thực hiện. Bởi vì phần `finally` luôn được thực hiện, câu lệnh `return` trong phần `finally` sẽ luôn là câu lệnh trả về gia trị cuối cùng.
+- Tuy nhiên nếu phần finally thực hiện câu lệnh `return` hay `break` thì phần ngoại lệ tồn tại sẽ bị bỏ qua.
+
+---
+
+
+### ▶ For what?
+
+```py
+some_string = "wtf"
+some_dict = {}
+for i, some_dict[i] in enumerate(some_string):
+ i = 10
+```
+
+**Kết quả:**
+```py
+>>> some_dict # An indexed dict appears.
+{0: 'w', 1: 't', 2: 'f'}
+```
+
+#### 💡 Giải thích:
+
+* Câu lệnh `for` được định nghĩa trong [ngữ pháp Python](https://docs.python.org/3/reference/grammar.html) như sau:
+ ```
+ for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
+ ```
+
+ Ở đó `exprlist` là biến mục tiêu của phép gán. Điều đó co nghĩa là câu lệnh gán `{exprlist} = {next_value}` được **thực hiện đối với mỗi phần tử** trong đối tượng lặp (iterable)
+ An interesting example that illustrates this:
+ ```py
+ for i in range(4):
+ print(i)
+ i = 10
+ ```
+
+ **Kết quả:**
+ ```
+ 0
+ 1
+ 2
+ 3
+ ```
+
+ Bạn có nghĩ răng vòng lặp trên chỉ chạy có một lần?
+
+ **💡 Giải thích:**
+
+- Câu lệnh gán `i = 10` không bao giờ ảnh hưởng tới các vòng lặp do cách thức hoạt động của vòng lặp for tron Python. Trước điểm khởi đầu của mỗi vòng lặp, phần tử tiếp theo được đưa ra bởi trình sinh (iterator, trong trường hợp nay là `range(4)`), phần tử này được giải nén ra (unpacked) và gán cho các biến chạy (trong trường hợp nay là `i`)
+
+* Hàm `enumerate(some_string)` sinh ra giá trị mới `i` (biến đếm) và một kí tự từ `some_string` tại mỗi lần lặp. Sau đó nó gán khoá `i` của từ điển `some_dict` cho kí tự đó. Trình tự được thể hiện đơn giản như dưới đây:
+ ```py
+ >>> i, some_dict[i] = (0, 'w')
+ >>> i, some_dict[i] = (1, 't')
+ >>> i, some_dict[i] = (2, 'f')
+ >>> some_dict
+ ```
+
+---
+
+### ▶ Sự khác biệt đến từ thời điểm đánh giá
+
+1\.
+```py
+array = [1, 8, 15]
+# Một biểu diễn generator thông thường
+gen = (x for x in array if array.count(x) > 0)
+array = [2, 8, 22]
+```
+
+**Kết quả:**
+
+```py
+>>> print(list(gen)) # Các giá trị khác đi đâu mất rồi?
+[8]
+```
+
+2\.
+
+```py
+array_1 = [1,2,3,4]
+gen_1 = (x for x in array_1)
+array_1 = [1,2,3,4,5]
+
+array_2 = [1,2,3,4]
+gen_2 = (x for x in array_2)
+array_2[:] = [1,2,3,4,5]
+```
+
+**Kết quả:**
+```py
+>>> print(list(gen_1))
+[1, 2, 3, 4]
+
+>>> print(list(gen_2))
+[1, 2, 3, 4, 5]
+```
+
+3\.
+
+```py
+array_3 = [1, 2, 3]
+array_4 = [10, 20, 30]
+gen = (i + j for i in array_3 for j in array_4)
+
+array_3 = [4, 5, 6]
+array_4 = [400, 500, 600]
+```
+
+**Kết quả:**
+```py
+>>> print(list(gen))
+[401, 501, 601, 402, 502, 602, 403, 503, 603]
+```
+
+#### 💡 Lý giải
+
+- Trong một biểu diễn [generator](https://wiki.python.org/moin/Generators), câu `in` được thực hiện tại thời điểm khai báo, nhưng câu điều kiện được thực hiện tại thời điểm chạy (runtime).
+- Do trước thời điểm chạy, `array` được gán cho giá trị `[2, 8, 22]`, và trong ba số được gán trước đó `1`, `8` and `15`, chỉ có `8` có số lần xuất hiện trong mảng mới và do đó số lần xuât hiện lớn hơn `0`, nên generator chỉ cho ra số `8`.
+- Sự khác biệt giữa kết quả của `g1` and `g2` trong phần thứ hai là do cách các biến `array_1` và `array_2` được gán lại các giá trị.
+- Trong trường hợp đầu tiên, `array_1` được gán cho một đối tượng mới `[1,2,3,4,5]` và vì câu `in` được thực hiện tại thời điểm khai báo nên nó vẫn trỏ tới đối tượng cũ `[1,2,3,4]` (đối tượng này chưa bị mất đi).
+- Trong trường hợp thư hai, phép gán lát cắt (slice assignment) `array_2` cập nhật đối tượng cũ `[1,2,3,4]` thành `[1,2,3,4,5]`. Hiển nhiên cả `g2` và `array_2` đều trỏ tới cung một đối tượng (đối tượng đã được cập nhật thành `[1,2,3,4,5]`).
+ - Okay, với những gì ta quan sát trên, co phải giá trị trả về từ `list(g)` trong phần thứ ba phải là `[11, 21, 31, 12, 22, 32, 13, 23, 33]`? (bởi vì `array_3` và `array_4` sẽ giống như `array_1`). Lý do chỉ `array_4` được cập nhật được giải thích ở đây [PEP-289](https://www.python.org/dev/peps/pep-0289/#the-details)
+ >Chỉ có vòng for ngoài cùng được thực hiện ngay lập tức, các lệnh khác được trì hoãn cho đến khi generator được chạy.
+---
+
+
+### ▶ `is not ...` không phải là `is (not ...)`
+
+
+```py
+>>> 'something' is not None
+True
+>>> 'something' is (not None)
+False
+```
+
+#### 💡 Giải thích
+
+- `is not` là một toán tử nhị phân đơn, và khi thực hiện sẽ cho kết quả khác với sử dụng `is` và `not` riêng biệt.
+- `is not` cho ra kết quả `False` khi các biến ở hai đầu của nó trỏ về cùng một đối tượng , và nếu hai biến này trỏ về khác đối tượng, kết quả sẽ là `True`. Cụ thể, `None` và `something` trỏ về hai biến khác nhau nên kết quả là `True`
+- Trong ví dụ trên, `(not None)` sẽ cho ra kết quả `True` bởi vì khi sử dụng trong phép so sánh luận lý `None` tương đương với `False`, do đó dòng mã trên sẽ trở thành `'something' is True`. Cụ thể, do `something` và `True` trỏ về hai đối tượng khác nhau nên kết quả là `False`
+
+---
+
+### ▶ A tic-tac-toe where X wins in the first attempt!
+
+
+```py
+# Khởi tạo một hàng
+row = [""] * 3 #row i['', '', '']
+# Và tạo một bảng gồm các hàng
+board = [row] * 3
+```
+
+**Kết quả:**
+
+```py
+>>> board
+[['', '', ''], ['', '', ''], ['', '', '']]
+>>> board[0]
+['', '', '']
+>>> board[0][0]
+''
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['X', '', ''], ['X', '', '']]
+```
+
+Chúng ta đã gán `"X"` cho ba vị trí trong bảng này chăng?
+
+#### 💡 Giải thích:
+
+Khi chúng ta khởi tạoi biến `row`, hình mô phỏng dưới đây cho ta biết những gì diễn ra trong bộ nhớ
+
+![image](../images/tic-tac-toe/after_row_initialized.png)
+
+
+Khi `board` được khởi tạo băng việc nhân bản `row`, minh hoạ phía dưới mô tả những gì diên ra trong bộ nhớ (mỗi thành phần của bảng `board[0]`, `board[1]` và `board[2]` là một tham chiếu tới cung một danh sách trỏ bởi `row`)
+
+![image](../images/tic-tac-toe/after_board_initialized.png)
+
+Chúng ta có thể tránh điều trên xảy ra bằng cách không dùng biến `row` để sinh ra `board`. (Tim hiểu thêm tại (https://github.com/satwikkansal/wtfpython/issues/68) )
+```py
+>>> board = [['']*3 for _ in range(3)]
+>>> board[0][0] = "X"
+>>> board
+[['X', '', ''], ['', '', ''], ['', '', '']]
+```
+
+---
+
+### ▶ The sticky output function
+
+
+1\.
+
+```py
+funcs = []
+results = []
+for x in range(7):
+ def some_func():
+ return x
+ funcs.append(some_func)
+ results.append(some_func()) # Chú ý lời gọi hàm ở đây
+
+funcs_results = [func() for func in funcs]
+```
+
+**kết quả:**
+
+```py
+>>> results
+[0, 1, 2, 3, 4, 5, 6]
+>>> funcs_results
+[6, 6, 6, 6, 6, 6, 6]
+```
+Ngay cả khi các giá trị `x` khác nhau trong mọi vòng lặp trước khi đặt `some_func` vào trong danh sách `funcs`, tất cả các hàm đều trả về 6.
+
+2\.
+
+```py
+>>> powers_of_x = [lambda x: x**i for i in range(10)]
+>>> [f(2) for f in powers_of_x]
+[512, 512, 512, 512, 512, 512, 512, 512, 512, 512]
+```
+
+#### 💡 Lý giải
+
+- Khi định nghia một hàm bên trong một vòng lặp, vòng lặp có biến lặp được sử dụng trong thân hàm, closure của hàm này được giơi hạn cho biến, chư không phải là giá trị. Vì vậy tất cả các ham sử dụng giá trị cuối cung được gán cho biến này để thực hiện tính toán. Để rõ hơn ta thấy được rằng biến lặp `x` (và giá trị của cung của nó nhận được là `6`) trong ví dụ thứ nhất được sử dụng cho tất cả các hàm `func()`, hàm này sẽ luôn trả về giá trị là `6`
+
+- Để thực hiện được tính toán mong muốn bạn có thể truyền biến lặp như là biến được đặt tên (named variable) cho hàm. **Sao mà nó lại chạy đúng được?** ởi vì việc truyền biến như vậy sẽ định nghĩa lại biến nay bên trong phạm vi của hàm.
+
+ ```py
+ funcs = []
+ for x in range(7):
+ def some_func(x=x):
+ return x
+ funcs.append(some_func)
+ ```
+
+ **Kết quả:**
+ ```py
+ >>> funcs_results = [func() for func in funcs]
+ >>> funcs_results
+ [0, 1, 2, 3, 4, 5, 6]
+ ```
+
+---
+
+### ▶ Bài toán con gà và quả trứng *
+
+1\.
+```py
+>>> isinstance(3, int)
+True
+>>> isinstance(type, object)
+True
+>>> isinstance(object, type)
+True
+```
+
+Đâu là lớp cơ bản cuối cùng ? Còn nhiều thứ gây khó hiểu hơn nữa sau đây
+
+2\.
+
+```py
+>>> class A: pass
+>>> isinstance(A, A)
+False
+>>> isinstance(type, type)
+True
+>>> isinstance(object, object)
+True
+```
+
+3\.
+
+```py
+>>> issubclass(int, object)
+True
+>>> issubclass(type, object)
+True
+>>> issubclass(object, type)
+False
+```
+
+
+#### 💡 Giải thích
+
+- `type` là một [metaclass](https://realpython.com/python-metaclasses/) trong Python.
+- **Mọi thứ** đều là một đối tượng `object` trong Python, bao gồm cả các lớp (classes) cũng như là các hiện thực của chúng (instances).
+- Lớp `type` là metaclass của lớp `object`, do đó mọi lớp (gồm cả `type`) thừa hưởng trực tiếp hay gián tiếp từ `object`.
+- Không có một lớp nào nằm giữa lớp `object` và `type`. Vấn đề đối với các đoạn mã phía trên nằm ở cách ta tìm các mối quan hệ giữa các lớp trong Python (dùng `issubclass` và `isinstance`). Mỗi quan hệ giữa `object` và `type` không thể được định nghĩa trong Python đơn thuần. Chính xác hơn, nghĩa là,
+ + Lớp A là một hiện thực của lớp B, và lớp B là một hiện thực của lớp A.
+ + Lớp A là một hiện thực của chính nó.
+- Các mối quan hệ giữa `object` và `type` (ở cả hai cấp độ hiện thực cũng như chính lớp đó) tồn tại là do việc "ăn gian" khi triển khai các lớp này.
+---
+
+### ▶ Các mối quan hệ của lơp con (subclass)
+
+
+**Kết quả:**
+```py
+>>> from collections import Hashable
+>>> issubclass(list, object)
+True
+>>> issubclass(object, Hashable)
+True
+>>> issubclass(list, Hashable)
+False
+```
+
+Mối quan hệ giữa các lớp con có tính bắc cầu không?(ví dụ, nếu `A` là lớp con của `B`, và `B` là lớp con của `C`, vậy `A` _nên_ là lớp con của `C`)
+
+#### 💡 Lý giải:
+
+* Trong Python mối quan hệ giữa cá lớp con không nhất thiết phải mang tính bắc cầu. Bất cứ ai cũng được phép định nghĩa một lớp `__subclasscheck__` riêng, tuỳ ý trong một siêu lớp (metaclass).
+* Khi `issubclass(cls, Hashable)` được gọi, nó chỉ tìm phương thức non-Falsey "`__hash__`" trong `cls` hoặc bất cư thư gì nó thừa hưởng từ đó.
+* Bởi vì `object` là một đối tượng có thể băm (hashable), còn `list` thi không, nên nó phá vỡ tính bắc cầu.
+* Giải thích chi tiết có thể xem [ở đây](https://www.naftaliharris.com/blog/python-subclass-intransitivity/).
+
+---
+
+### ▶ All-true-ation *
+
+
+
+```py
+>>> all([True, True, True])
+True
+>>> all([True, True, False])
+False
+
+>>> all([])
+True
+>>> all([[]])
+False
+>>> all([[[]]])
+True
+```
+
+Lý do vì sao mà lúc thì True mà lúc thì lại False
+
+#### 💡 Giải thích:
+
+- Hàm `all` tương đương như đoạn mã dưới
+
+- ```py
+ def all(iterable):
+ for element in iterable:
+ if not element:
+ return False
+ return True
+ ```
+
+- `all([])` trả về `True` bởi vì danh sách nay rỗng.
+- `all([[]])` trả về `False` bởi vì `not []` là `True` tương đương với `not False` bởi vì danh sách phía trong rỗng.
+- `all([[[]]])` và các biến thể đệ quy cao hơn luôn trả về `True` bởi vì `not [[]]`, `not [[[]]]`, tương đương với`not True`.
+---
+
+### ▶ Dấu phẩy lạ lùng
+
+**Kết quả (< 3.6):**
+
+```py
+>>> def f(x, y,):
+... print(x, y)
+...
+>>> def g(x=4, y=5,):
+... print(x, y)
+...
+>>> def h(x, **kwargs,):
+ File "", line 1
+ def h(x, **kwargs,):
+ ^
+SyntaxError: invalid syntax (Lỗi cú pháp không hợp lệ)
+
+>>> def h(*args,):
+ File "", line 1
+ def h(*args,):
+ ^
+SyntaxError: invalid syntax (Lỗi cú pháp không hợp lệ)
+```
+
+#### 💡 Lý giải:
+
+- Dấu phẩy nằm ở cuối danh sách các tham số của một hàm không phải bao giờ cũng hợp lệ.
+- Trong Python, danh sách tham số được định nghĩa bởi một phần các dấu phẩy nằm phía trước và một phần các dấy phẩy nằm phía sau. Điều nay mâu thuẫn với các tình huống ở đó một dấu phẩy bị mắc kẹt ở giữa danh sách, và chẳng có một luật nào chấp nhận điều này cả.
+- **Chú ý:** Vấn đề về dâu phẩy ở cuối [đã được sửa trong Python 3.6](https://bugs.python.org/issue9232). Thảo luận [ở đây](https://bugs.python.org/issue9232#msg248399) cung cấp những cách dung khác nhau của dây phẩy ở cuối.
+
+---
+
+### ▶ Strings and the backslashes
+
+**Kết quả:**
+```py
+>>> print("\"")
+"
+
+>>> print(r"\"")
+\"
+
+>>> print(r"\")
+File "", line 1
+ print(r"\")
+ ^
+SyntaxError: EOL while scanning string literal
+
+>>> r'\'' == "\\'"
+True
+```
+
+#### 💡 Giải thích
+
+- Trong một string thông thường, dấu xuyệc ngược được dùng để "escape" các kí tự có một ý nghĩa đặc biệt (như là dấu trích dẫn đơn, dấu trích dẫn kép, va chính dấu xuyệc ngược).
+ ```py
+ >>> "wt\"f"
+ 'wt"f'
+ ```
+- Đối với một string thô (raw string) (được chỉ định bởi tiếp đầu ngữ `r`), dấu xuyệc ngược.
+ ```py
+ >>> r'wt\"f' == 'wt\\"f'
+ True
+ >>> print(repr(r'wt\"f')
+ 'wt\\"f'
+
+ >>> print("\n")
+
+ >>> print(r"\\n")
+ '\\\\n'
+ ```
+- Có nghĩa là khi một trình phân tích cú pháp gặp một dấu xuyệc ngược tron một string thô, trình này mong đợi một kí tự khác phía sau nó. Và tron trường hợp của chúng ta `print(r"\")`, dấu xuyệc ngược vô hiệu hoá (escape) dấu trích dẫn ở đuôi, để lại cho trình thông dịch xử lý mà không có dấu trích dẫn kết thúc (do đó gây ra lỗi về cú pháp `SyntaxError`). Đó là lý do tại sao các dấu xuyệc ngược không chạy được khi ở cuối một string thô.
+
+---
+
+### ▶ not knot!
+
+```py
+x = True
+y = False
+```
+
+**Kết quả:**
+```py
+>>> not x == y
+True
+>>> x == not y
+ File "", line 1
+ x == not y
+ ^
+SyntaxError: invalid syntax (Lỗi về cú pháp)
+```
+
+#### 💡 Giải thích:
+
+* Thứ tự ưu tiên của các phép toán ảnh hưởng tơi cách một câu lệnh được thực thi, và phép `==` có độ ưu tiên cao hơn phép `not` trong Python.
+* Vì vậy `not x == y` tương đương với `not (x == y)` rồi tương đương với `not (True == False)` và kết quả là`True`.
+* Nhưng `x == not y` gây ra lỗi cú pháp `SyntaxError` bởi vì Python nghĩ rằng nó sẽ thực hiện `(x == not) y` chứ không phải là `x == (not y)` do đó bạn mới gặp lỗi về cú pháp.
+* Trình phân tích cú pháp mong đợi `not` là một phần của phép toán `not in` (bởi vì cả `==` và `not in` có cùng độ ưu tiên), nhưng do trong trương hợp của chúng ta, trình xử lý không tìm thấy `in` đằng sau `not`, nên nó gây ra `SyntaxError`.
+
+---
+
+### ▶ Các chuỗi rưỡi trích dẫn
+
+**Kết quả:**
+```py
+>>> print('wtfpython''')
+wtfpython
+>>> print("wtfpython""")
+wtfpython
+>>> # Các câu lệnh phía dưới gặp lỗi cú pháp `SyntaxError` khi thực thi
+>>> # print('''wtfpython')
+>>> # print("""wtfpython")
+ File "", line 3
+ print("""wtfpython")
+ ^
+SyntaxError: EOF while scanning triple-quoted string literal (Lỗi cú pháp khi sử lý chuỗi được bao bởi dấu trích dẫn)
+```
+
+#### 💡 Giải thích:
++ Python hỗ trợ ngầm [ghép chuỗi](https://docs.python.org/2/reference/lexical_analysis.html#string-literal-concatenation), ví dụ,
+ ```
+ >>> print("wtf" "python")
+ wtfpython
+ >>> print("wtf" "") # or "wtf"""
+ wtf
+ ```
+
++ `'''` và `"""` là các kí hiệu phân cách chuỗi trong Python, chúng tạo ra lỗi SyntaxError bởi vì trình thông dịch Python chơ đợi kí hiệu phân cách kết thúc khi nó dò tìm chuỗi kí tự trích dẫn có ba dấu nháy .
+---
+
+### ▶ Có gì sai sai với các giá trị luận lý (booleans)?
+
+1\.
+
+```py
+# Một ví dụ tính toán số lượng các giá trị luận lý và
+# sô nguyên trong một danh sách lẫn lộn các kiểu phần tử khác nhau.
+mixed_list = [False, 1.0, "some_string", 3, True, [], False]
+integers_found_so_far = 0
+booleans_found_so_far = 0
+
+for item in mixed_list:
+ if isinstance(item, int):
+ integers_found_so_far += 1
+ elif isinstance(item, bool):
+ booleans_found_so_far += 1
+```
+
+**Kết quả:**
+```py
+>>> integers_found_so_far
+4
+>>> booleans_found_so_far
+0
+```
+
+
+2\.
+```py
+>>> some_bool = True
+>>> "wtf" * some_bool
+'wtf'
+>>> some_bool = False
+>>> "wtf" * some_bool
+''
+```
+
+3\.
+
+```py
+def tell_truth():
+ True = False
+ if True == False:
+ print("I have lost faith in truth!")
+```
+
+**Kết quả (< 3.x):**
+
+```py
+>>> tell_truth()
+I have lost faith in truth!
+```
+
+#### 💡 Lý giải:
+
+* `bool` là lơp con của `int` trong Python
+
+ ```py
+ >>> issubclass(bool, int)
+ True
+ >>> issubclass(int, bool)
+ False
+ ```
+
+* Và do vậy, `True` và `False` là các hiện thực (instances) của `int`
+ ```py
+ >>> isinstance(True, int)
+ True
+ >>> isinstance(False, int)
+ True
+ ```
+
+* Giá trị dạng số nguyên của `True` là `1` và của `False` là `0`.
+ ```py
+ >>> int(True)
+ 1
+ >>> int(False)
+ 0
+ ```
+
+* Xem trên [câu trả lời] trên StackOverflow(https://stackoverflow.com/a/8169049/4354153) để biết lý do.
+
+* Lúc đầu, Python không có kiểu `bool` (người ta dung 0 cho false và các giá trị khác không như 1 cho true). `True`, `False`, va kiểu `bool` được bổ sung trong cá phiên bản 2.x, nhưng vi lý do hỗ trợ tương thích ngược (backward compatibility), `True` và `False` không thể trơ thành các hăng số (constants). Chúng chỉ là các biến được tích hợp sẵn trong Python, và ta có thể gán lại giá trị cho chúng.
+
+* Python 3 không hỗ trợ tương thích ngược, và do vậy các đoạn mã cuối cung không chạy được trên Python 3.x!
+---
+
+### ▶ Class attributes and instance attributes
+
+1\.
+```py
+class A:
+ x = 1
+
+class B(A):
+ pass
+
+class C(A):
+ pass
+```
+
+**Kết quả:**
+```py
+>>> A.x, B.x, C.x
+(1, 1, 1)
+>>> B.x = 2
+>>> A.x, B.x, C.x
+(1, 2, 1)
+>>> A.x = 3
+>>> A.x, B.x, C.x # C.x thay đổi, nhưng B.x không thay đổi
+(3, 2, 3)
+>>> a = A()
+>>> a.x, A.x
+(3, 3)
+>>> a.x += 1
+>>> a.x, A.x
+(4, 3)
+```
+
+2\.
+```py
+class SomeClass:
+ some_var = 15
+ some_list = [5]
+ another_list = [5]
+ def __init__(self, x):
+ self.some_var = x + 1
+ self.some_list = self.some_list + [x]
+ self.another_list += [x]
+```
+
+**Kết quả:**
+
+```py
+>>> some_obj = SomeClass(420)
+>>> some_obj.some_list
+[5, 420]
+>>> some_obj.another_list
+[5, 420]
+>>> another_obj = SomeClass(111)
+>>> another_obj.some_list
+[5, 111]
+>>> another_obj.another_list
+[5, 420, 111]
+>>> another_obj.another_list is SomeClass.another_list
+True
+>>> another_obj.another_list is some_obj.another_list
+True
+```
+
+#### 💡 Giải thích:
+
+* Các biến thuộc về lớp và các biến thuộc về các hiện thực của lơp (class instances) được xử lý nội tại như là các từ điển của một đối tượng lớp. Nếu một biến không nằm trong từ điển của lớp hiện hành, nó sẽ được tìm trong các lớp cha.
+* Phép `+=` thay đổi đối tượng có thể biến đổi (mutable) tại chỗ mà khôn cần phải tạo một đối tượng mới. Vì vậy thay đổi thuộc tính cua một hiện thực ảnh hưởng tới thuộc tính của các hiện thực khác và thuộc tính lớp.
+---
+
+### ▶ Non-reflexive class method *
+
+
+
+```py
+class SomeClass:
+ def instance_method(self):
+ pass
+
+ @classmethod
+ def class_method(cls):
+ pass
+```
+
+**Kết quả:**
+
+```py
+>>> SomeClass.instance_method is SomeClass.instance_method
+True
+>>> SomeClass.class_method is SomeClass.class_method
+False
+>>> id(SomeClass.class_method) == id(SomeClass.class_method)
+True
+```
+
+#### 💡 Giải thích:
+
+- Lý do là vì `SomeClass.class_method is SomeClass.class_method` trả về `False` là bởi vì decorator `@classmethod`.
+
+ ```py
+ >>> SomeClass.instance_method
+
+ >>> SomeClass.class_method
+
+ ```
+
+ Mỗi lần truy cập tới `SomeClass.class_method` là lại có một phương thức được bọc mới sinh ra (new bound method)
+- `id(SomeClass.class_method) == id(SomeClass.class_method)` trả về `True` bởi vì quá trình cấp phát bộ nhớ cho `class_method` lần thứ hai xảy ra tại cung một ví trí với quá trình giải phóng bộ nhớ đầu tiên (Xem lại "Deep Down, we're all the same example" để có lời giải thích rõ hơn).
+---
+
+
+### ▶ yielding None
+
+```py
+some_iterable = ('a', 'b')
+
+def some_func(val):
+ return "something"
+```
+
+**Kết quả (<= 3.7.x):**
+
+```py
+>>> [x for x in some_iterable]
+['a', 'b']
+>>> [(yield x) for x in some_iterable]
+ at 0x7f70b0a4ad58>
+>>> list([(yield x) for x in some_iterable])
+['a', 'b']
+>>> list((yield x) for x in some_iterable)
+['a', None, 'b', None]
+>>> list(some_func((yield x)) for x in some_iterable)
+['a', 'something', 'b', 'something']
+```
+
+#### 💡 Giải thích:
+- Đây là một bug tồn tại khi CPython xử lý `yield` trong các generators và comprehensions.
+- Bạn có thể tham khảo thêm về lỗi này tại đây: https://stackoverflow.com/questions/32139885/yield-in-list-comprehensions-and-generator-expressions
+- Báo cáo về bug này: http://bugs.python.org/issue10544
+- Theo Python 3.8+ `yield` không được phép nằm bên trong list comprehension và nếu bạn làm như vậy sẽ tạo ra lỗi cú pháp `SyntaxError`.
+
+---
+
+
+### ▶ Yielding from... return! *
+
+1\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ yield from range(x)
+```
+
+**Kết quả (> 3.3):**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+Đáng lẽ phải hiển thị `"wtf"` chứ nhỉ? Có phải là do `yield from`? Cùng tìm hiểu thêm nào,
+
+2\.
+
+```py
+def some_func(x):
+ if x == 3:
+ return ["wtf"]
+ else:
+ for i in range(x):
+ yield i
+```
+
+**Kết quả:**
+
+```py
+>>> list(some_func(3))
+[]
+```
+
+Vẫn lại không in ra `"wtf"` .
+
+#### 💡 Giải thích:
+
++ Từ Python 3.3 trở đi, ta có thể sử dụng `return` với các giá trị bên trong các generators (Xem thêm [PEP380](https://www.python.org/dev/peps/pep-0380/)). Các [tài liệu chính thức](https://www.python.org/dev/peps/pep-0380/#enhancements-to-stopiteration) cũng nói như vậy,
+> "... `return expr` tron một generator tạo ra ngoại lệ `StopIteration(expr)` khi thoát ra từ generator."
+
++ Trong trường hợp `some_func(3)`, ngoại lệ `StopIteration` được khởi lên ngay từ đầu bởi vì câu lệnh `return`. Ngoại lệ `StopIteration` được tự động bắt lại trong dòng lệnh bao `list(...)` và trong vòng lặp `for`. Do đó, cả hai đoạn mã trên đều trả về một danh sách rỗng.
+
++ Để có được `["wtf"]` từ generator `some_func` bạn cần bắt ngoại lệ `StopIteration`,
+
+ ```py
+ try:
+ next(some_func(3))
+ except StopIteration as e:
+ some_string = e.value
+ ```
+
+ ```py
+ >>> some_string
+ ["wtf"]
+ ```
+
+---
+
+### ▶ Tính phản xạ của Nan *
+
+
+1\.
+
+```py
+a = float('inf')
+b = float('nan')
+c = float('-iNf') # Các strings này không phân biệt hoa hay thường
+d = float('nan')
+```
+
+**Kết quả:**
+
+```py
+>>> a
+inf
+>>> b
+nan
+>>> c
+-inf
+>>> float('some_other_string')
+ValueError: could not convert string to float: some_other_string (Lỗi giá trị: Không thể chuyển đổi từ string sang float)
+>>> a == -c # inf==inf
+True
+>>> None == None # None == None
+True
+>>> b == d # nhưng nan!=nan
+False
+>>> 50 / a
+0.0
+>>> a / a
+nan
+>>> 23 + b
+nan
+```
+
+2\.
+
+```py
+>>> x = float('nan')
+>>> y = x / x
+>>> y is y # định danh giống nhau (cùng trỏ về một đối tượng)
+True
+>>> y == y # Gía trị lại không bằng nhau
+False
+>>> [y] == [y] # Giá trị bằng nhau khi nằm ở trong một list
+True
+```
+
+
+
+#### 💡 Giải thích:
+
+- `'inf'` và `'nan'` là các strings đặc biệt (không phân biệt hoa thường), khi được chuyển đổi sang kiểu `float` sẽ tương ứng biểu diễn cho giá trị vô hạn "infinity" hay không phải là một số "not a number".
+
+- Theo chuẩn của IEEE thì ` NaN != NaN`, tuân theo quy tắc trên sẽ phá vỡ giả định về tính tương phản của một phần tử trong một tập trong Python ví dụ. nếu `x` là một phần tử của một tập `list`, các phép toán trên các phần tử của tập như là so sánh sẽ dựa trên giải định rằng `x == x`. Do giả định này nên định danh được so sánh đầu tiên (do nhanh hơn) khi so sánh hai phần tử, và các giá trị được so sánh chỉ khi các định danh không khớp. Ví dụ sau sẽ giúp bạn dễ hiểu hơn
+ ```py
+ >>> x = float('nan')
+ >>> x == x, [x] == [x]
+ (False, True)
+ >>> y = float('nan')
+ >>> y == y, [y] == [y]
+ (False, True)
+ >>> x == y, [x] == [y]
+ (False, False)
+ ```
+
+ Do các định danh của `x` và `y` khác nhau, do đó giá trị của chúng sẽ được so sánh, mà giá trị của chúng khác nhau trong ví dụ này; nên kết quả trả về là `False`. Cụ thể hơn, theo tiêu chuẩn của IEEE thì `x` f va `y` đểu có giá trị là `nan` khi được chuyển đổi qua float, ` NaN != NaN` nên `x != y`, nhưng khi đặt trong một list thì định danh sẽ đc so sánh trước nên `[x] == [y]`
+
+- Đọc thêm: [Reflexivity, and other pillars of civilization](https://bertrandmeyer.com/2010/02/06/reflexivity-and-other-pillars-of-civilization/)
+
+---
+
+### ▶ Mutating the immutable!
+
+
+
+Ví dụ dưới đây có vẻ tầm thường nếu bạn hiểu cách các tham chiếu (references) hoạt đông trong Python.
+```py
+some_tuple = ("A", "tuple", "with", "values")
+another_tuple = ([1, 2], [3, 4], [5, 6])
+```
+
+**Kết quả:**
+```py
+>>> some_tuple[2] = "change this"
+TypeError: 'tuple' object does not support item assignment (Lỗi về kiểu: đối tượng 'tuple' không hỗ trợ phép gán phần tử)
+>>> another_tuple[2].append(1000) # Dòng này không bị lỗi
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000])
+>>> another_tuple[2] += [99, 999]
+TypeError: 'tuple' object does not support item assignment (Lỗi về kiểu: đối tượng 'tuple' không hỗ trợ phép gán phần tử)
+>>> another_tuple
+([1, 2], [3, 4], [5, 6, 1000, 99, 999])
+```
+
+Tôi đã nghĩ rằng các tuples thì bất biến (immutable) ...
+#### 💡 Giải thích:
+
+* Trích từ https://docs.python.org/2/reference/datamodel.html
+
+ > Các chuỗi bất biến
+ Một đối tượng kiểu chuỗi bất biến không thể thay đổi giá trị của nó sau khi được tạo. (Nếu đối tượng này chứa các tham chiếu tới các đối tượng khác, những đối tượng có thể thay đổi được; tuy thế, tập hợp các đối tượng được trỏ trực tiếp bởi một đối tượng bất biến không thể thay đổi .)
+
+* Toán tử `+=` thay đổi list tại chỗ. Phép gán phần tử không thực hiện được, nhưng khi ngoại lệ xảy ra, phần tử đã được thay đổi ngay tại chỗ.
+
+---
+
+### ▶ The disappearing variable from outer scope
+
+
+```py
+e = 7
+try:
+ raise Exception()
+except Exception as e:
+ pass
+```
+
+**Kết quả (Python 2.x):**
+```py
+>>> print(e)
+# Không in ra kết quả nào cả
+```
+
+**Kết quả (Python 3.x):**
+```py
+>>> print(e)
+NameError: name 'e' is not defined (Lỗi tên biến không được định nghĩa)
+```
+
+#### 💡 Giải thích:
+
+* Source: https://docs.python.org/3/reference/compound_stmts.html#except
+
+ Khi một ngoại lệ được gán sử dụng đích được định nghia bởi `as`, ngoại lệ này được dọn dẹp ở cuối câu `except`. Như vậy thì
+
+ ```py
+ except E as N:
+ foo
+ ```
+
+ sẽ được hiểu như sau
+
+ ```py
+ except E as N:
+ try:
+ foo
+ finally:
+ del N
+ ```
+
+
+ Điều này có nghĩa la ngoại lệ phải được gán cho một cái tên khác để ta có thể tìm thấy nó sau câu except. Các ngoại lệ được dọn dẹp bởi vì với truy lỗi (traceback) được gán cho chúng, chúng hình thành nên một vòng tham chiếu bên trong khung ngăn xếp (stack frame), giữ tất cả các biến cục bộ không bị xoá đi cho đến khi việc thu thập rác tiếp theo tiếp diễn.
+
+* Các câu này không được tính phạm vi trong Python. Mọi thư trong ví dụ nay đều nàm trong cùng một phạm vi, và biến `e` bị xoá đi bởi vì ngoại lệ `except`. Điều tương tự như vậy không đúng với các hàm có các nội phạm vi (inner-scopes) riêng biệt. Ví dụ sau sẽ cho ta thấy:
+
+ ```py
+ def f(x):
+ del(x)
+ print(x)
+
+ x = 5
+ y = [5, 4, 3]
+ ```
+
+ **Kết quả:**
+ ```py
+ >>>f(x)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>>f(y)
+ UnboundLocalError: local variable 'x' referenced before assignment
+ >>> x
+ 5
+ >>> y
+ [5, 4, 3]
+ ```
+
+* Trong Python 2.x, biến `e` được gán cho một hiện thực `Exception()`, khi bạn in ra biến này, nó không hiển thị gì cả
+
+ **Kết quả (Python 2.x):**
+ ```py
+ >>> e
+ Exception()
+ >>> print e
+ # Không in ra gì cả!
+ ```
+
+---
+
+
+### ▶ The mysterious key type conversion
+
+```py
+class SomeClass(str):
+ pass
+
+some_dict = {'s': 42}
+```
+
+**Kết quả:**
+```py
+>>> type(list(some_dict.keys())[0])
+str
+>>> s = SomeClass('s')
+>>> some_dict[s] = 40
+>>> some_dict # Giá trị mong đợi: Hai cặp khoá và giá trị khác nhau
+{'s': 40}
+>>> type(list(some_dict.keys())[0])
+str
+```
+
+#### 💡 Giải thích:
+
+* Cả đối tượng `s` và string `"s"` khi được băm (hash) sẽ cho ra cùng một giá trị ởi `SomeClass` thừa kế phương thức `__hash__` của lớp `str`.
+* `SomeClass("s") == "s"` bằng `True` bởi vì `SomeClass` cũng thừa kế phương thức `__eq__` từ lớp `str` .
+* Bởi vì cả hai đối tượng này khi băm cho ra cùng giá trị và bằng nhau nên chúng được biểu diễn bởi cùng một khoá trong từ điển.
+* Để có được kết quả như mong muốn với hai cặp giá khoá và giá trị khác nhau, chúng ta có thể định nghĩa phương thức `__eq__` trong `SomeClass`
+
+ ```py
+ class SomeClass(str):
+ def __eq__(self, other):
+ return (
+ type(self) is SomeClass
+ and type(other) is SomeClass
+ and super().__eq__(other)
+ )
+
+ # Khi chúng ta ta định nghĩa một phương thức __eq__ tuỳ chỉnh, Python tự động dừng việc thừa kế
+ # và chúng ta cũng cần định nghĩa phươn thức __hash__ ,
+ __hash__ = str.__hash__
+
+ some_dict = {'s':42}
+ ```
+
+ **Kết quả:**
+ ```py
+ >>> s = SomeClass('s')
+ >>> some_dict[s] = 40
+ >>> some_dict
+ {'s': 40, 's': 42}
+ >>> keys = list(some_dict.keys())
+ >>> type(keys[0]), type(keys[1])
+ (__main__.SomeClass, str)
+ ```
+
+---
+
+### ▶ Let's see if you can guess this?
+
+```py
+a, b = a[b] = {}, 5
+```
+
+**Kết quả:**
+```py
+>>> a
+{5: ({...}, 5)}
+```
+
+#### 💡 Giải thích:
+
+* Dựa trên [nguồn tham khảo ngôn ngữ Python](https://docs.python.org/2/reference/simple_stmts.html#assignment-statements), các câu lệnh gán sẽ có cách khai báo như sau
+ ```
+ (target_list "=")+ (expression_list | yield_expression)
+ ```
+ và
+Một câu lệnh gán đánh giá một danh sách biểu diễn(nhớ rằng đây có thể là một biểu diễn đơn lẻ hoặc một danh sách các biểu diễn được phân cách bởi dấu phẩy, đối với trường hợp phía sau thì sainh ra một tuple) và gán đối tượng kết quả đơn cho mỗi một danh sách mục tiêu, từ trái sang phải.
+
+
+* Toán tử `+` trong `(target_list "=")+` có nghĩa là có thể có **một hay nhiều** danh sách mục tiêu (target lists). Trong trường hợp này, các danh sách mục tiêu là `a, b` và `a[b]` (chú ý là danh sách biểu diễn chỉ có chính xác một, trong trường hợp của chúng ta là `{}, 5`)
+
+
+* Sau khi biểu diễn danh sách được đánh giá, giá trị của nó được giải nén thành các danh sách được giải nén thành các danh sách mục tiêu từ **trái sang phải**. Do đó, trong trường hợp của chúng ta, đầu tiên tuple `{}, 5` được giải nén ra `a, b` và chúng ta có `a = {}` và `b = 5`.
+
+* `a` được gán cho `{}`, đây là một đối tượng có thể thay đổi được giá trị (mutable object).
+
+* Danh sách mục tiêu thứ hai là `a[b]` (bạn có thể có thể nghĩ ta sẽ gặp một lỗi bởi vì cả `a` và `b` đều chưa được định nghĩa trong các câu lệnh trước đó. Nhưng nhớ rằng, chúng ta đã gán `a` bằng `{}` và `b` bằng `5`).
+
+* Bây giờ, chúng ta thiết lập khoá `5` trong từ điển với giá trị là tuple `({}, 5)` tạo ra một tham chiếu vòng tròn (`{...}` trong kết quả trỏ tới cùng một đối tượng mà `a` đã đang tham chiếu). Một ví dụ đơn giản hơn về tham chiếu vòng tròn là
+ ```py
+ >>> some_list = some_list[0] = [0]
+ >>> some_list
+ [[...]]
+ >>> some_list[0]
+ [[...]]
+ >>> some_list is some_list[0]
+ True
+ >>> some_list[0][0][0][0][0][0] == some_list
+ True
+ ```
+ Tương tự là trong ví dụ của chúng ta (`a[b][0]` là cùng đối tượng với `a`)
+
+* Tổng kết lại, bạn có thể chia ví dụ này ra
+ ```py
+ a, b = {}, 5
+ a[b] = a, b
+ ```
+ Và tham chiếu vòng có thể được biện hộ bởi một điều là `a[b][0]` là đối tượng giống `a`
+ ```py
+ >>> a[b][0] is a
+ True
+ ```
+
+---
+---
+
+## Section: Slippery Slopes
+
+### ▶ Modifying a dictionary while iterating over it
+
+```py
+x = {0: None}
+
+for i in x:
+ del x[i]
+ x[i+1] = None
+ print(i)
+```
+
+**Kết quả (Áp dụng cho các phiên bản Python 2.7- Python 3.5):**
+
+```
+0
+1
+2
+3
+4
+5
+6
+7
+```
+
+Vòng lặp chạy đúng *tám* lần rồi dừng lại
+
+#### 💡 Giải thích:
+
+* Việc lặp và chỉnh sửa một từ điển cùng một lúc không được hỗ trợ.
+* Có tám lần chạy bởi vì tại thời điểm dừng đó là lúc từ điển điều chỉnh kích thước để giữ thêm nhiều khoá hơn (chúng ta có tám phần từ xoá, do đó điều chỉnh kích thước là cần thiết). Đây thực sự là một chi tiết trong triển khai.
+* Cách các khoá bị xoá được xử lý và khi nao thì việc điều chỉnh kích thước diễn ra sẽ khác biệt đối với các phiên bản Python khác nhau
+* Do đó các phiên bản Python lớn hơn 2.7 và nhỏ hơn 3.5, số lượng cá khoá có thể giữ dao động từ 8 (nhưng khi bạn chạy thì con số này sẽ không thay đổi giữa các lần chạy). Bạn có thể xem thêm các thảo luận khác ở [đây](https://github.com/satwikkansal/wtfpython/issues/53) hoặc trên Stackoverflow tại [đây](https://stackoverflow.com/questions/44763802/bug-in-python-dict)
+* Từ phiên bản 3.7.6 trở đi, bạn sẽ gặp ngoại lệ `RuntimeError: dictionary keys changed during iteration` khi bạn cố gắng thử ví dụ trên.
+---
+
+### ▶ Stubborn `del` operation
+
+
+
+```py
+class SomeClass:
+ def __del__(self):
+ print("Deleted!")
+```
+
+**Kết quả:**
+1\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x # this should print "Deleted!"
+>>> del y
+Deleted!
+```
+
+
+Có thể bạn đoán được làm sao mà `__del__` không được gọi khi ta cố gắng xoá `x` trong lần đầu tiên. Hãy thử thêm
+2\.
+```py
+>>> x = SomeClass()
+>>> y = x
+>>> del x
+>>> y # check if y exists Kiểm tra y có tồn tại
+<__main__.SomeClass instance at 0x7f98a1a67fc8>
+>>> del y # Như lần trước, đáng lẽ kết quả nên là in ra "Deleted!"
+>>> globals() # nhưng không, ta không có được kết quả như mong muốn. Cùng kiểm tra tất cả các biến toàn cục và xác nhận
+Deleted!
+{'__builtins__': , 'SomeClass': , '__package__': None, '__name__': '__main__', '__doc__': None}
+```
+
+Ok giờ thì nó đã được xoá :confused:
+
+#### 💡 Giải thích:
++ `del x` không gọi trực tiếp `x.__del__()`.
++ Khi `del x` được chạy, Python xoá tên `x` khỏi phạm vi hiện hành và giảm biến đếm tham chiếu của đối tượng đi 1. `__del__()` được gọi chỉ khi biến đếm này giảm xuống 0.
++ Tron đoạn mã thứ hai, `__del__()` không được gọi bởi vì câu lệnh trước đó (`>>> y`) tạo một tham chiếu khác tới cùng một đối tượng (cụ thể là, biến `_` trỏ tới giá trị kết quả của một biểu diễn không phải `None` tron REPL), do đó ngăn biến đếm tham chiếu giảm về 0 khi thực hiện `del y`
++ Gọi `globals` (thực ra thì thực hiện bất cứ thứ gì tạo ra kết quả không phải là `None`) làm cho `_` tham chiếu tới kết quả mới, bỏ đi tham chiếu hiện tồn tại. Bây giờ thì biến đếm tham chiếu giảm ve 0 và bạn có thể từ "Deleted!" được hiển thị.
+---
+
+### ▶ The out of scope variable
+
+```py
+a = 1
+def some_func():
+ return a
+
+def another_func():
+ a += 1
+ return a
+```
+
+**Kết quả:**
+```py
+>>> some_func()
+1
+>>> another_func()
+UnboundLocalError: local variable 'a' referenced before assignment (tham chiếu tới 'a' trước khi gán giá trị)
+```
+
+#### 💡 Giải thích:
+* Khi bạn gán giá trị cho một biến trong một phạm vi, thì biến này trở thành cục bộ cho phạm vi đó. Do đó `a` trở thành cục bộ đối với `another_func`, nhưng nó chưa được khởi tạo trong cùng phạm vi, nên ta gặp lỗi về tham chiếu phía trên.
+* Đọc [bài này](http://sebastianraschka.com/Articles/2014_python_scope_and_namespaces.html) để học về cách hoạt động của các không gian tên (namespaces) và việc phân giải phạm vi trong Python.
+* Để thay đổi biên bên ngoài phạm vi `a` bên trong hàm `another_func`, sử dụng từ khoá `global`
+ ```py
+ def another_func()
+ global a
+ a += 1
+ return a
+ ```
+
+ **Kết quả:**
+ ```py
+ >>> another_func()
+ 2
+ ```
+
+---
+
+### ▶ Deleting a list item while iterating
+
+```py
+list_1 = [1, 2, 3, 4]
+list_2 = [1, 2, 3, 4]
+list_3 = [1, 2, 3, 4]
+list_4 = [1, 2, 3, 4]
+
+for idx, item in enumerate(list_1):
+ del item
+
+for idx, item in enumerate(list_2):
+ list_2.remove(item)
+
+for idx, item in enumerate(list_3[:]):
+ list_3.remove(item)
+
+for idx, item in enumerate(list_4):
+ list_4.pop(idx)
+```
+
+**Kết quả:**
+```py
+>>> list_1
+[1, 2, 3, 4]
+>>> list_2
+[2, 4]
+>>> list_3
+[]
+>>> list_4
+[2, 4]
+```
+
+Vì sao lại có kết quả là `[2, 4]`?
+#### 💡 Giải thích:
+
+* Thay đổi đối tượng trong khi lặp không phải là một cách làm hay. Cách thực hiện đúng là lặp qua một bản sao của chính đối tượng đó như trường hợp sử dụng `list_3[:]`
+
+ ```py
+ >>> some_list = [1, 2, 3, 4]
+ >>> id(some_list)
+ 139798789457608
+ >>> id(some_list[:]) # Chú ý rằng Python tạo một đối tượng mới sinh ra từ việc cắt (slice) list.
+ 139798779601192
+ ```
+
+**Sự khác biệt giữa `del`, `remove`, và `pop`:**
+* `del var_name` chỉ loại bỏ sự có măt của `var_name` khỏi khô gian tên cục bộ và toàn cục (đó la lý do tại sao `list_1` không bị ảnh hưởng )
+* `remove` loại bỏ giá trị được khớp đầu tiên, không phải tại một chỉ số cụ thể, ngoại lệ `ValueError` sẽ xảy ra nếu giá trị muốn loại bỏ không tồn tại.
+* `pop` loại bỏ phần tử của danh sách ở mỗi chỉ số cụ thể và trả về phần từ đó, `pop` sẽ khởi lên `IndexError` nếu nó nhận một chỉ số không hợp lệ
+
+**Tại sao kết quả lại ra `[2, 4]`?**
+- Việc lặp qua danh sách được thực hiện theo từng chỉ số một, và khi chúng ta loại bỏ `1` khỏi `list_2` hay `list_4`, các danh sách này sẽ còn lại `[2, 3, 4]`. Các phần tử còn lại trong list được dịch chuyển sang trái, ví dụ như `2` sẽ có chỉ số là 0, và `3` có chỉ số là 1. Do lần lặp tiếp theo sẽ xử lý phần tử có chỉ số là 1 (`3`), nên phần tử tại chỉ số 0 (`2`) sẽ bị bỏ qua.
+
+* Xem thêm tại StackOverflow [thread](https://stackoverflow.com/questions/45946228/what-happens-when-you-try-to-delete-a-list-element-while-iterating-over-it) explaining the example
+* Và xem thêm ở đây để có các ví dụ liên quan tới các từ điển [thread](https://stackoverflow.com/questions/45877614/how-to-change-all-the-dictionary-keys-in-a-for-loop-with-d-items).
+
+---
+
+
+### ▶ Lossy zip of iterators *
+
+
+```py
+>>> numbers = list(range(7))
+>>> numbers
+[0, 1, 2, 3, 4, 5, 6]
+>>> first_three, remaining = numbers[:3], numbers[3:]
+>>> first_three, remaining
+([0, 1, 2], [3, 4, 5, 6])
+>>> numbers_iter = iter(numbers)
+>>> list(zip(numbers_iter, first_three))
+[(0, 0), (1, 1), (2, 2)]
+# so far so good, let's zip the remaining
+>>> list(zip(numbers_iter, remaining))
+[(4, 3), (5, 4), (6, 5)]
+```
+Phần tử `3` trong `numbers` đâu?
+#### 💡 Giải thích:
+
+- Theo tài liệu của Python [docs](https://docs.python.org/3.3/library/functions.html#zip), triển khai gần nhất của hàm zip như sau,
+ ```py
+ def zip(*iterables):
+ sentinel = object()
+ iterators = [iter(it) for it in iterables]
+ while iterators:
+ result = []
+ for it in iterators:
+ elem = next(it, sentinel)
+ if elem is sentinel: return
+ result.append(elem)
+ yield tuple(result)
+ ```
+- Hàm này nhận vào một số tuỳ ý các đối tượng có thể lặp (iterable objects), thêm mỗi phần tử của các đối tượng này vào danh sách `result` thông qua việc gọi hàm `next` trên đối tượng này, và dưng lại khi đôi tượng này hết các phần tử để lặp
+- Chú ý là khi bất cứ đối tượng lặp nào cạn kiệt đối tượng lặp, các phần tử tồn tại trong danh sách `result` được bỏ qua. Đó là những gì xảy với `3` trong `numbers_iter`
+- Để thực hiện đúng, ta cần làm như sau
+ ```py
+ >>> numbers = list(range(7))
+ >>> numbers_iter = iter(numbers)
+ >>> list(zip(first_three, numbers_iter))
+ [(0, 0), (1, 1), (2, 2)]
+ >>> list(zip(remaining, numbers_iter))
+ [(3, 3), (4, 4), (5, 5), (6, 6)]
+ ```
+ Tham số đầu tiên của zip nên là đối tượng có số lượng các phần tử ít nhất.
+
+---
+
+### ▶ Loop variables leaking out!
+
+1\.
+```py
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Kết quả:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+Nhưng mà `x` chưa bao giờ được định nghĩa bên ngoài vòng lặp...
+2\.
+```py
+# Lần này khởi tạo x trước
+x = -1
+for x in range(7):
+ if x == 6:
+ print(x, ': for x inside loop')
+print(x, ': x in global')
+```
+
+**Kết quả:**
+```py
+6 : for x inside loop
+6 : x in global
+```
+
+3\.
+
+**Kết quả (Python 2.x):**
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+4
+```
+
+**Kết quả (Python 3.x):**
+```py
+>>> x = 1
+>>> print([x for x in range(5)])
+[0, 1, 2, 3, 4]
+>>> print(x)
+1
+```
+
+#### 💡 Giải thích:
+
+- Trong Python, vòng lặp for sử dụng phạm vi mà chúng tồn tại bên trong và để biến lặp (loop-variable) đã được định nghĩa của chúng đằng sau. Điều này cũng áp dụng khi chúng ta định nghĩa rõ biến lặp trong không gian tên toàn cục trước đó. Trong trường hợp này vòng lặp sẽ gắn lại biến đã tồn tại.
+
+- Sự khác nhau giữa kết quả của trình thông dịch Python 2.x và Python 3.x liên quan tới ví dụ list comprehension có thể được giải thích bằng tài liệu [Những điều mới trong Python 3.0](https://docs.python.org/3/whatsnew/3.0.html):
+
+ > "List comprehensions không còn hỗ trợ dạng cú pháp `[... for var in item1, item2, ...]`. Hãy Sử dụng `[... for var in (item1, item2, ...)]`. Còn nữa, chú ý rằng list comprehensions có các ngữ nghĩa khác nhau: chúng khá gần với cú pháp đặc biệt của một biểu diễn generator bên trong hàm xây dựng `list()` , và cụ thể là, các biến điều khiển vòng lặp không còn bị rò rỉ (leaked) trong phạm vi xung quanh"
+
+---
+
+### ▶ Beware of default mutable arguments!
+
+
+```py
+def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+```
+
+**Kết quả:**
+```py
+>>> some_func()
+['some_string']
+>>> some_func()
+['some_string', 'some_string']
+>>> some_func([])
+['some_string']
+>>> some_func()
+['some_string', 'some_string', 'some_string']
+```
+
+#### 💡 Giải thích:
+
+- Các tham số mặc định có kiểu giá trị thay đổi được (mutable) của các hàm trong Python không thực sự được khởi tạo mỗi lần ta gọi hàm. Thay vào đó giá trị trị mới được gán cho chúng được sử dụng với vai trò giá trị mặc định. Khi ta truyền vào hàm `some_func` giá trị `[]`, giá trị mặc định của biến `default_arg` không được sử dụng, do đó hàm trả về kết quả như mong đợi
+
+ ```py
+ def some_func(default_arg=[]):
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+ **Kết quả:**
+ ```py
+ >>> some_func.__defaults__ # Thuộc tính này sẽ cho ta thấy các giá trị mặc định của tham số truyền vào hàm
+ ([],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string'],)
+ >>> some_func()
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ >>> some_func([])
+ >>> some_func.__defaults__
+ (['some_string', 'some_string'],)
+ ```
+
+- Để tránh các bugs liên quan tới các tham số có thể thay đổi giá trị, ta có thể gán cho chúng giá trị mặc định là `None` và sau đó viết mã kiểm tra xem giá trị truyền vào hàm có tương ứng với tham số đó hay không. Ví dụ:
+
+ ```py
+ def some_func(default_arg=None):
+ if default_arg is None:
+ default_arg = []
+ default_arg.append("some_string")
+ return default_arg
+ ```
+
+---
+
+### ▶ Catching the Exceptions
+
+```py
+some_list = [1, 2, 3]
+try:
+ # Câu lệnh sau sẽ gây ra ngoại lệ ``IndexError``
+ print(some_list[4])
+except IndexError, ValueError:
+ print("Caught!")
+
+try:
+ # Câu lệnh sau sẽ gây ra ngoại lệ ``ValueError``
+ some_list.remove(4)
+except IndexError, ValueError:
+ print("Caught again!")
+```
+
+**Kết quả (Python 2.x):**
+```py
+Caught!
+
+ValueError: list.remove(x): x not in list
+```
+
+**Kết quả (Python 3.x):**
+```py
+ File "", line 3
+ except IndexError, ValueError:
+ ^
+SyntaxError: invalid syntax
+```
+
+#### 💡 Giải thích
+
+* Để thêm nhiều ngoại lệ trên cùng một dòng except, bạn cần truyền vào một tuple với các ngoại lệ được liệt kê bên trong hai dấu ngoặc đơn. Ví dụ,
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ # This should raise a ``ValueError``
+ some_list.remove(4)
+ except (IndexError, ValueError), e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Kết quả (Python 2.x):**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+ **Kết quả (Python 3.x):**
+ ```py
+ File "", line 4
+ except (IndexError, ValueError), e:
+ ^
+ IndentationError: unindent does not match any outer indentation level
+ ```
+
+* Tách biệt ngoại lệ với biến dùng dấu phẩy đã khôn còn được áp dụng nữa và khi viết như vậy thi code sẽ không chạy trong Python 3; Cách làm đúng là sử dụng `as`. Ví dụ,
+ ```py
+ some_list = [1, 2, 3]
+ try:
+ some_list.remove(4)
+
+ except (IndexError, ValueError) as e:
+ print("Caught again!")
+ print(e)
+ ```
+ **Kết quả:**
+ ```
+ Caught again!
+ list.remove(x): x not in list
+ ```
+
+---
+
+### ▶ Same operands, different story!
+
+1\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a = a + [5, 6, 7, 8]
+```
+
+**Kết quả:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4]
+```
+
+2\.
+```py
+a = [1, 2, 3, 4]
+b = a
+a += [5, 6, 7, 8]
+```
+
+**Kết quả:**
+```py
+>>> a
+[1, 2, 3, 4, 5, 6, 7, 8]
+>>> b
+[1, 2, 3, 4, 5, 6, 7, 8]
+```
+
+#### 💡 Giải thích:
+
+* `a += b` không phải luôn luôn cho ra kết quả giống như `a = a + b`. Các lơp *có thể* triển khai phép *`op=`* khác nhau.
+
+* Biểu diễn `a = a + [5,6,7,8]` sinh ra một danh sách mới và tham chiếu của `a` trỏ tới danh sách mới, và để cho `b` không đổi.
+* Biển diễn `a += [5,6,7,8]` thực sự tương đương vơi hàm "mở rộng" áp dụng cho danh sách ơ đó `a` và `b` vẫn trỏ về cùng một danh sách đã được thay đổi ngay tại chỗ.
+
+---
+
+### ▶ Name resolution ignoring class scope
+
+1\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = (x for i in range(10))
+```
+
+**Kết quả:**
+```py
+>>> list(SomeClass.y)[0]
+5
+```
+
+2\.
+```py
+x = 5
+class SomeClass:
+ x = 17
+ y = [x for i in range(10)]
+```
+
+**Kết quả (Python 2.x):**
+```py
+>>> SomeClass.y[0]
+17
+```
+
+**Kết quả (Python 3.x):**
+```py
+>>> SomeClass.y[0]
+5
+```
+
+#### 💡 Giải thích
+- Các phạm vi được lồng bên trong định nghĩa của lớp lờ đi các tên (biến) được khai báo ở mức lớp (biến gán với lớp)
+- Biễn diễn bộ sinh (generator) có phạm vi riêng của chúng
+- Bắt đầu từ Python 3.X, các list comprehensions cũng có phạm riêng của chúng
+
+---
+
+### ▶ Needles in a Haystack *
+
+
+
+Tôi chưa bao giờ gặp một lập trình viên Python kinh nghiệm nào mà chưa gặp phải một trong những tình huống éo le dưới đây
+1\.
+
+```py
+x, y = (0, 1) if True else None, None
+```
+
+**Kết qủa:**
+
+```py
+>>> x, y # Kết quả mong đợi (0, 1)
+((0, 1), None)
+```
+
+2\.
+
+```py
+t = ('one', 'two')
+for i in t:
+ print(i)
+
+t = ('one')
+for i in t:
+ print(i)
+
+t = ()
+print(t)
+```
+
+**Kết quả:**
+
+```py
+one
+two
+o
+n
+e
+tuple()
+```
+
+3\.
+
+```
+ten_words_list = [
+ "some",
+ "very",
+ "big",
+ "list",
+ "that"
+ "consists",
+ "of",
+ "exactly",
+ "ten",
+ "words"
+]
+```
+
+**Kết quả**
+
+```py
+>>> len(ten_words_list)
+9
+```
+
+4\. Not asserting strongly enough
+
+```py
+a = "python"
+b = "javascript"
+```
+
+**Kết quả:**
+
+```py
+# Gọi một câu lệnh assert với một thông điệp chỉ rõ lý do bị lỗi
+>>> assert(a == b, "Both languages are different")
+# Không thấy một ngoại lệ lỗi AssertionError
+```
+
+5\.
+
+```py
+some_list = [1, 2, 3]
+some_dict = {
+ "key_1": 1,
+ "key_2": 2,
+ "key_3": 3
+}
+
+some_list = some_list.append(4)
+some_dict = some_dict.update({"key_4": 4})
+```
+
+**Kết quả:**
+
+```py
+>>> print(some_list)
+None
+>>> print(some_dict)
+None
+```
+
+6\.
+
+```py
+def some_recursive_func(a):
+ if a[0] == 0:
+ return
+ a[0] -= 1
+ some_recursive_func(a)
+ return a
+
+def similar_recursive_func(a):
+ if a == 0:
+ return a
+ a -= 1
+ similar_recursive_func(a)
+ return a
+```
+
+**Kết quả:**
+
+```py
+>>> some_recursive_func([5, 0])
+[0, 0]
+>>> similar_recursive_func(5)
+4
+```
+
+#### 💡 Giải thích:
+
+* Trường hợp thứ nhất cách viết để có kết quả như mong đợi là `x, y = (0, 1) if True else (None, None)`.
+
+* Trong trường hợp thứ hai cách viết đúng phải là `t = ('one',)` hoặc `t = 'one',` (có thêm dấu phẩy phía sau) nếu không trình thông dịch sẽ xem `t` như là một `str` và lặp qua từng kí tự của string này.
+
+* `()` là một cặp kí hiệu đặc biệt định nghĩa một `tuple` rỗng.
+
+* Trong ví dụ thứ ba, bạn có thể để ý thấy thiếu một dấu phẩy đằng sau phần tử thứ 5 (`"that"`) của danh sách. Do đó, Python ngầm nối hai string liền kề,
+ ```py
+ >>> ten_words_list
+ ['some', 'very', 'big', 'list', 'thatconsists', 'of', 'exactly', 'ten', 'words']
+ ```
+
+* Không có ngoại lệ `AssertionError` trong ví dụ thứ tư bởi vì thay vì kiểm tra tính đúng sai của biểu diễn `a == b`, chúng ta kiểm tra một tuple. Ví dụ dưới đây làm rõ hơn điều này,
+ ```py
+ >>> a = "python"
+ >>> b = "javascript"
+ >>> assert a == b
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError
+
+ >>> assert (a == b, "Values are not equal")
+ :1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
+
+ >>> assert a == b, "Values are not equal"
+ Traceback (most recent call last):
+ File "", line 1, in
+ AssertionError: Values are not equal
+ ```
+
+* Trong ví dụ thứ năm, hầu hết các phương thức thay đổi các phần tử của các đối tượng chuỗi (sequence) hay ánh xạ như `list.append`, `dict.update`,`list.sort`, và vân vân. thay đổi ngay tại chỗ (in-place) các đối tượng này và trả về `None`. Nguyên nhân sâu xa là nhằm tăng hiệu năng thông qua việc tránh tạo bản sao của đối tượng nếu hành động được thực hiện trên đối tượng mang tính "ngay tại chỗ" (Tham khảo thêm tại [đây](http://docs.python.org/2/faq/design.html#why-doesn-t-list-sort-return-the-sorted-list)).
+* Trường hợp cuối cùng dường như khá rõ rằng, đối tượng có thể thay đổi giá trị (như `list` ) có thể được thay đổi trong hàm, và việc gán biến này lại cho một đối tượng không thể thay đổi giá trị ()`a -= 1` không làm thay đổi giá trị.
+* Lưu ý đến những điều kể trên có thể giúp bạn tiết kiệm được nhiều thời gian cho việc tìm sửa lỗi trong các chương trình lớn.
+---
+
+
+### ▶ Splitsies *
+
+```py
+# thực hiện câu lệnh này
+>>> 'a'.split()
+['a']
+
+# cũng giống như
+>>> 'a'.split(' ')
+['a']
+
+# nhưng khi chạy
+>>> len(''.split())
+0
+
+# lại cho kết quả không giống nhau
+>>> len(''.split(' '))
+1
+```
+
+#### 💡 Giải thích:
+
+- Ta dường như thấy ngay là kí tự phân cách mặc định dùng cho split là một khoảng cách đơn (single space) `' '`, nhưng trong tài liệu [docs] (https://docs.python.org/2.7/library/stdtypes.html#str.split)
+ > Nếu kí tự phân cách không được chỉ định hoặc là `None`, một giải thuật chia tách khác được áp dụng: các khoảng trắng liên tục được xem như là một phân cách đơn, và kết quả sẽ chứa các strings khác rỗng tại phần đầu hoặc cuối nếu string có các khoảng trắng ở phần đầu và đuôi. Do vậy, phân tách một string rỗng hoặc một string chứa chỉ khoảng trắng với một phân tách None sẽ trả về `[]`.
+ > Nếu có kí tự phân cách, các dấu phân cách liên tục không được nhóm lại cùng nhau và được coi là để phân định các strings rỗng (ví dụ, `'1,,2'.split(',')` trả về `['1', '', '2']`). Phân tách một string rỗng với một kí tự phân tách chỉ định trả về `['']`.
+- Ví dụ dưới đây chỉ ra cách các khoảng trắng được xử lý,
+ ```py
+ >>> ' a '.split(' ')
+ ['', 'a', '']
+ >>> ' a '.split()
+ ['a']
+ >>> ''.split(' ')
+ ['']
+ ```
+
+---
+
+### ▶ Wild imports *
+
+
+
+```py
+# File: module.py
+
+def some_weird_name_func_():
+ print("works!")
+
+def _another_weird_name_func():
+ print("works!")
+
+```
+
+**Kết quả**
+
+```py
+>>> from module import *
+>>> some_weird_name_func_()
+"works!"
+>>> _another_weird_name_func()
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name '_another_weird_name_func' is not defined
+```
+
+#### 💡 Giải thích:
+
+- Chúng ta được khuyên là tránh sử dụng các wildcard imports. Vì với chúng, các tên (ví dụ hàm) với dấu gạch chân phía trước sẽ không được import. Điều này gây ra lỗi trong lúc code chạy
+- Khi chúng ta dùng `from ... import a, b, c`, lỗi `NameError` sẽ khôn xảy ra nữa.
+ ```py
+ >>> from module import some_weird_name_func_, _another_weird_name_func
+ >>> _another_weird_name_func()
+ works!
+ ```
+- Nếu bạn thực sự muốn sử dụng wildcard imports, bạn se phải định nghĩa biến `__all__` trong module của bạn, biến này sẽ chưa một danh sách các đối tượng công cộng được import khi sử dụng wildcard imports.
+ ```py
+ __all__ = ['_another_weird_name_func']
+
+ def some_weird_name_func_():
+ print("works!")
+
+ def _another_weird_name_func():
+ print("works!")
+ ```
+ **Kết quả**
+
+ ```py
+ >>> _another_weird_name_func()
+ "works!"
+ >>> some_weird_name_func_()
+ Traceback (most recent call last):
+ File "", line 1, in
+ NameError: name 'some_weird_name_func_' is not defined (hàm "some_weird_name_func_" không được định nghĩa)
+ ```
+
+---
+
+### ▶ All sorted? *
+
+
+
+```py
+>>> x = 7, 8, 9
+>>> sorted(x) == x
+False
+>>> sorted(x) == sorted(x)
+True
+
+>>> y = reversed(x)
+>>> sorted(y) == sorted(y)
+False
+```
+
+#### 💡 Giải thích:
+
+- Phương thức `sorted` luôn luôn trả về một list, và trong Python so sánh giữa lists và tuples luôn luôn cho ra kết quả `False`.
+- ```py
+ >>> [] == tuple()
+ False
+ >>> x = 7, 8, 9
+ >>> type(x), type(sorted(x))
+ (tuple, list)
+ ```
+
+- Khác với `sorted`, phương thức `reversed` trả về một iterator. Tại sao? Bởi vì việc sắp xếp yêu cầu iterator có thể được thay đổi ngay tại chỗ (in-place) hoặc sử dụng một bộ chứa bổ sung (một list), trái lại việc đảo chiều (reversing) có thể hoạt động đơn giản là lặp từ chỉ số cuối về phần tử đầu
+- Do đó khi so sánh `sorted(y) == sorted(y)`, lời gọi đầu tiên `sorted()` sẽ tiêu thụ iterator `y`, và lời gọi tiếp theo sẽ trả về một list rỗng.
+
+ ```py
+ >>> x = 7, 8, 9
+ >>> y = reversed(x)
+ >>> sorted(y), sorted(y)
+ ([7, 8, 9], [])
+ ```
+
+---
+
+### ▶ Midnight time doesn't exist?
+
+```py
+from datetime import datetime
+
+midnight = datetime(2018, 1, 1, 0, 0)
+midnight_time = midnight.time()
+
+noon = datetime(2018, 1, 1, 12, 0)
+noon_time = noon.time()
+
+if midnight_time:
+ print("Time at midnight is", midnight_time)
+
+if noon_time:
+ print("Time at noon is", noon_time)
+```
+
+**Kết quả (< 3.5):**
+
+```py
+('Time at noon is', datetime.time(12, 0))
+```
+Thời gian nửa đêm không được hiển thị
+#### 💡 Giải thêm:
+
+Trước Python 3.5, giá trị luận lý cho đối tượng `datetime.time` được xem như là `False` nếu đối tượng này biểu diễn nửa đêm trong UTC. Code dễ sai khi sử dụng cú pháp `if obj:` để kiểm tra `obj` là null hay những giá trị tương tương với "rỗng".
+
+---
+---
+
+
+
+## Section: The Hidden treasures!
+
+Phần này bao gồm những điều ít được biết tới về Python, những điều hấp dẫn này hầu hết những người mới bắt đầu như tôi đều không biết
+
+### ▶ Okay Python, Can you make me fly?
+
+Hãy bắt đầu nào
+
+```py
+import antigravity
+```
+
+**Kết quả:**
+Sshh... Một bí mật lớn.
+
+#### 💡 Giải thích:
++ `antigravity` là một trong vài quả trứng phục sinh được tạo ra bởi các lập trình viên Python
++ `import antigravity` mở trang web [truyện hài XKCD ](http://xkcd.com/353/) trong một trình duyệt.
++ Còn nhiều hơn thế nữa. Có các **quả trứng phục sinh khác bên trong quả trứng phục sinh**. Nếu bạn xem đoạn [mã] (https://github.com/python/cpython/blob/master/Lib/antigravity.py#L7-L17), có một hàm được định nghĩa để triển khai [Giải thuật geohashing của XKCD]
+
+---
+
+### ▶ `goto`, but why?
+
+
+```py
+from goto import goto, label
+for i in range(9):
+ for j in range(9):
+ for k in range(9):
+ print("I am trapped, please rescue!")
+ if k == 2:
+ goto .breakout # breaking out from a deeply nested loop
+label .breakout
+print("Freedom!")
+```
+
+**Kết quả (Python 2.3):**
+```py
+I am trapped, please rescue!
+I am trapped, please rescue!
+Freedom!
+```
+
+#### 💡 Giải thích:
+- Một phiên bản Python của `goto` được [công bố](https://mail.python.org/pipermail/python-announce-list/2004-April/002982.html) trong ngày cá tháng tư năm 2004 như là một lời đùa.
+- Các phiên bản Python hiện hành không có module này.
+- Mặc dù triển khai `goto` chạy được, nhưng đừng sử dụng nó. [Lý do](https://docs.python.org/3/faq/design.html#why-is-there-no-goto) tại sao Python không có `goto`.
+
+---
+
+### ▶ Brace yourself!
+
+Nếu bạn không thích sử dụng khoảng trắng để biểu thị các phạm vi, bạn có thể sử dụng phonng cách như trong ngôn ngữ C {} bằng cách import
+```py
+from __future__ import braces
+```
+
+**Kết quả:**
+```py
+ File "some_file.py", line 1
+ from __future__ import braces
+SyntaxError: not a chance
+```
+
+Các dấu ngoặc nhọn? Không được đâu! Nếu bạn nghĩ rằng điều gây thất vọng, sử dụng Java. Còn một điều ngạc nhiên nữa đây, bạn có thể tìm thấy ngoại lệ `SyntaxError` được khởi lên ở đâu trong module `__future__` hay không (https://github.com/python/cpython/blob/master/Lib/__future__.py)?
+
+#### 💡 Giải thích:
++ Module `__future__` thường được dùng để cung cấp các tính năng đến từ các phiên bản mới của Python. Tuy nhiên "tương lai" ("feature") trong hoàn cảnh này có vẻ hơi mỉa mai.
++ Có một quả trứng phục sinh liên quan đến tâm trạng của cộng đồng Python về vấn đề này.
++ Đoạn mã được tìm thấy [ở đây](https://github.com/python/cpython/blob/025eb98dc0c1dc27404df6c544fc2944e0fa9f3a/Python/future.c#L49) trong file `future.c`.
++ Khi trình biên dịch Cpython gặp một [câu lệnh future](https://docs.python.org/3.3/reference/simple_stmts.html#future-statements), Nó chạy đoạn mã tương ứng trong `future.c` trước khi thực hiện câu lệnh import thông thường.
+
+---
+
+### ▶ Let's meet Friendly Language Uncle For Life
+
+**Output (Python 3.x)**
+```py
+>>> from __future__ import barry_as_FLUFL
+>>> "Ruby" != "Python" # chả có gì phải nghi ngờ về câu lệnh so sánh này cả
+ File "some_file.py", line 1
+ "Ruby" != "Python"
+ ^
+SyntaxError: invalid syntax
+
+>>> "Ruby" <> "Python"
+True
+```
+
+Rồi! có thứ để xem.
+
+
+#### 💡 Giải thích:
+- Điều ta muốn tìm hiểu liên quan tới [PEP-401](https://www.python.org/dev/peps/pep-0401/), PEP này được xuất bản vào ngày một tháng tư năm 2009 (là gì thì bạn biết rồi đó).
+- Đoạn dưới đây được trích ra từ PEP-401
+
+ > Nhận ra rằng kí hiệu toán tử so sánh không bằng != trong Python 3.0 thấy gớm, gõ thật khó, FLUFL khôi phục lại kí hiệu hình kim cương <>.
+- Bạn nhiều thứ đáng xem trong PEP này; bạn có thể đọc chúng [ở đây]
+(https://www.python.org/dev/peps/pep-0401/).
+- Toán tử hình kim cương này hoạt động tốt trong môi trường tương tác, nhưng lại gây ra ngoại lệ `SyntaxError` khi chạy qua file python (hãy xem [vấn đề](https://github.com/satwikkansal/wtfpython/issues/94)). Tuy nhiên bạn có thể gói câu lệnh này vào trong hàm `eval` hoặc `compile` để chạy được.
+ ```py
+ from __future__ import barry_as_FLUFL
+ print(eval('"Ruby" <> "Python"'))
+ ```
+
+---
+
+### ▶ Even Python understands that love is complicated
+
+```py
+import this
+```
+
+Nào, **this** là gì thế? `this` là tình yêu :heart:
+
+**Kết quả:**
+```
+The Zen of Python, by Tim Peters
+
+Beautiful is better than ugly.
+Explicit is better than implicit.
+Simple is better than complex.
+Complex is better than complicated.
+Flat is better than nested.
+Sparse is better than dense.
+Readability counts.
+Special cases aren't special enough to break the rules.
+Although practicality beats purity.
+Errors should never pass silently.
+Unless explicitly silenced.
+In the face of ambiguity, refuse the temptation to guess.
+There should be one-- and preferably only one --obvious way to do it.
+Although that way may not be obvious at first unless you're Dutch.
+Now is better than never.
+Although never is often better than *right* now.
+If the implementation is hard to explain, it's a bad idea.
+If the implementation is easy to explain, it may be a good idea.
+Namespaces are one honking great idea -- let's do more of those!
+```
+
+`this` là Thiền của Python!
+
+```py
+>>> love = this
+>>> this is love
+True
+>>> love is True
+False
+>>> love is False
+False
+>>> love is not True or False
+True
+>>> love is not True or False; love is love # Tình yêu thật phức tạp
+True
+```
+
+#### 💡 Giải thích:
+* Module `this` trong Python là một quả trứng phục sinh cho Thiền của Python ([PEP 20](https://www.python.org/dev/peps/pep-0020)).
+* Và nếu bạn thấy nó hứng thú, hãy xem mã triển khai của [this.py](https://hg.python.org/cpython/file/c3896275c0f6/Lib/this.py). Hấp dẫn thay, **mã của Thiền lại vi phạm chính quy tắc thiền**
+* Nói về câu `love is not True or False; love is love`, mỉa mai thay câu nay tự thấy nó co nghĩa (nếu không, hay xnhinf cá ví dụ liên quan tới `is` và `is not`)
+---
+
+### ▶ Yes, it exists!
+
+** Khối `else` của các vòng lặp .** Dưới đây là một ví dụ điển hình:
+
+```py
+ def does_exists_num(l, to_find):
+ for num in l:
+ if num == to_find:
+ print("Exists!")
+ break
+ else:
+ print("Does not exist")
+```
+
+**Kết quả:**
+```py
+>>> some_list = [1, 2, 3, 4, 5]
+>>> does_exists_num(some_list, 4)
+Exists! (Tồn tại)
+>>> does_exists_num(some_list, -1)
+Does not exist (Không tồn tại)
+```
+
+**Khối `else` trong xử lý ngoại lệ.** Một ví dụ,
+
+```py
+try:
+ pass
+except:
+ print("Exception occurred!!!")
+else:
+ print("Try block executed successfully...")
+```
+
+**Kết quả:**
+```py
+Try block executed successfully...
+```
+
+#### 💡 Giải thích:
+- Khối `else` sau một vòng lặp được thực hiện chỉ khi trong quá trình lặp không có một `break` nào. Bạn có thể nghĩ nó như là một khối "không ngắt" ("nobreak")
+- Khối `else` sau một khối try còn được gọi la "khối hoàn tất" đó khi các câu lệnh trong khối này sẽ được thực thi khi khối try hoàn thành trọn vẹn công việc của mình.
+
+---
+### ▶ Ellipsis *
+
+```py
+def some_func():
+ Ellipsis
+```
+
+**Kết quả**
+```py
+>>> some_func()
+# No output, No Error
+
+>>> SomeRandomString
+Traceback (most recent call last):
+ File "", line 1, in
+NameError: name 'SomeRandomString' is not defined
+
+>>> Ellipsis
+Ellipsis
+```
+
+#### 💡 Giải thích
+- Trong Python, `Ellipsis` là một đối tượng có sẵn, toàn cục tương đương với `...`.
+ ```py
+ >>> ...
+ Ellipsis
+ ```
+- Eliipsis có thể được sử dụng cho các mục đích sau
+ + Là một đối tượng "xí chỗ" cho mã chưa được viết (như câu lệnh `pass`)
+ + Trong cú pháp lát cắt (slicing syntax), nó được dùng để biểu diễn các lát cắt đầy đủ theo hướng còn lại
+ + In slicing syntax to represent the full slices in remaining direction
+ ```py
+ >>> import numpy as np
+ >>> three_dimensional_array = np.arange(8).reshape(2, 2, 2)
+ array([
+ [
+ [0, 1],
+ [2, 3]
+ ],
+
+ [
+ [4, 5],
+ [6, 7]
+ ]
+ ])
+ ```
+ `three_dimensional_array` là một mảng của mảng của các mảng. Nào, chúng ta muốn in ra phần tử thứ hai (chỉ số `1`) của tất cả các mảng phía trong cùng, chugns ta có thể sử dụng Ellipsis để "chơi chiêu qua mặt" (bypass) tất cả các chiều (dimensions) phía trước đó.
+ ```py
+ >>> three_dimensional_array[:,:,1]
+ array([[1, 3],
+ [5, 7]])
+ >>> three_dimensional_array[..., 1] # using Ellipsis.
+ array([[1, 3],
+ [5, 7]])
+ ```
+ Chú ý rằng: cách trên có thể thực trên bất cứ mảng với số lượng chiều nào. Bạn có thể lựa trên lát cắt (slice) trước và chiều cuối cùng và lờ đi các chiều nằm ở giữa (`n_dimensional_array[firs_dim_slice, ..., last_dim_slice]`)
+ + Trong [Gợi ý kiểu](https://docs.python.org/3/library/typing.html), Ellipsis được dùng để chỉ ra một phần của kiểu (như là `(Callable[..., int]` hay `Tuple[str, ...]`))
+ + Bạn có thể sử dụng Ellipsis như là một tham số hàm mặc định (trong những trường hợp bạn muốn phân biệt giữa hai tính huống "không có tham số được truyền vào") và "giá trị None được truyền vào"
+
+---
+
+### ▶ Inpinity
+
+Việc viết sai chính tả là chủ ý.
+**Kết quả (Python 3.x):**
+```py
+>>> infinity = float('infinity')
+>>> hash(infinity)
+314159
+>>> hash(float('-inf'))
+-314159
+```
+
+#### 💡 Giải thích:
+- Mã băm của vô cực là 10⁵ x π.
+- Thú vị là ở chỗ trong Python 3 mã băm của `float('-inf')` là "-10⁵ x π", trái lại trong Python là "-10⁵ x e"
+---
+
+### ▶ Let's mangle
+
+1\.
+```py
+class Yo(object):
+ def __init__(self):
+ self.__honey = True
+ self.bro = True
+```
+
+**Kết quả:**
+```py
+>>> Yo().bro
+True
+>>> Yo().__honey
+AttributeError: 'Yo' object has no attribute '__honey' (Đối tượng 'Yo' không có thuộc tính '__honey')
+>>> Yo()._Yo__honey
+True
+```
+
+2\.
+```py
+class Yo(object):
+ def __init__(self):
+ # Let's try something symmetrical this time
+ self.__honey__ = True
+ self.bro = True
+```
+
+**Kết quả:**
+```py
+>>> Yo().bro
+True
+
+>>> Yo()._Yo__honey__
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'Yo' object has no attribute '_Yo__honey__' (Đối tượng 'Yo' không có thuộc tính '_Yo__honey__')
+```
+
+Tại sao `Yo()._Yo__honey` lại gây ra lỗi?
+
+3\.
+
+```py
+_A__variable = "Some value"
+
+class A(object):
+ def some_func(self):
+ return __variable # (biến này chưa được khởi tạo)
+```
+
+**Kết quả:**
+```py
+>>> A().__variable
+Traceback (most recent call last):
+ File "", line 1, in
+AttributeError: 'A' object has no attribute '__variable' (Đối tượng 'A' không có thuộc tính '__variable')
+
+>>> A().some_func()
+'Some value'
+```
+
+
+#### 💡 Giải thích:
+
+* [Name Mangling](https://en.wikipedia.org/wiki/Name_mangling) được sử dụng để tránh việc đụng độ về tên (names) giữa các không gian tên (namespaces).
+* Trong Python, trình thông dịch thay đổi (mangles) tên của các thành viên của một lớp mà bắt đầu với `__` (hai dấu gạch chân liền nhau hay còn gọi là "dunder") và các tên không thúc với nhiều hơn một dấu gạch chân bằng việc thêm vào `_TênCủaLớp` vào trước đó
+* Vì vậy, để truy cập vào thuộc tính `__honey` trong đoạn mã đầu tiên, bạn phải nối `_Yo` vào phía trước, để ngăn việc xung đột với thuộc tính có cùng tên trong lớp khác.
+* Nhưng tại sao trong đoạn mã thứ hai mặc dù đã dung tên đúng nhưng ta vẫn không truy cập được vào thuộc tính? Bởi vì cách thức mangling này không áp dụng đối với các tên biến kết thúc với hai dấu gạch chân.
+* Đoạn mã thứ ba cũng là kết quả của việc mangling. Tên `__variable` trong câu lệnh `return __variable` được chuyển thành `_A__variable`, và lại vô tình trùng với tên biến được khai báo phía bên ngoài
+* Còn nữa, nếu tên bị mangle nhiều hơn 255 kí tự, việc cắt gọn tên sẽ diên ra.
+
+---
+---
+
+## Section: Appearances are deceptive!
+
+### ▶ Skipping lines?
+
+**Kết quả:**
+```py
+>>> value = 11
+>>> valuе = 32
+>>> value
+11
+```
+
+Cái quái gì thế?
+
+**Chú ý:** Để làm ra kết quả như trên bạn chỉ cần sao chếp các câu lệnh và dán nó vào file hay shell.
+
+#### 💡 Giải thích
+
+Một vài kí tự không phải là kí tự phương tây trông giống như các kí tự trong bảng chữ cái tiếng Anh nhưng lại được diễn giải khác bởi trình thông idhcj
+```py
+>>> ord('е') # Kí tự cyrillic 'e' (Ye)
+1077
+>>> ord('e') # Kí tự latin 'e', được sử dụng trong tiếng Anh và trên các bàn phím chuẩn
+101
+>>> 'е' == 'e'
+False
+
+>>> value = 42 # latin e
+>>> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
+>>> value
+42
+```
+
+
+Hàm tích hợp sẵn `ord()` trả về mã Unicode của một kí tự [code point](https://en.wikipedia.org/wiki/Code_point), và bạn có thể thấy các mã khác nhau cho kí tự Cyrillic 'e' và kí tự Latin 'e' .
+
+---
+
+### ▶ Teleportation
+
+
+
+```py
+# Cài đặt thư viên numpy sử dụng `pip install numpy` trước
+import numpy as np
+
+def energy_send(x):
+ # Khởi tạo một mảng numpy
+ np.array([float(x)])
+
+def energy_receive():
+ # Trả về một mang numpy rỗng
+ return np.empty((), dtype=np.float).tolist()
+```
+
+**Kết quả:**
+```py
+>>> energy_send(123.456)
+>>> energy_receive()
+123.456
+```
+
+Có gì mới ở đây nào ?
+
+#### 💡 Giải thích:
+
+* Chú ý rằng mảng numpy được tạo trong hàm `energy_send` không có được trả vì, vì vậy không gian bộ nhớ được giải phóng.
+* `numpy.empty()` trả về phần bộ nhơ tự do tiếp theo mà không khởi tạo lại nó. Phân bộ nhớ nay lại vô tình trung với phần bộ nhớ trước đó được giải phóng (thường xảy ra, nhưng không phải là luôn luôn)
+
+---
+
+
+### ▶ Well, something is fishy...
+
+```py
+def square(x):
+ """
+ A simple function to calculate the square of a number by addition.
+ """
+ sum_so_far = 0
+ for counter in range(x):
+ sum_so_far = sum_so_far + x
+ return sum_so_far
+```
+
+**Kết quả (Python 2.x):**
+
+```py
+>>> square(10)
+10
+```
+
+Sao kết quả lại ra 100?
+
+**Chú ý:** Nếu bạn không thực hiện lại được ví dụ trên, chạy thử file [mixed_tabs_and_spaces.py](/mixed_tabs_and_spaces.py) thông qua shell.
+#### 💡 Explanation
+
+* **Đừng trộn lẫn tabs và các khoảng trắng (spaces)!** Kí tự nằm phía trước lệnh return là một "tab", và đoạn mã ở đâu đó trong code được thụt lùi theo bội số của "4 khoảng trắng"
+* This is how Python handles tabs:
+* Đây là cách Python xử lý tabs:
+ > Đầu tiên, tabs được thay thế (từ trái sang phải) bằng từ một tới tám khoảng trắng để cho tổng số lượng các kí tự (bao gồm cả kí tự thay thế) có giá trị là bội số của tám <...>
+* Do đó "tab" nằm ở dòng cuối của hàm `square` được thay thế bằng tám khoảng trắng, và nó đi vào nằm trong vòng lặp.
+* Python 3 sẽ văng ra một lỗi khi nó gặp phải các lỗi liên quan tới tabs và khoảng trắng.
+ **Kết quả (Python 3.x):**
+ ```py
+ TabError: inconsistent use of tabs and spaces in indentation
+ ```
+
+---
+---
+
+## Section: Miscellaneous
+
+
+### ▶ `+=` chạy nhanh hơn
+
+
+```py
+# Sử dụng "+" để cộng 3 strings:
+>>> timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.25748300552368164
+# Sử dụng "+=" để cộng 3 strings:
+>>> timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100)
+0.012188911437988281
+```
+
+#### 💡 Giải thích:
++ `+=` nhanh hơn `+` khi nối nhiều hơn 2 strings bởi vì string đầu tiên (ví dụ, `s1` trong `s1 += s2 + s3`) không bị huỷ đi khi tạo ra chuỗi kết quả cuối cùng
+---
+
+### ▶ Let's make a giant string!
+
+```py
+def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s += "xyz"
+ assert len(s) == 3*iters
+
+def add_bytes_with_plus(iters):
+ s = b""
+ for i in range(iters):
+ s += b"xyz"
+ assert len(s) == 3*iters
+
+def add_string_with_format(iters):
+ fs = "{}"*iters
+ s = fs.format(*(["xyz"]*iters))
+ assert len(s) == 3*iters
+
+def add_string_with_join(iters):
+ l = []
+ for i in range(iters):
+ l.append("xyz")
+ s = "".join(l)
+ assert len(s) == 3*iters
+
+def convert_list_to_string(l, iters):
+ s = "".join(l)
+ assert len(s) == 3*iters
+```
+
+**Kết quả:**
+
+```py
+# Chạy các đoạn lệnh trong ipython shell sử dung %timeit để có kết quả dễ đọc hơn
+# Bạn cũng có thể sử dụng module timeit trong python shell bình thường, như ví dụ dưới đây
+# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
+
+>>> NUM_ITERS = 1000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS)
+124 µs ± 4.73 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS)
+211 µs ± 10.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS)
+61 µs ± 2.18 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS)
+117 µs ± 3.21 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS)
+10.1 µs ± 1.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+Bây giờ tăng số vòng lặp lên 10 lần
+```py
+>>> NUM_ITERS = 10000
+>>> %timeit -n1000 add_string_with_plus(NUM_ITERS) # Linear increase in execution time
+1.26 ms ± 76.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_bytes_with_plus(NUM_ITERS) # Quadratic increase
+6.82 ms ± 134 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_format(NUM_ITERS) # Linear increase
+645 µs ± 24.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> %timeit -n1000 add_string_with_join(NUM_ITERS) # Linear increase
+1.17 ms ± 7.25 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+>>> l = ["xyz"]*NUM_ITERS
+>>> %timeit -n1000 convert_list_to_string(l, NUM_ITERS) # Linear increase
+86.3 µs ± 2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+```
+
+#### 💡 Giải thích
+- Bạn có thể đọc thêm về [timeit] (https://docs.python.org/3/library/timeit.html) or [%timeit](https://ipython.org/ipython-doc/dev/interactive/magics.html#magic-timeit). Module này được sử dụng để đo thời gian chạy của các đoạn code.
+- Đừng dùng `+` để sinh ra các strings dài. Trong Python, `str` không thể thay đổi được giá trị, do đó các strings phía bên trái và phải phải được chép vào string mới cho mỗi cặp ghép string. Nếu bạn nối strings có độ dài là 10, bạn sẽ chép + ((10+10)+10) + (((10+10)+10)+10) = 90 kí tự thay vì chỉ 40 kí tự. Sẽ tệ hơn nữa khi số lượng và kích thước của string tăng (như bạn có thể thấy ở hàm `add_bytes_with_plus` )
+- Do đó, bên nên dùng `.format.` hay `%` (mặc dù các cách sử dụng này sẽ chậm hơn `+` đối với các strings nhỏ )
+- Hay tốt hơn nữa, nếu bạn đã có sẵn những nội dung ở dạng một đối tượng có thể lặp, khi đó sử dụng `''.join(iterable_object)` sẽ nhanh hơn.
+- Không giống như `add_bytes_with_plus` với các tối ưu đến từ `+=` như đã thảo luận ở ví dụ trước đó, `add_string_with_plus` không cho thấy sự tăng cấp bậc hai về thời gian thưucj thi. Sử dụng `s = s + "x" + "y" + "z"` thay vì `s += "xyz"` sẽ làm tăng thời gian chạy lên hai lần..
+ ```py
+ def add_string_with_plus(iters):
+ s = ""
+ for i in range(iters):
+ s = s + "x" + "y" + "z"
+ assert len(s) == 3*iters
+
+ >>> %timeit -n100 add_string_with_plus(1000)
+ 388 µs ± 22.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
+ >>> %timeit -n100 add_string_with_plus(10000) # Quadratic increase in execution time
+ 9 ms ± 298 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
+ ```
+
+- Có nhiều cách để định dạng và tạo một string khổng lồ, trái ngược với điều trong [Zen of Python](https://www.python.org/dev/peps/pep-0020/), như sau
+ > Chỉ nên có một -- và tốt hơn là chỉ một -- cách rõ ràng để làm điều này.
+---
+
+
+### ▶ Minor Ones *
+
+* `join()` là một hàm dành cho string thay vì dành cho list. (nếu nhìn vào thì ta có thể thấy nó hơi ngược ngược)
+ **💡 Giải thích:** Nếu `join()` là một phương thức hoạt động với một string, khi đó nó cũng có thể làm việc được với bất cứ đối tượng có thể lặp khác (như list, tuple, iterators). Nếu nó là một phương thức hoạt động được trên list, thì nó phải được triển khai một cách tách biệt với mọi kiểu. Còn nữa, sẽ là không hợp lý để đặt một phương thức chỉ dành riêng cho string lên một đối tượng tổng quát `list`.
+
+* Các câu lệnh dưới đây nhìn có vẻ lạ, nhưng đúng về ngữ nghĩa:
+ + `[] = ()` vẫn đúng về mặt ngữ nghĩa (Giải nén một `tuple` rỗng sẽ tạo ra một `list` rỗng)
+ + `'a'[0][0][0][0][0]` cũng đúng về ngữ nghĩa bởi các strings là các [chuỗi](https://docs.python.org/3/glossary.html#term-sequence) (các đối tượng có thể lặp hỗ trợ truy cập và các phần tử nó chứa sử dụng các chỉ số số nguyên) trong Python.
+ + Cả `3 --0-- 5 == 8` và `--5 == 5` đều đúng về ngữ nghĩa và cho ra kết quả là `True`.
+
+* Với `a` là một số, `++a` và `--a` đều là các câu lệnh Python hợp lệ nhưng chúng không chạy giống như các câu lệnh tương tự trong các ngôn ngữ khác như C, C++, hay Java.
+ ```py
+ >>> a = 5
+ >>> a
+ 5
+ >>> ++a
+ 5
+ >>> --a
+ 5
+ ```
+
+ **💡 Giải thích:**
+ + Trong Python, không có phép `++`. Thực ra nó chỉ là hai phép tính `+`.
+ + `++a` được phân tích như `+(+a)` và được dịch ra là `a`. Tương tự là `--a`.
+ + [Bài đăng](https://stackoverflow.com/questions/3654830/why-are-there-no-and-operators-in-python) trên StackOverflow thảo luận lý do tại sao không có các phép tính tăng và giảm trong Python.
+
+* Bạn phải biết về kí hiệu con hà mã trong Python. Nhưng bạn có bao giờ nghe tới *kí hiệu xâm lược không gian*?
+ ```py
+ >>> a = 42
+ >>> a -=- 1
+ >>> a
+ 43
+ ```
+ Nó được sử dụng như phép tính tăng, như với một ví dụ khác sau đây
+ ```py
+ >>> a +=+ 1
+ >>> a
+ >>> 44
+ ```
+ **💡 Giải thích:** Trò chơi khăm này xuất phát từ [Raymond Hettinger's tweet](https://twitter.com/raymondh/status/1131103570856632321?lang=en). Kí hiệu xâm lược không gian chỉ được định dạng khác đi là `a -= (-1)`. Tương tự với `a = a - (- 1)`. Tương tự với trường hợp `a += (+ 1)`.
+
+* Python có một tài liệu về phép [hàm ý đảo ngược](https://en.wikipedia.org/wiki/Converse_implication).
+
+ ```py
+ >>> False ** False == True
+ True
+ >>> False ** True == False
+ True
+ >>> True ** False == True
+ True
+ >>> True ** True == True
+ True
+ ```
+
+ **💡 Giải thích:** Nếu bạn thay thế `False` và `True` bằng 0 và 1 và sau đó làm các phép toán, bảng chân trị tương đương với phép ám chỉ ngược. ([Nguồn](https://github.com/cosmologicon/pywat/blob/master/explanation.md#the-undocumented-converse-implication-operator))
+
+* Do chúng ta đang bàn về các phép tính, cũng có phép `@` dành cho việc nhân ma trận (đừng lo, đây là phép tính thực sự)
+ ```py
+ >>> import numpy as np
+ >>> np.array([2, 2, 2]) @ np.array([7, 8, 8])
+ 46
+ ```
+
+ **💡 Giải thích:** Phép tính `@` được thêm vào Python 3.5 với sự chú ý dành cho cộng đồng khoa học. Bất cứ đối tượng nào cũng có thể ghi đè lên phương thức ma thuật (magic method) `__matmul__` để định nghĩa hành vi cho phép tính..
+
+* Từ Python 3.8 trở đi bạn có thể sử dụng cú pháp f-string như `f'{some_var=}` để debugging cho dễ. Ví dụ
+ ```py
+ >>> some_string = "wtfpython"
+ >>> f'{some_string=}'
+ "some_string='wtfpython'"
+ ```
+
+* Python sử dụng 2 bytes cho lưu trữ biến cụ bộ trong các hàm. Về mặt lý thuyết, điều này có nghĩa là chỉ có 65536 biến có thể được định nghĩa trong một hàm. Tuy nhiên, Python có một giải pháp khéo léo, tích hợp sẵn để lưu nhiều hơn 2^16 tên biến. Đoạn code phía dưới cho ta thấy điều gì xảy ra trong stack khi có nhiều hơn 65536 biến cục bộ được định nghĩa (Cảnh báo: đoạn mã này in ra 2^18 dòng văn bản, hảy chuẩn bị!):
+
+ ```py
+ import dis
+ exec("""
+ def f():
+ """ + """
+ """.join(["X" + str(x) + "=" + str(x) for x in range(65539)]))
+
+ f()
+
+ print(dis.dis(f))
+ ```
+
+* Nhiều Python threads sẽ không chạy *Python code* đồng thời. ó vẻ tự nhiên khi sinh ra một số luồng và để nó chạy code Python của bạn đồng thời, nhưng bởi vì [Khoá trình thông dịch toàn cục](https://wiki.python.org/moin/GlobalInterpreterLock) trong Python, tất cả những gì bạn làm là làm cho các luồng của bạn thực thi trên cùng một core theo thứ tự. Các luồng Python rất tốt cho các tác vị liên quan tới IO, nhưng để đạt được việc xử lý song song trong Python cho các tác vụ gắn với CPY, bạn có thể sử dụng module [multiprocessing](https://docs.python.org/2/library/multiprocessing.html)
+
+ ```py
+ # File some_file.py
+ import time
+
+ print("wtfpython", end="_")
+ time.sleep(3)
+ ```
+
+ Đoạn mã trên in ra `wtfpython` sau 3 seconds bởi vì tham số `end` bộ đệm kết quả được dọn sạch sau khi găp `\n` hoặc khi chương trình kết thúc thực thi. Bạn có thể bắt dọn sạch bộ đệm thông qua việc gửi vào tham số `flush=True`..
+
+* Cắt danh sách với các chỉ số nằm ngoài phạm vị sẽ không trả về lỗi
+*
+ ```py
+ >>> some_list = [1, 2, 3, 4, 5]
+ >>> some_list[111:]
+ []
+ ```
+* Cắt một đối tượng có thể lặp không phải lúc nào cũng tạo ra một đối tượng mới. Ví dụ,
+ ```py
+ >>> some_str = "wtfpython"
+ >>> some_list = ['w', 't', 'f', 'p', 'y', 't', 'h', 'o', 'n']
+ >>> some_list is some_list[:] # False là bởi vì một đối tượng mới được tạo
+ False
+ >>> some_str is some_str[:] # True là bởi vì các strings không thể thay đổi giá trị, do vậy không tạo ra một đối tượng mới khi cắt.
+ True
+ ```
+
+* `int('١٢٣٤٥٦٧٨٩')` trả về `123456789` trong Python 3. Trong Python, các kí tự số thập phân bao gồm các kí tự số, và tất cả các kí tự có thể được dùng để tạo nên có số cơ số thập phân, ví dụ. U+0660, ARABIC-INDIC DIGIT ZERO. Đậy là [một câu chuyện hấp dân](http://chris.improbable.org/2014/8/25/adventures-in-unicode-digits/) liên quan
+
+* Bạn có thể ngăn cách các số đơn với dấu gạch chân (để đọc dễ hơn), từ Python 3 trở đi.
+
+ ```py
+ >>> six_million = 6_000_000
+ >>> six_million
+ 6000000
+ >>> hex_address = 0xF00D_CAFE
+ >>> hex_address
+ 4027435774
+ ```
+
+* `'abc'.count('') == 4`. Dưới đây là một cách triển khai gần tương tự với phương thức `count`, ví dụ này sẽ làm mọi thứ rõ ràng hơn
+ ```py
+ def count(s, sub):
+ result = 0
+ for i in range(len(s) + 1 - len(sub)):
+ result += (s[i:i + len(sub)] == sub)
+ return result
+ ```
+ Cư xử của hàm trên là do việc so khớp các chuỗi con (substring) rỗng với các lát cắt có độ dài bằng 0 trong string gốc.
+---
+---
+
+# Đóng góp
+
+Bạn có thể đóng góp cho wtfpython theo những cách dưới đây
+
+- Cho chúng tôi biết những ví dụ mới
+- Dịch wtfpython sang các ngôn gnuwx khác (Xem tại [issues labeled translation](https://github.com/satwikkansal/wtfpython/issues?q=is%3Aissue+is%3Aopen+label%3Atranslation))
+- Minor corrections like pointing out outdated snippets, typos, formatting errors, etc.
+- Sửa các lỗi phụ khác như các đoạn mã bị lỗi thời, các lỗi cú pháp, lỗi định dạng, vân vân.
+- Identifying gaps (things like inadequate explanation, redundant examples, etc.)
+- Tìm những thiếu sót (ví dụ như giải thích chưa cặn kẽ, các ví dụ không cần thiết, vân vân.)
+- Bất cứ đề xuất sáng tạo nào cũng làm cho wtfpython thú vị và hữu dụng hơn
+
+Please see [CONTRIBUTING.md](/CONTRIBUTING.md) for more details. Feel free to create a new [issue](https://github.com/satwikkansal/wtfpython/issues/new) to discuss things.
+
+PS: Please don't reach out with backlinking requests, no links will be added unless they're highly relevant to the project.
+
+# Lời cám ơn
+
+Ý tưởng và thiết kế của wtfpython lấy cảm hứng từ dự án tuyệt vời của Denys Dovhan's [wtfjs](https://github.com/denysdovhan/wtfjs). Cùng với đó là sự hỗ trợ tuyệt vời của những người yêu Python, giúp cho wtfpython có được như ngày hôm nay.
+
+#### Một vài liên kết hay khác!
+* https://www.youtube.com/watch?v=sH4XF6pKKmk
+* https://www.reddit.com/r/Python/comments/3cu6ej/what_are_some_wtf_things_about_python
+* https://sopython.com/wiki/Common_Gotchas_In_Python
+* https://stackoverflow.com/questions/530530/python-2-x-gotchas-and-landmines
+* https://stackoverflow.com/questions/1011431/common-pitfalls-in-python
+* https://www.python.org/doc/humor/
+* https://github.com/cosmologicon/pywat#the-undocumented-converse-implication-operator
+* https://www.codementor.io/satwikkansal/python-practices-for-efficient-code-performance-memory-and-usability-aze6oiq65
+* https://github.com/wemake-services/wemake-python-styleguide/search?q=wtfpython&type=Issues
+* WFTPython discussion threads on [Hacker News](https://news.ycombinator.com/item?id=21862073) and [Reddit](https://www.reddit.com/r/programming/comments/edsh3q/what_the_fck_python_30_exploring_and/).
+
+# 🎓 License
+
+[![WTFPL 2.0][license-image]][license-url]
+
+© [Satwik Kansal](https://satwikkansal.xyz)
+
+[license-url]: http://www.wtfpl.net
+[license-image]: https://img.shields.io/badge/License-WTFPL%202.0-lightgrey.svg?style=flat-square
+
+## Chia sẻ cho bạn bè!
+
+Nếu bạn thích wtfpython, bạn có thể chia sẻ các liên kết dưới đây cho bạn bè,
+
+[Twitter](https://twitter.com/intent/tweet?url=https://github.com/satwikkansal/wtfpython&text=If%20you%20really%20think%20you%20know%20Python,%20think%20once%20more!%20Check%20out%20wtfpython&hashtags=python,wtfpython) | [Linkedin](https://www.linkedin.com/shareArticle?url=https://github.com/satwikkansal&title=What%20the%20f*ck%20Python!&summary=If%20you%20really%20thing%20you%20know%20Python,%20think%20once%20more!) | [Facebook](https://www.facebook.com/dialog/share?app_id=536779657179021&display=page&href=https%3A%2F%2Fgithub.com%2Fsatwikkansal%2Fwtfpython"e=If%20you%20really%20think%20you%20know%20Python%2C%20think%20once%20more!)
+
+## Bạn có cần bản pdf của wtf hay không?
+
+I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details [here](https://satwikkansal.xyz/wtfpython-pdf/) to get them as soon as they are finished.
+
+Tôi nhận được một vài yêu cầu cho phiên bản pdf (và epub) của wtfpython. Nếu bạn muốn, bạn có thể thêm thông tin chi tiết của mình ở đây [here](https://satwikkansal.xyz/wtfpython-pdf/) để có phiên bản pdf sớm nhất.
+
+**That's all folks!** For upcoming content like this, you can add your email [here](https://www.satwikkansal.xyz/content-like-wtfpython/).
+
+*PS: On a sidenote, consider donating a dollar to [plant a tree](https://teamtrees.org/).*
diff --git a/docs/css/neoteroi-cards.css b/docs/css/neoteroi-cards.css
new file mode 100644
index 0000000..5a5152f
--- /dev/null
+++ b/docs/css/neoteroi-cards.css
@@ -0,0 +1,102 @@
+.nt-cards.nt-grid {
+ display: grid;
+ grid-auto-columns: 1fr;
+ gap: 0.5rem;
+ max-width: 100vw;
+ overflow-x: auto;
+ padding: 1px;
+}
+.nt-cards.nt-grid.cols-1 {
+ grid-template-columns: repeat(1, 1fr);
+}
+.nt-cards.nt-grid.cols-2 {
+ grid-template-columns: repeat(2, 1fr);
+}
+.nt-cards.nt-grid.cols-3 {
+ grid-template-columns: repeat(3, 1fr);
+}
+.nt-cards.nt-grid.cols-4 {
+ grid-template-columns: repeat(4, 1fr);
+}
+.nt-cards.nt-grid.cols-5 {
+ grid-template-columns: repeat(5, 1fr);
+}
+.nt-cards.nt-grid.cols-6 {
+ grid-template-columns: repeat(6, 1fr);
+}
+
+@media only screen and (max-width: 400px) {
+ .nt-cards.nt-grid {
+ grid-template-columns: repeat(1, 1fr) !important;
+ }
+}
+.nt-card {
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
+}
+.nt-card:hover {
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 3px 1px -2px rgba(0, 0, 0, 0.3), 0 1px 5px 0 rgba(0, 0, 0, 0.22);
+}
+
+[data-md-color-scheme=slate] .nt-card {
+ box-shadow: 0 2px 2px 0 rgba(4, 40, 33, 0.14), 0 3px 1px -2px rgba(40, 86, 94, 0.47), 0 1px 5px 0 rgba(139, 252, 255, 0.64);
+}
+[data-md-color-scheme=slate] .nt-card:hover {
+ box-shadow: 0 2px 2px 0 rgba(0, 255, 206, 0.14), 0 3px 1px -2px rgba(33, 156, 177, 0.47), 0 1px 5px 0 rgba(96, 251, 255, 0.64);
+}
+
+.nt-card > a {
+ color: var(--md-default-fg-color);
+}
+
+.nt-card > a > div {
+ cursor: pointer;
+}
+
+.nt-card {
+ padding: 5px;
+ margin-bottom: 0.5rem;
+}
+
+.nt-card-title {
+ font-size: 1rem;
+ font-weight: bold;
+ margin: 4px 0 8px 0;
+ line-height: 22px;
+}
+
+.nt-card-content {
+ padding: 0.4rem 0.8rem 0.8rem 0.8rem;
+}
+
+.nt-card-text {
+ font-size: 14px;
+ padding: 0;
+ margin: 0;
+}
+
+.nt-card .nt-card-image {
+ text-align: center;
+ border-radius: 2px;
+ background-position: center center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ min-height: 120px;
+}
+
+.nt-card .nt-card-image.tags img {
+ margin-top: 12px;
+}
+
+.nt-card .nt-card-image img {
+ height: 105px;
+ margin-top: 5px;
+}
+
+.nt-card a:hover,
+.nt-card a:focus {
+ color: var(--md-accent-fg-color);
+}
+
+.nt-card h2 {
+ margin: 0;
+}
diff --git a/docs/css/neoteroi-gantt.css b/docs/css/neoteroi-gantt.css
new file mode 100644
index 0000000..f93f202
--- /dev/null
+++ b/docs/css/neoteroi-gantt.css
@@ -0,0 +1,328 @@
+/**
+ * Extra CSS for the neoteroi.projects.gantt extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+:root {
+ --nt-scrollbar-color: #2751b0;
+ --nt-plan-actions-height: 24px;
+ --nt-units-background: #ff9800;
+ --nt-months-background: #2751b0;
+ --nt-plan-vertical-line-color: #a3a3a3ad;
+}
+
+.nt-pastello {
+ --nt-scrollbar-color: #9fb8f4;
+ --nt-units-background: #f5dc82;
+ --nt-months-background: #5b7fd1;
+}
+
+[data-md-color-scheme=slate] {
+ --nt-units-background: #003773;
+}
+[data-md-color-scheme=slate] .nt-pastello {
+ --nt-units-background: #3f4997;
+}
+
+.nt-plan-root {
+ min-height: 200px;
+ scrollbar-width: 20px;
+ scrollbar-color: var(--nt-scrollbar-color);
+ display: flex;
+}
+.nt-plan-root ::-webkit-scrollbar {
+ width: 20px;
+}
+.nt-plan-root ::-webkit-scrollbar-track {
+ box-shadow: inset 0 0 5px grey;
+ border-radius: 10px;
+}
+.nt-plan-root ::-webkit-scrollbar-thumb {
+ background: var(--nt-scrollbar-color);
+ border-radius: 10px;
+}
+.nt-plan-root .nt-plan {
+ flex: 80%;
+}
+.nt-plan-root.no-groups .nt-plan-periods {
+ padding-left: 0;
+}
+.nt-plan-root.no-groups .nt-plan-group-summary {
+ display: none;
+}
+.nt-plan-root .nt-timeline-dot.bigger {
+ top: -10px;
+}
+.nt-plan-root .nt-timeline-dot.bigger[title] {
+ cursor: help;
+}
+
+.nt-plan {
+ white-space: nowrap;
+ overflow-x: auto;
+ display: flex;
+}
+.nt-plan .ug-timeline-dot {
+ left: 368px;
+ top: -8px;
+ cursor: help;
+}
+
+.months {
+ display: flex;
+}
+
+.month {
+ flex: auto;
+ display: inline-block;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px inset;
+ background-color: var(--nt-months-background);
+ color: white;
+ text-transform: uppercase;
+ font-family: Roboto, Helvetica, Arial, sans-serif;
+ padding: 2px 5px;
+ font-size: 12px;
+ border: 1px solid #000;
+ width: 150px;
+ border-radius: 8px;
+}
+
+.nt-plan-group-activities {
+ flex: auto;
+ position: relative;
+}
+
+.nt-vline {
+ border-left: 1px dashed var(--nt-plan-vertical-line-color);
+ height: 100%;
+ left: 0;
+ position: absolute;
+ margin-left: -0.5px;
+ top: 0;
+ -webkit-transition: all 0.5s linear !important;
+ -moz-transition: all 0.5s linear !important;
+ -ms-transition: all 0.5s linear !important;
+ -o-transition: all 0.5s linear !important;
+ transition: all 0.5s linear !important;
+ z-index: -2;
+}
+
+.nt-plan-activity {
+ display: flex;
+ margin: 2px 0;
+ background-color: rgba(187, 187, 187, 0.2509803922);
+}
+
+.actions {
+ height: var(--nt-plan-actions-height);
+}
+
+.actions {
+ position: relative;
+}
+
+.period {
+ display: inline-block;
+ height: var(--nt-plan-actions-height);
+ width: 120px;
+ position: absolute;
+ left: 0px;
+ background: #1da1f2;
+ border-radius: 5px;
+ transition: all 0.5s;
+ cursor: help;
+ -webkit-transition: width 1s ease-in-out;
+ -moz-transition: width 1s ease-in-out;
+ -o-transition: width 1s ease-in-out;
+ transition: width 1s ease-in-out;
+}
+.period .nt-tooltip {
+ display: none;
+ top: 30px;
+ position: relative;
+ padding: 1rem;
+ text-align: center;
+ font-size: 12px;
+}
+.period:hover .nt-tooltip {
+ display: inline-block;
+}
+
+.period-0 {
+ left: 340px;
+ visibility: visible;
+ background-color: rgb(69, 97, 101);
+}
+
+.period-1 {
+ left: 40px;
+ visibility: visible;
+ background-color: green;
+}
+
+.period-2 {
+ left: 120px;
+ visibility: visible;
+ background-color: pink;
+ width: 80px;
+}
+
+.period-3 {
+ left: 190px;
+ visibility: visible;
+ background-color: darkred;
+ width: 150px;
+}
+
+.weeks > span,
+.days > span {
+ height: 25px;
+}
+
+.weeks > span {
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+}
+.weeks > span .week-text {
+ font-size: 10px;
+ position: absolute;
+ display: inline-block;
+ padding: 3px 4px;
+}
+
+.days {
+ z-index: -2;
+ position: relative;
+}
+
+.day-text {
+ font-size: 10px;
+ position: absolute;
+ display: inline-block;
+ padding: 3px 4px;
+}
+
+.period span {
+ font-size: 12px;
+ vertical-align: top;
+ margin-left: 4px;
+ color: black;
+ background: rgba(255, 255, 255, 0.6588235294);
+ border-radius: 6px;
+ padding: 0 4px;
+}
+
+.weeks,
+.days {
+ height: 20px;
+ display: flex;
+ box-sizing: content-box;
+}
+
+.months {
+ display: flex;
+}
+
+.week,
+.day {
+ height: 20px;
+ position: relative;
+ border: 1;
+ flex: auto;
+ border: 2px solid white;
+ border-radius: 4px;
+ background-color: var(--nt-units-background);
+ cursor: help;
+}
+
+.years {
+ display: flex;
+}
+
+.year {
+ text-align: center;
+ border-right: 1px solid var(--nt-plan-vertical-line-color);
+ font-weight: bold;
+}
+.year:first-child {
+ border-left: 1px solid var(--nt-plan-vertical-line-color);
+}
+.year:first-child:last-child {
+ width: 100%;
+}
+
+.quarters {
+ display: flex;
+}
+
+.quarter {
+ width: 12.5%;
+ text-align: center;
+ border-right: 1px solid var(--nt-plan-vertical-line-color);
+ font-weight: bold;
+}
+.quarter:first-child {
+ border-left: 1px solid var(--nt-plan-vertical-line-color);
+}
+
+.nt-plan-group {
+ margin: 20px 0;
+ position: relative;
+}
+
+.nt-plan-group {
+ display: flex;
+}
+
+.nt-plan-group-summary {
+ background: #2751b0;
+ width: 150px;
+ white-space: normal;
+ padding: 0.1rem 0.5rem;
+ border-radius: 5px;
+ color: #fff;
+ z-index: 3;
+}
+.nt-plan-group-summary p {
+ margin: 0;
+ padding: 0;
+ font-size: 0.6rem;
+ color: #fff;
+}
+
+.nt-plan-group-summary,
+.month,
+.period,
+.week,
+.day,
+.nt-tooltip {
+ border: 3px solid white;
+ box-shadow: 0 2px 3px -1px rgba(0, 0, 0, 0.2), 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
+}
+
+.nt-plan-periods {
+ padding-left: 150px;
+}
+
+.months {
+ z-index: 2;
+ position: relative;
+}
+
+.weeks {
+ position: relative;
+ top: -2px;
+ z-index: 0;
+}
+
+.month,
+.quarter,
+.year,
+.week,
+.day,
+.nt-tooltip {
+ font-family: Roboto, Helvetica, Arial, sans-serif;
+ box-sizing: border-box;
+}
diff --git a/docs/css/neoteroi-mkdocs.css b/docs/css/neoteroi-mkdocs.css
new file mode 100644
index 0000000..ee7d53a
--- /dev/null
+++ b/docs/css/neoteroi-mkdocs.css
@@ -0,0 +1,1399 @@
+/**
+ * All CSS for the neoteroi.projects.gantt extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+:root {
+ --nt-color-0: #CD853F;
+ --nt-color-1: #B22222;
+ --nt-color-2: #000080;
+ --nt-color-3: #4B0082;
+ --nt-color-4: #3CB371;
+ --nt-color-5: #D2B48C;
+ --nt-color-6: #FF00FF;
+ --nt-color-7: #98FB98;
+ --nt-color-8: #FFEBCD;
+ --nt-color-9: #2E8B57;
+ --nt-color-10: #6A5ACD;
+ --nt-color-11: #48D1CC;
+ --nt-color-12: #FFA500;
+ --nt-color-13: #F4A460;
+ --nt-color-14: #A52A2A;
+ --nt-color-15: #FFE4C4;
+ --nt-color-16: #FF4500;
+ --nt-color-17: #AFEEEE;
+ --nt-color-18: #FA8072;
+ --nt-color-19: #2F4F4F;
+ --nt-color-20: #FFDAB9;
+ --nt-color-21: #BC8F8F;
+ --nt-color-22: #FFC0CB;
+ --nt-color-23: #00FA9A;
+ --nt-color-24: #F0FFF0;
+ --nt-color-25: #FFFACD;
+ --nt-color-26: #F5F5F5;
+ --nt-color-27: #FF6347;
+ --nt-color-28: #FFFFF0;
+ --nt-color-29: #7FFFD4;
+ --nt-color-30: #E9967A;
+ --nt-color-31: #7B68EE;
+ --nt-color-32: #FFF8DC;
+ --nt-color-33: #0000CD;
+ --nt-color-34: #D2691E;
+ --nt-color-35: #708090;
+ --nt-color-36: #5F9EA0;
+ --nt-color-37: #008080;
+ --nt-color-38: #008000;
+ --nt-color-39: #FFE4E1;
+ --nt-color-40: #FFFF00;
+ --nt-color-41: #FFFAF0;
+ --nt-color-42: #DCDCDC;
+ --nt-color-43: #ADFF2F;
+ --nt-color-44: #ADD8E6;
+ --nt-color-45: #8B008B;
+ --nt-color-46: #7FFF00;
+ --nt-color-47: #800000;
+ --nt-color-48: #20B2AA;
+ --nt-color-49: #556B2F;
+ --nt-color-50: #778899;
+ --nt-color-51: #E6E6FA;
+ --nt-color-52: #FFFAFA;
+ --nt-color-53: #FF7F50;
+ --nt-color-54: #FF0000;
+ --nt-color-55: #F5DEB3;
+ --nt-color-56: #008B8B;
+ --nt-color-57: #66CDAA;
+ --nt-color-58: #808000;
+ --nt-color-59: #FAF0E6;
+ --nt-color-60: #00BFFF;
+ --nt-color-61: #C71585;
+ --nt-color-62: #00FFFF;
+ --nt-color-63: #8B4513;
+ --nt-color-64: #F0F8FF;
+ --nt-color-65: #FAEBD7;
+ --nt-color-66: #8B0000;
+ --nt-color-67: #4682B4;
+ --nt-color-68: #F0E68C;
+ --nt-color-69: #BDB76B;
+ --nt-color-70: #A0522D;
+ --nt-color-71: #FAFAD2;
+ --nt-color-72: #FFD700;
+ --nt-color-73: #DEB887;
+ --nt-color-74: #E0FFFF;
+ --nt-color-75: #8A2BE2;
+ --nt-color-76: #32CD32;
+ --nt-color-77: #87CEFA;
+ --nt-color-78: #00CED1;
+ --nt-color-79: #696969;
+ --nt-color-80: #DDA0DD;
+ --nt-color-81: #EE82EE;
+ --nt-color-82: #FFB6C1;
+ --nt-color-83: #8FBC8F;
+ --nt-color-84: #D8BFD8;
+ --nt-color-85: #9400D3;
+ --nt-color-86: #A9A9A9;
+ --nt-color-87: #FFFFE0;
+ --nt-color-88: #FFF5EE;
+ --nt-color-89: #FFF0F5;
+ --nt-color-90: #FFDEAD;
+ --nt-color-91: #800080;
+ --nt-color-92: #B0E0E6;
+ --nt-color-93: #9932CC;
+ --nt-color-94: #DAA520;
+ --nt-color-95: #F0FFFF;
+ --nt-color-96: #40E0D0;
+ --nt-color-97: #00FF7F;
+ --nt-color-98: #006400;
+ --nt-color-99: #808080;
+ --nt-color-100: #87CEEB;
+ --nt-color-101: #0000FF;
+ --nt-color-102: #6495ED;
+ --nt-color-103: #FDF5E6;
+ --nt-color-104: #B8860B;
+ --nt-color-105: #BA55D3;
+ --nt-color-106: #C0C0C0;
+ --nt-color-107: #000000;
+ --nt-color-108: #F08080;
+ --nt-color-109: #B0C4DE;
+ --nt-color-110: #00008B;
+ --nt-color-111: #6B8E23;
+ --nt-color-112: #FFE4B5;
+ --nt-color-113: #FFA07A;
+ --nt-color-114: #9ACD32;
+ --nt-color-115: #FFFFFF;
+ --nt-color-116: #F5F5DC;
+ --nt-color-117: #90EE90;
+ --nt-color-118: #1E90FF;
+ --nt-color-119: #7CFC00;
+ --nt-color-120: #FF69B4;
+ --nt-color-121: #F8F8FF;
+ --nt-color-122: #F5FFFA;
+ --nt-color-123: #00FF00;
+ --nt-color-124: #D3D3D3;
+ --nt-color-125: #DB7093;
+ --nt-color-126: #DA70D6;
+ --nt-color-127: #FF1493;
+ --nt-color-128: #228B22;
+ --nt-color-129: #FFEFD5;
+ --nt-color-130: #4169E1;
+ --nt-color-131: #191970;
+ --nt-color-132: #9370DB;
+ --nt-color-133: #483D8B;
+ --nt-color-134: #FF8C00;
+ --nt-color-135: #EEE8AA;
+ --nt-color-136: #CD5C5C;
+ --nt-color-137: #DC143C;
+}
+
+:root {
+ --nt-group-0-main: #000000;
+ --nt-group-0-dark: #FFFFFF;
+ --nt-group-0-light: #000000;
+ --nt-group-0-main-bg: #F44336;
+ --nt-group-0-dark-bg: #BA000D;
+ --nt-group-0-light-bg: #FF7961;
+ --nt-group-1-main: #000000;
+ --nt-group-1-dark: #FFFFFF;
+ --nt-group-1-light: #000000;
+ --nt-group-1-main-bg: #E91E63;
+ --nt-group-1-dark-bg: #B0003A;
+ --nt-group-1-light-bg: #FF6090;
+ --nt-group-2-main: #FFFFFF;
+ --nt-group-2-dark: #FFFFFF;
+ --nt-group-2-light: #000000;
+ --nt-group-2-main-bg: #9C27B0;
+ --nt-group-2-dark-bg: #6A0080;
+ --nt-group-2-light-bg: #D05CE3;
+ --nt-group-3-main: #FFFFFF;
+ --nt-group-3-dark: #FFFFFF;
+ --nt-group-3-light: #000000;
+ --nt-group-3-main-bg: #673AB7;
+ --nt-group-3-dark-bg: #320B86;
+ --nt-group-3-light-bg: #9A67EA;
+ --nt-group-4-main: #FFFFFF;
+ --nt-group-4-dark: #FFFFFF;
+ --nt-group-4-light: #000000;
+ --nt-group-4-main-bg: #3F51B5;
+ --nt-group-4-dark-bg: #002984;
+ --nt-group-4-light-bg: #757DE8;
+ --nt-group-5-main: #000000;
+ --nt-group-5-dark: #FFFFFF;
+ --nt-group-5-light: #000000;
+ --nt-group-5-main-bg: #2196F3;
+ --nt-group-5-dark-bg: #0069C0;
+ --nt-group-5-light-bg: #6EC6FF;
+ --nt-group-6-main: #000000;
+ --nt-group-6-dark: #FFFFFF;
+ --nt-group-6-light: #000000;
+ --nt-group-6-main-bg: #03A9F4;
+ --nt-group-6-dark-bg: #007AC1;
+ --nt-group-6-light-bg: #67DAFF;
+ --nt-group-7-main: #000000;
+ --nt-group-7-dark: #000000;
+ --nt-group-7-light: #000000;
+ --nt-group-7-main-bg: #00BCD4;
+ --nt-group-7-dark-bg: #008BA3;
+ --nt-group-7-light-bg: #62EFFF;
+ --nt-group-8-main: #000000;
+ --nt-group-8-dark: #FFFFFF;
+ --nt-group-8-light: #000000;
+ --nt-group-8-main-bg: #009688;
+ --nt-group-8-dark-bg: #00675B;
+ --nt-group-8-light-bg: #52C7B8;
+ --nt-group-9-main: #000000;
+ --nt-group-9-dark: #FFFFFF;
+ --nt-group-9-light: #000000;
+ --nt-group-9-main-bg: #4CAF50;
+ --nt-group-9-dark-bg: #087F23;
+ --nt-group-9-light-bg: #80E27E;
+ --nt-group-10-main: #000000;
+ --nt-group-10-dark: #000000;
+ --nt-group-10-light: #000000;
+ --nt-group-10-main-bg: #8BC34A;
+ --nt-group-10-dark-bg: #5A9216;
+ --nt-group-10-light-bg: #BEF67A;
+ --nt-group-11-main: #000000;
+ --nt-group-11-dark: #000000;
+ --nt-group-11-light: #000000;
+ --nt-group-11-main-bg: #CDDC39;
+ --nt-group-11-dark-bg: #99AA00;
+ --nt-group-11-light-bg: #FFFF6E;
+ --nt-group-12-main: #000000;
+ --nt-group-12-dark: #000000;
+ --nt-group-12-light: #000000;
+ --nt-group-12-main-bg: #FFEB3B;
+ --nt-group-12-dark-bg: #C8B900;
+ --nt-group-12-light-bg: #FFFF72;
+ --nt-group-13-main: #000000;
+ --nt-group-13-dark: #000000;
+ --nt-group-13-light: #000000;
+ --nt-group-13-main-bg: #FFC107;
+ --nt-group-13-dark-bg: #C79100;
+ --nt-group-13-light-bg: #FFF350;
+ --nt-group-14-main: #000000;
+ --nt-group-14-dark: #000000;
+ --nt-group-14-light: #000000;
+ --nt-group-14-main-bg: #FF9800;
+ --nt-group-14-dark-bg: #C66900;
+ --nt-group-14-light-bg: #FFC947;
+ --nt-group-15-main: #000000;
+ --nt-group-15-dark: #FFFFFF;
+ --nt-group-15-light: #000000;
+ --nt-group-15-main-bg: #FF5722;
+ --nt-group-15-dark-bg: #C41C00;
+ --nt-group-15-light-bg: #FF8A50;
+ --nt-group-16-main: #FFFFFF;
+ --nt-group-16-dark: #FFFFFF;
+ --nt-group-16-light: #000000;
+ --nt-group-16-main-bg: #795548;
+ --nt-group-16-dark-bg: #4B2C20;
+ --nt-group-16-light-bg: #A98274;
+ --nt-group-17-main: #000000;
+ --nt-group-17-dark: #FFFFFF;
+ --nt-group-17-light: #000000;
+ --nt-group-17-main-bg: #9E9E9E;
+ --nt-group-17-dark-bg: #707070;
+ --nt-group-17-light-bg: #CFCFCF;
+ --nt-group-18-main: #000000;
+ --nt-group-18-dark: #FFFFFF;
+ --nt-group-18-light: #000000;
+ --nt-group-18-main-bg: #607D8B;
+ --nt-group-18-dark-bg: #34515E;
+ --nt-group-18-light-bg: #8EACBB;
+}
+
+.nt-pastello {
+ --nt-group-0-main: #000000;
+ --nt-group-0-dark: #000000;
+ --nt-group-0-light: #000000;
+ --nt-group-0-main-bg: #EF9A9A;
+ --nt-group-0-dark-bg: #BA6B6C;
+ --nt-group-0-light-bg: #FFCCCB;
+ --nt-group-1-main: #000000;
+ --nt-group-1-dark: #000000;
+ --nt-group-1-light: #000000;
+ --nt-group-1-main-bg: #F48FB1;
+ --nt-group-1-dark-bg: #BF5F82;
+ --nt-group-1-light-bg: #FFC1E3;
+ --nt-group-2-main: #000000;
+ --nt-group-2-dark: #000000;
+ --nt-group-2-light: #000000;
+ --nt-group-2-main-bg: #CE93D8;
+ --nt-group-2-dark-bg: #9C64A6;
+ --nt-group-2-light-bg: #FFC4FF;
+ --nt-group-3-main: #000000;
+ --nt-group-3-dark: #000000;
+ --nt-group-3-light: #000000;
+ --nt-group-3-main-bg: #B39DDB;
+ --nt-group-3-dark-bg: #836FA9;
+ --nt-group-3-light-bg: #E6CEFF;
+ --nt-group-4-main: #000000;
+ --nt-group-4-dark: #000000;
+ --nt-group-4-light: #000000;
+ --nt-group-4-main-bg: #9FA8DA;
+ --nt-group-4-dark-bg: #6F79A8;
+ --nt-group-4-light-bg: #D1D9FF;
+ --nt-group-5-main: #000000;
+ --nt-group-5-dark: #000000;
+ --nt-group-5-light: #000000;
+ --nt-group-5-main-bg: #90CAF9;
+ --nt-group-5-dark-bg: #5D99C6;
+ --nt-group-5-light-bg: #C3FDFF;
+ --nt-group-6-main: #000000;
+ --nt-group-6-dark: #000000;
+ --nt-group-6-light: #000000;
+ --nt-group-6-main-bg: #81D4FA;
+ --nt-group-6-dark-bg: #4BA3C7;
+ --nt-group-6-light-bg: #B6FFFF;
+ --nt-group-7-main: #000000;
+ --nt-group-7-dark: #000000;
+ --nt-group-7-light: #000000;
+ --nt-group-7-main-bg: #80DEEA;
+ --nt-group-7-dark-bg: #4BACB8;
+ --nt-group-7-light-bg: #B4FFFF;
+ --nt-group-8-main: #000000;
+ --nt-group-8-dark: #000000;
+ --nt-group-8-light: #000000;
+ --nt-group-8-main-bg: #80CBC4;
+ --nt-group-8-dark-bg: #4F9A94;
+ --nt-group-8-light-bg: #B2FEF7;
+ --nt-group-9-main: #000000;
+ --nt-group-9-dark: #000000;
+ --nt-group-9-light: #000000;
+ --nt-group-9-main-bg: #A5D6A7;
+ --nt-group-9-dark-bg: #75A478;
+ --nt-group-9-light-bg: #D7FFD9;
+ --nt-group-10-main: #000000;
+ --nt-group-10-dark: #000000;
+ --nt-group-10-light: #000000;
+ --nt-group-10-main-bg: #C5E1A5;
+ --nt-group-10-dark-bg: #94AF76;
+ --nt-group-10-light-bg: #F8FFD7;
+ --nt-group-11-main: #000000;
+ --nt-group-11-dark: #000000;
+ --nt-group-11-light: #000000;
+ --nt-group-11-main-bg: #E6EE9C;
+ --nt-group-11-dark-bg: #B3BC6D;
+ --nt-group-11-light-bg: #FFFFCE;
+ --nt-group-12-main: #000000;
+ --nt-group-12-dark: #000000;
+ --nt-group-12-light: #000000;
+ --nt-group-12-main-bg: #FFF59D;
+ --nt-group-12-dark-bg: #CBC26D;
+ --nt-group-12-light-bg: #FFFFCF;
+ --nt-group-13-main: #000000;
+ --nt-group-13-dark: #000000;
+ --nt-group-13-light: #000000;
+ --nt-group-13-main-bg: #FFE082;
+ --nt-group-13-dark-bg: #CAAE53;
+ --nt-group-13-light-bg: #FFFFB3;
+ --nt-group-14-main: #000000;
+ --nt-group-14-dark: #000000;
+ --nt-group-14-light: #000000;
+ --nt-group-14-main-bg: #FFCC80;
+ --nt-group-14-dark-bg: #CA9B52;
+ --nt-group-14-light-bg: #FFFFB0;
+ --nt-group-15-main: #000000;
+ --nt-group-15-dark: #000000;
+ --nt-group-15-light: #000000;
+ --nt-group-15-main-bg: #FFAB91;
+ --nt-group-15-dark-bg: #C97B63;
+ --nt-group-15-light-bg: #FFDDC1;
+ --nt-group-16-main: #000000;
+ --nt-group-16-dark: #000000;
+ --nt-group-16-light: #000000;
+ --nt-group-16-main-bg: #BCAAA4;
+ --nt-group-16-dark-bg: #8C7B75;
+ --nt-group-16-light-bg: #EFDCD5;
+ --nt-group-17-main: #000000;
+ --nt-group-17-dark: #000000;
+ --nt-group-17-light: #000000;
+ --nt-group-17-main-bg: #EEEEEE;
+ --nt-group-17-dark-bg: #BCBCBC;
+ --nt-group-17-light-bg: #FFFFFF;
+ --nt-group-18-main: #000000;
+ --nt-group-18-dark: #000000;
+ --nt-group-18-light: #000000;
+ --nt-group-18-main-bg: #B0BEC5;
+ --nt-group-18-dark-bg: #808E95;
+ --nt-group-18-light-bg: #E2F1F8;
+}
+
+.nt-group-0 .nt-plan-group-summary,
+.nt-group-0 .nt-timeline-dot {
+ color: var(--nt-group-0-dark);
+ background-color: var(--nt-group-0-dark-bg);
+}
+.nt-group-0 .period {
+ color: var(--nt-group-0-main);
+ background-color: var(--nt-group-0-main-bg);
+}
+
+.nt-group-1 .nt-plan-group-summary,
+.nt-group-1 .nt-timeline-dot {
+ color: var(--nt-group-1-dark);
+ background-color: var(--nt-group-1-dark-bg);
+}
+.nt-group-1 .period {
+ color: var(--nt-group-1-main);
+ background-color: var(--nt-group-1-main-bg);
+}
+
+.nt-group-2 .nt-plan-group-summary,
+.nt-group-2 .nt-timeline-dot {
+ color: var(--nt-group-2-dark);
+ background-color: var(--nt-group-2-dark-bg);
+}
+.nt-group-2 .period {
+ color: var(--nt-group-2-main);
+ background-color: var(--nt-group-2-main-bg);
+}
+
+.nt-group-3 .nt-plan-group-summary,
+.nt-group-3 .nt-timeline-dot {
+ color: var(--nt-group-3-dark);
+ background-color: var(--nt-group-3-dark-bg);
+}
+.nt-group-3 .period {
+ color: var(--nt-group-3-main);
+ background-color: var(--nt-group-3-main-bg);
+}
+
+.nt-group-4 .nt-plan-group-summary,
+.nt-group-4 .nt-timeline-dot {
+ color: var(--nt-group-4-dark);
+ background-color: var(--nt-group-4-dark-bg);
+}
+.nt-group-4 .period {
+ color: var(--nt-group-4-main);
+ background-color: var(--nt-group-4-main-bg);
+}
+
+.nt-group-5 .nt-plan-group-summary,
+.nt-group-5 .nt-timeline-dot {
+ color: var(--nt-group-5-dark);
+ background-color: var(--nt-group-5-dark-bg);
+}
+.nt-group-5 .period {
+ color: var(--nt-group-5-main);
+ background-color: var(--nt-group-5-main-bg);
+}
+
+.nt-group-6 .nt-plan-group-summary,
+.nt-group-6 .nt-timeline-dot {
+ color: var(--nt-group-6-dark);
+ background-color: var(--nt-group-6-dark-bg);
+}
+.nt-group-6 .period {
+ color: var(--nt-group-6-main);
+ background-color: var(--nt-group-6-main-bg);
+}
+
+.nt-group-7 .nt-plan-group-summary,
+.nt-group-7 .nt-timeline-dot {
+ color: var(--nt-group-7-dark);
+ background-color: var(--nt-group-7-dark-bg);
+}
+.nt-group-7 .period {
+ color: var(--nt-group-7-main);
+ background-color: var(--nt-group-7-main-bg);
+}
+
+.nt-group-8 .nt-plan-group-summary,
+.nt-group-8 .nt-timeline-dot {
+ color: var(--nt-group-8-dark);
+ background-color: var(--nt-group-8-dark-bg);
+}
+.nt-group-8 .period {
+ color: var(--nt-group-8-main);
+ background-color: var(--nt-group-8-main-bg);
+}
+
+.nt-group-9 .nt-plan-group-summary,
+.nt-group-9 .nt-timeline-dot {
+ color: var(--nt-group-9-dark);
+ background-color: var(--nt-group-9-dark-bg);
+}
+.nt-group-9 .period {
+ color: var(--nt-group-9-main);
+ background-color: var(--nt-group-9-main-bg);
+}
+
+.nt-group-10 .nt-plan-group-summary,
+.nt-group-10 .nt-timeline-dot {
+ color: var(--nt-group-10-dark);
+ background-color: var(--nt-group-10-dark-bg);
+}
+.nt-group-10 .period {
+ color: var(--nt-group-10-main);
+ background-color: var(--nt-group-10-main-bg);
+}
+
+.nt-group-11 .nt-plan-group-summary,
+.nt-group-11 .nt-timeline-dot {
+ color: var(--nt-group-11-dark);
+ background-color: var(--nt-group-11-dark-bg);
+}
+.nt-group-11 .period {
+ color: var(--nt-group-11-main);
+ background-color: var(--nt-group-11-main-bg);
+}
+
+.nt-group-12 .nt-plan-group-summary,
+.nt-group-12 .nt-timeline-dot {
+ color: var(--nt-group-12-dark);
+ background-color: var(--nt-group-12-dark-bg);
+}
+.nt-group-12 .period {
+ color: var(--nt-group-12-main);
+ background-color: var(--nt-group-12-main-bg);
+}
+
+.nt-group-13 .nt-plan-group-summary,
+.nt-group-13 .nt-timeline-dot {
+ color: var(--nt-group-13-dark);
+ background-color: var(--nt-group-13-dark-bg);
+}
+.nt-group-13 .period {
+ color: var(--nt-group-13-main);
+ background-color: var(--nt-group-13-main-bg);
+}
+
+.nt-group-14 .nt-plan-group-summary,
+.nt-group-14 .nt-timeline-dot {
+ color: var(--nt-group-14-dark);
+ background-color: var(--nt-group-14-dark-bg);
+}
+.nt-group-14 .period {
+ color: var(--nt-group-14-main);
+ background-color: var(--nt-group-14-main-bg);
+}
+
+.nt-group-15 .nt-plan-group-summary,
+.nt-group-15 .nt-timeline-dot {
+ color: var(--nt-group-15-dark);
+ background-color: var(--nt-group-15-dark-bg);
+}
+.nt-group-15 .period {
+ color: var(--nt-group-15-main);
+ background-color: var(--nt-group-15-main-bg);
+}
+
+.nt-group-16 .nt-plan-group-summary,
+.nt-group-16 .nt-timeline-dot {
+ color: var(--nt-group-16-dark);
+ background-color: var(--nt-group-16-dark-bg);
+}
+.nt-group-16 .period {
+ color: var(--nt-group-16-main);
+ background-color: var(--nt-group-16-main-bg);
+}
+
+.nt-group-17 .nt-plan-group-summary,
+.nt-group-17 .nt-timeline-dot {
+ color: var(--nt-group-17-dark);
+ background-color: var(--nt-group-17-dark-bg);
+}
+.nt-group-17 .period {
+ color: var(--nt-group-17-main);
+ background-color: var(--nt-group-17-main-bg);
+}
+
+.nt-group-18 .nt-plan-group-summary,
+.nt-group-18 .nt-timeline-dot {
+ color: var(--nt-group-18-dark);
+ background-color: var(--nt-group-18-dark-bg);
+}
+.nt-group-18 .period {
+ color: var(--nt-group-18-main);
+ background-color: var(--nt-group-18-main-bg);
+}
+
+/**
+ * Extra CSS file for MkDocs and the neoteroi.timeline extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+.nt-error {
+ border: 2px dashed darkred;
+ padding: 0 1rem;
+ background: #faf9ba;
+ color: darkred;
+}
+
+.nt-timeline {
+ margin-top: 30px;
+}
+.nt-timeline .nt-timeline-title {
+ font-size: 1.1rem;
+ margin-top: 0;
+}
+.nt-timeline .nt-timeline-sub-title {
+ margin-top: 0;
+}
+.nt-timeline .nt-timeline-content {
+ font-size: 0.8rem;
+ border-bottom: 2px dashed #ccc;
+ padding-bottom: 1.2rem;
+}
+.nt-timeline.horizontal .nt-timeline-items {
+ flex-direction: row;
+ overflow-x: scroll;
+}
+.nt-timeline.horizontal .nt-timeline-items > div {
+ min-width: 400px;
+ margin-right: 50px;
+}
+.nt-timeline.horizontal.reverse .nt-timeline-items {
+ flex-direction: row-reverse;
+}
+.nt-timeline.horizontal.center .nt-timeline-before {
+ background-image: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal.center .nt-timeline-after {
+ background-image: linear-gradient(180deg, rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal.center .nt-timeline-items {
+ background-image: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal .nt-timeline-dot {
+ left: 50%;
+}
+.nt-timeline.horizontal .nt-timeline-dot:not(.bigger) {
+ top: calc(50% - 4px);
+}
+.nt-timeline.horizontal .nt-timeline-dot.bigger {
+ top: calc(50% - 15px);
+}
+.nt-timeline.vertical .nt-timeline-items {
+ flex-direction: column;
+}
+.nt-timeline.vertical.reverse .nt-timeline-items {
+ flex-direction: column-reverse;
+}
+.nt-timeline.vertical.center .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-dot {
+ left: calc(50% - 10px);
+}
+.nt-timeline.vertical.center .nt-timeline-dot:not(.bigger) {
+ top: 10px;
+}
+.nt-timeline.vertical.center .nt-timeline-dot.bigger {
+ left: calc(50% - 20px);
+}
+.nt-timeline.vertical.left {
+ padding-left: 100px;
+}
+.nt-timeline.vertical.left .nt-timeline-item {
+ padding-left: 70px;
+}
+.nt-timeline.vertical.left .nt-timeline-sub-title {
+ left: -100px;
+ width: 100px;
+}
+.nt-timeline.vertical.left .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-dot {
+ left: 21px;
+ top: 8px;
+}
+.nt-timeline.vertical.left .nt-timeline-dot.bigger {
+ top: 0px;
+ left: 10px;
+}
+.nt-timeline.vertical.right {
+ padding-right: 100px;
+}
+.nt-timeline.vertical.right .nt-timeline-sub-title {
+ right: -100px;
+ text-align: left;
+ width: 100px;
+}
+.nt-timeline.vertical.right .nt-timeline-item {
+ padding-right: 70px;
+}
+.nt-timeline.vertical.right .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-dot {
+ right: 21px;
+ top: 8px;
+}
+.nt-timeline.vertical.right .nt-timeline-dot.bigger {
+ top: 10px;
+ right: 10px;
+}
+
+.nt-timeline-items {
+ display: flex;
+ position: relative;
+}
+.nt-timeline-items > div {
+ min-height: 100px;
+ padding-top: 2px;
+ padding-bottom: 20px;
+}
+
+.nt-timeline-before {
+ content: "";
+ height: 15px;
+}
+
+.nt-timeline-after {
+ content: "";
+ height: 60px;
+ margin-bottom: 20px;
+}
+
+.nt-timeline-sub-title {
+ position: absolute;
+ width: 50%;
+ top: 4px;
+ font-size: 18px;
+ color: var(--nt-color-50);
+}
+
+[data-md-color-scheme=slate] .nt-timeline-sub-title {
+ color: var(--nt-color-51);
+}
+
+.nt-timeline-item {
+ position: relative;
+}
+
+.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item {
+ padding-left: calc(50% + 40px);
+}
+.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title {
+ left: 0;
+ padding-right: 40px;
+ text-align: right;
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) {
+ padding-left: calc(50% + 40px);
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title {
+ left: 0;
+ padding-right: 40px;
+ text-align: right;
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) {
+ text-align: right;
+ padding-right: calc(50% + 40px);
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title {
+ right: 0;
+ padding-left: 40px;
+ text-align: left;
+}
+
+.nt-timeline-dot {
+ position: relative;
+ width: 20px;
+ height: 20px;
+ border-radius: 100%;
+ background-color: #fc5b5b;
+ position: absolute;
+ top: 0px;
+ z-index: 2;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+ border: 3px solid white;
+}
+.nt-timeline-dot:not(.bigger) .icon {
+ font-size: 10px;
+}
+.nt-timeline-dot.bigger {
+ width: 40px;
+ height: 40px;
+ padding: 3px;
+}
+.nt-timeline-dot .icon {
+ color: white;
+}
+
+/* Fix for webkit (Chrome, Safari) */
+@supports not (-moz-appearance: none) {
+ /*
+ This fix is necessary, for some reason, to render the timeline properly
+ inside `details` elements used by pymdownx. Firefox doesn't need this fix,
+ it renders elements properly.
+ */
+ details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title,
+details .nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title {
+ left: -40px;
+ }
+ details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title {
+ right: -40px;
+ }
+ details .nt-timeline.vertical.center .nt-timeline-dot {
+ left: calc(50% - 12px);
+ }
+ details .nt-timeline-dot.bigger {
+ font-size: 1rem !important;
+ }
+}
+/* default colors */
+.nt-timeline-item:nth-child(0) .nt-timeline-dot {
+ background-color: var(--nt-color-0);
+}
+
+.nt-timeline-item:nth-child(1) .nt-timeline-dot {
+ background-color: var(--nt-color-1);
+}
+
+.nt-timeline-item:nth-child(2) .nt-timeline-dot {
+ background-color: var(--nt-color-2);
+}
+
+.nt-timeline-item:nth-child(3) .nt-timeline-dot {
+ background-color: var(--nt-color-3);
+}
+
+.nt-timeline-item:nth-child(4) .nt-timeline-dot {
+ background-color: var(--nt-color-4);
+}
+
+.nt-timeline-item:nth-child(5) .nt-timeline-dot {
+ background-color: var(--nt-color-5);
+}
+
+.nt-timeline-item:nth-child(6) .nt-timeline-dot {
+ background-color: var(--nt-color-6);
+}
+
+.nt-timeline-item:nth-child(7) .nt-timeline-dot {
+ background-color: var(--nt-color-7);
+}
+
+.nt-timeline-item:nth-child(8) .nt-timeline-dot {
+ background-color: var(--nt-color-8);
+}
+
+.nt-timeline-item:nth-child(9) .nt-timeline-dot {
+ background-color: var(--nt-color-9);
+}
+
+.nt-timeline-item:nth-child(10) .nt-timeline-dot {
+ background-color: var(--nt-color-10);
+}
+
+.nt-timeline-item:nth-child(11) .nt-timeline-dot {
+ background-color: var(--nt-color-11);
+}
+
+.nt-timeline-item:nth-child(12) .nt-timeline-dot {
+ background-color: var(--nt-color-12);
+}
+
+.nt-timeline-item:nth-child(13) .nt-timeline-dot {
+ background-color: var(--nt-color-13);
+}
+
+.nt-timeline-item:nth-child(14) .nt-timeline-dot {
+ background-color: var(--nt-color-14);
+}
+
+.nt-timeline-item:nth-child(15) .nt-timeline-dot {
+ background-color: var(--nt-color-15);
+}
+
+.nt-timeline-item:nth-child(16) .nt-timeline-dot {
+ background-color: var(--nt-color-16);
+}
+
+.nt-timeline-item:nth-child(17) .nt-timeline-dot {
+ background-color: var(--nt-color-17);
+}
+
+.nt-timeline-item:nth-child(18) .nt-timeline-dot {
+ background-color: var(--nt-color-18);
+}
+
+.nt-timeline-item:nth-child(19) .nt-timeline-dot {
+ background-color: var(--nt-color-19);
+}
+
+.nt-timeline-item:nth-child(20) .nt-timeline-dot {
+ background-color: var(--nt-color-20);
+}
+
+/**
+ * Extra CSS for the neoteroi.projects.gantt extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+:root {
+ --nt-scrollbar-color: #2751b0;
+ --nt-plan-actions-height: 24px;
+ --nt-units-background: #ff9800;
+ --nt-months-background: #2751b0;
+ --nt-plan-vertical-line-color: #a3a3a3ad;
+}
+
+.nt-pastello {
+ --nt-scrollbar-color: #9fb8f4;
+ --nt-units-background: #f5dc82;
+ --nt-months-background: #5b7fd1;
+}
+
+[data-md-color-scheme=slate] {
+ --nt-units-background: #003773;
+}
+[data-md-color-scheme=slate] .nt-pastello {
+ --nt-units-background: #3f4997;
+}
+
+.nt-plan-root {
+ min-height: 200px;
+ scrollbar-width: 20px;
+ scrollbar-color: var(--nt-scrollbar-color);
+ display: flex;
+}
+.nt-plan-root ::-webkit-scrollbar {
+ width: 20px;
+}
+.nt-plan-root ::-webkit-scrollbar-track {
+ box-shadow: inset 0 0 5px grey;
+ border-radius: 10px;
+}
+.nt-plan-root ::-webkit-scrollbar-thumb {
+ background: var(--nt-scrollbar-color);
+ border-radius: 10px;
+}
+.nt-plan-root .nt-plan {
+ flex: 80%;
+}
+.nt-plan-root.no-groups .nt-plan-periods {
+ padding-left: 0;
+}
+.nt-plan-root.no-groups .nt-plan-group-summary {
+ display: none;
+}
+.nt-plan-root .nt-timeline-dot.bigger {
+ top: -10px;
+}
+.nt-plan-root .nt-timeline-dot.bigger[title] {
+ cursor: help;
+}
+
+.nt-plan {
+ white-space: nowrap;
+ overflow-x: auto;
+ display: flex;
+}
+.nt-plan .ug-timeline-dot {
+ left: 368px;
+ top: -8px;
+ cursor: help;
+}
+
+.months {
+ display: flex;
+}
+
+.month {
+ flex: auto;
+ display: inline-block;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 3px 1px -2px, rgba(0, 0, 0, 0.14) 0px 2px 2px 0px, rgba(0, 0, 0, 0.12) 0px 1px 5px 0px inset;
+ background-color: var(--nt-months-background);
+ color: white;
+ text-transform: uppercase;
+ font-family: Roboto, Helvetica, Arial, sans-serif;
+ padding: 2px 5px;
+ font-size: 12px;
+ border: 1px solid #000;
+ width: 150px;
+ border-radius: 8px;
+}
+
+.nt-plan-group-activities {
+ flex: auto;
+ position: relative;
+}
+
+.nt-vline {
+ border-left: 1px dashed var(--nt-plan-vertical-line-color);
+ height: 100%;
+ left: 0;
+ position: absolute;
+ margin-left: -0.5px;
+ top: 0;
+ -webkit-transition: all 0.5s linear !important;
+ -moz-transition: all 0.5s linear !important;
+ -ms-transition: all 0.5s linear !important;
+ -o-transition: all 0.5s linear !important;
+ transition: all 0.5s linear !important;
+ z-index: -2;
+}
+
+.nt-plan-activity {
+ display: flex;
+ margin: 2px 0;
+ background-color: rgba(187, 187, 187, 0.2509803922);
+}
+
+.actions {
+ height: var(--nt-plan-actions-height);
+}
+
+.actions {
+ position: relative;
+}
+
+.period {
+ display: inline-block;
+ height: var(--nt-plan-actions-height);
+ width: 120px;
+ position: absolute;
+ left: 0px;
+ background: #1da1f2;
+ border-radius: 5px;
+ transition: all 0.5s;
+ cursor: help;
+ -webkit-transition: width 1s ease-in-out;
+ -moz-transition: width 1s ease-in-out;
+ -o-transition: width 1s ease-in-out;
+ transition: width 1s ease-in-out;
+}
+.period .nt-tooltip {
+ display: none;
+ top: 30px;
+ position: relative;
+ padding: 1rem;
+ text-align: center;
+ font-size: 12px;
+}
+.period:hover .nt-tooltip {
+ display: inline-block;
+}
+
+.period-0 {
+ left: 340px;
+ visibility: visible;
+ background-color: rgb(69, 97, 101);
+}
+
+.period-1 {
+ left: 40px;
+ visibility: visible;
+ background-color: green;
+}
+
+.period-2 {
+ left: 120px;
+ visibility: visible;
+ background-color: pink;
+ width: 80px;
+}
+
+.period-3 {
+ left: 190px;
+ visibility: visible;
+ background-color: darkred;
+ width: 150px;
+}
+
+.weeks > span,
+.days > span {
+ height: 25px;
+}
+
+.weeks > span {
+ display: inline-block;
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+}
+.weeks > span .week-text {
+ font-size: 10px;
+ position: absolute;
+ display: inline-block;
+ padding: 3px 4px;
+}
+
+.days {
+ z-index: -2;
+ position: relative;
+}
+
+.day-text {
+ font-size: 10px;
+ position: absolute;
+ display: inline-block;
+ padding: 3px 4px;
+}
+
+.period span {
+ font-size: 12px;
+ vertical-align: top;
+ margin-left: 4px;
+ color: black;
+ background: rgba(255, 255, 255, 0.6588235294);
+ border-radius: 6px;
+ padding: 0 4px;
+}
+
+.weeks,
+.days {
+ height: 20px;
+ display: flex;
+ box-sizing: content-box;
+}
+
+.months {
+ display: flex;
+}
+
+.week,
+.day {
+ height: 20px;
+ position: relative;
+ border: 1;
+ flex: auto;
+ border: 2px solid white;
+ border-radius: 4px;
+ background-color: var(--nt-units-background);
+ cursor: help;
+}
+
+.years {
+ display: flex;
+}
+
+.year {
+ text-align: center;
+ border-right: 1px solid var(--nt-plan-vertical-line-color);
+ font-weight: bold;
+}
+.year:first-child {
+ border-left: 1px solid var(--nt-plan-vertical-line-color);
+}
+.year:first-child:last-child {
+ width: 100%;
+}
+
+.quarters {
+ display: flex;
+}
+
+.quarter {
+ width: 12.5%;
+ text-align: center;
+ border-right: 1px solid var(--nt-plan-vertical-line-color);
+ font-weight: bold;
+}
+.quarter:first-child {
+ border-left: 1px solid var(--nt-plan-vertical-line-color);
+}
+
+.nt-plan-group {
+ margin: 20px 0;
+ position: relative;
+}
+
+.nt-plan-group {
+ display: flex;
+}
+
+.nt-plan-group-summary {
+ background: #2751b0;
+ width: 150px;
+ white-space: normal;
+ padding: 0.1rem 0.5rem;
+ border-radius: 5px;
+ color: #fff;
+ z-index: 3;
+}
+.nt-plan-group-summary p {
+ margin: 0;
+ padding: 0;
+ font-size: 0.6rem;
+ color: #fff;
+}
+
+.nt-plan-group-summary,
+.month,
+.period,
+.week,
+.day,
+.nt-tooltip {
+ border: 3px solid white;
+ box-shadow: 0 2px 3px -1px rgba(0, 0, 0, 0.2), 0 3px 3px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
+}
+
+.nt-plan-periods {
+ padding-left: 150px;
+}
+
+.months {
+ z-index: 2;
+ position: relative;
+}
+
+.weeks {
+ position: relative;
+ top: -2px;
+ z-index: 0;
+}
+
+.month,
+.quarter,
+.year,
+.week,
+.day,
+.nt-tooltip {
+ font-family: Roboto, Helvetica, Arial, sans-serif;
+ box-sizing: border-box;
+}
+
+.nt-cards.nt-grid {
+ display: grid;
+ grid-auto-columns: 1fr;
+ gap: 0.5rem;
+ max-width: 100vw;
+ overflow-x: auto;
+ padding: 1px;
+}
+.nt-cards.nt-grid.cols-1 {
+ grid-template-columns: repeat(1, 1fr);
+}
+.nt-cards.nt-grid.cols-2 {
+ grid-template-columns: repeat(2, 1fr);
+}
+.nt-cards.nt-grid.cols-3 {
+ grid-template-columns: repeat(3, 1fr);
+}
+.nt-cards.nt-grid.cols-4 {
+ grid-template-columns: repeat(4, 1fr);
+}
+.nt-cards.nt-grid.cols-5 {
+ grid-template-columns: repeat(5, 1fr);
+}
+.nt-cards.nt-grid.cols-6 {
+ grid-template-columns: repeat(6, 1fr);
+}
+
+@media only screen and (max-width: 400px) {
+ .nt-cards.nt-grid {
+ grid-template-columns: repeat(1, 1fr) !important;
+ }
+}
+.nt-card {
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
+}
+.nt-card:hover {
+ box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.24), 0 3px 1px -2px rgba(0, 0, 0, 0.3), 0 1px 5px 0 rgba(0, 0, 0, 0.22);
+}
+
+[data-md-color-scheme=slate] .nt-card {
+ box-shadow: 0 2px 2px 0 rgba(4, 40, 33, 0.14), 0 3px 1px -2px rgba(40, 86, 94, 0.47), 0 1px 5px 0 rgba(139, 252, 255, 0.64);
+}
+[data-md-color-scheme=slate] .nt-card:hover {
+ box-shadow: 0 2px 2px 0 rgba(0, 255, 206, 0.14), 0 3px 1px -2px rgba(33, 156, 177, 0.47), 0 1px 5px 0 rgba(96, 251, 255, 0.64);
+}
+
+.nt-card > a {
+ color: var(--md-default-fg-color);
+}
+
+.nt-card > a > div {
+ cursor: pointer;
+}
+
+.nt-card {
+ padding: 5px;
+ margin-bottom: 0.5rem;
+}
+
+.nt-card-title {
+ font-size: 1rem;
+ font-weight: bold;
+ margin: 4px 0 8px 0;
+ line-height: 22px;
+}
+
+.nt-card-content {
+ padding: 0.4rem 0.8rem 0.8rem 0.8rem;
+}
+
+.nt-card-text {
+ font-size: 14px;
+ padding: 0;
+ margin: 0;
+}
+
+.nt-card .nt-card-image {
+ text-align: center;
+ border-radius: 2px;
+ background-position: center center;
+ background-size: cover;
+ background-repeat: no-repeat;
+ min-height: 120px;
+}
+
+.nt-card .nt-card-image.tags img {
+ margin-top: 12px;
+}
+
+.nt-card .nt-card-image img {
+ height: 105px;
+ margin-top: 5px;
+}
+
+.nt-card a:hover,
+.nt-card a:focus {
+ color: var(--md-accent-fg-color);
+}
+
+.nt-card h2 {
+ margin: 0;
+}
+
+/**
+ * Extra CSS file recommended for MkDocs and neoteroi.spantable extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+.span-table-wrapper table {
+ border-collapse: collapse;
+ margin-bottom: 2rem;
+ border-radius: 0.1rem;
+}
+
+.span-table td,
+.span-table th {
+ padding: 0.2rem;
+ background-color: var(--md-default-bg-color);
+ font-size: 0.64rem;
+ max-width: 100%;
+ overflow: auto;
+ touch-action: auto;
+ border-top: 0.05rem solid var(--md-typeset-table-color);
+ padding: 0.9375em 1.25em;
+ vertical-align: top;
+}
+
+.span-table tr:first-child td {
+ font-weight: 700;
+ min-width: 5rem;
+ padding: 0.9375em 1.25em;
+ vertical-align: top;
+}
+
+.span-table td:first-child {
+ border-left: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table td:last-child {
+ border-right: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table tr:last-child {
+ border-bottom: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table [colspan],
+.span-table [rowspan] {
+ font-weight: bold;
+ border: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table tr:not(:first-child):hover td:not([colspan]):not([rowspan]),
+.span-table td[colspan]:hover,
+.span-table td[rowspan]:hover {
+ background-color: rgba(0, 0, 0, 0.035);
+ box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset;
+ transition: background-color 125ms;
+}
diff --git a/docs/css/neoteroi-mkdocs.min.css b/docs/css/neoteroi-mkdocs.min.css
new file mode 100644
index 0000000..ea55c67
--- /dev/null
+++ b/docs/css/neoteroi-mkdocs.min.css
@@ -0,0 +1 @@
+:root{--nt-color-0: #CD853F;--nt-color-1: #B22222;--nt-color-2: #000080;--nt-color-3: #4B0082;--nt-color-4: #3CB371;--nt-color-5: #D2B48C;--nt-color-6: #FF00FF;--nt-color-7: #98FB98;--nt-color-8: #FFEBCD;--nt-color-9: #2E8B57;--nt-color-10: #6A5ACD;--nt-color-11: #48D1CC;--nt-color-12: #FFA500;--nt-color-13: #F4A460;--nt-color-14: #A52A2A;--nt-color-15: #FFE4C4;--nt-color-16: #FF4500;--nt-color-17: #AFEEEE;--nt-color-18: #FA8072;--nt-color-19: #2F4F4F;--nt-color-20: #FFDAB9;--nt-color-21: #BC8F8F;--nt-color-22: #FFC0CB;--nt-color-23: #00FA9A;--nt-color-24: #F0FFF0;--nt-color-25: #FFFACD;--nt-color-26: #F5F5F5;--nt-color-27: #FF6347;--nt-color-28: #FFFFF0;--nt-color-29: #7FFFD4;--nt-color-30: #E9967A;--nt-color-31: #7B68EE;--nt-color-32: #FFF8DC;--nt-color-33: #0000CD;--nt-color-34: #D2691E;--nt-color-35: #708090;--nt-color-36: #5F9EA0;--nt-color-37: #008080;--nt-color-38: #008000;--nt-color-39: #FFE4E1;--nt-color-40: #FFFF00;--nt-color-41: #FFFAF0;--nt-color-42: #DCDCDC;--nt-color-43: #ADFF2F;--nt-color-44: #ADD8E6;--nt-color-45: #8B008B;--nt-color-46: #7FFF00;--nt-color-47: #800000;--nt-color-48: #20B2AA;--nt-color-49: #556B2F;--nt-color-50: #778899;--nt-color-51: #E6E6FA;--nt-color-52: #FFFAFA;--nt-color-53: #FF7F50;--nt-color-54: #FF0000;--nt-color-55: #F5DEB3;--nt-color-56: #008B8B;--nt-color-57: #66CDAA;--nt-color-58: #808000;--nt-color-59: #FAF0E6;--nt-color-60: #00BFFF;--nt-color-61: #C71585;--nt-color-62: #00FFFF;--nt-color-63: #8B4513;--nt-color-64: #F0F8FF;--nt-color-65: #FAEBD7;--nt-color-66: #8B0000;--nt-color-67: #4682B4;--nt-color-68: #F0E68C;--nt-color-69: #BDB76B;--nt-color-70: #A0522D;--nt-color-71: #FAFAD2;--nt-color-72: #FFD700;--nt-color-73: #DEB887;--nt-color-74: #E0FFFF;--nt-color-75: #8A2BE2;--nt-color-76: #32CD32;--nt-color-77: #87CEFA;--nt-color-78: #00CED1;--nt-color-79: #696969;--nt-color-80: #DDA0DD;--nt-color-81: #EE82EE;--nt-color-82: #FFB6C1;--nt-color-83: #8FBC8F;--nt-color-84: #D8BFD8;--nt-color-85: #9400D3;--nt-color-86: #A9A9A9;--nt-color-87: #FFFFE0;--nt-color-88: #FFF5EE;--nt-color-89: #FFF0F5;--nt-color-90: #FFDEAD;--nt-color-91: #800080;--nt-color-92: #B0E0E6;--nt-color-93: #9932CC;--nt-color-94: #DAA520;--nt-color-95: #F0FFFF;--nt-color-96: #40E0D0;--nt-color-97: #00FF7F;--nt-color-98: #006400;--nt-color-99: #808080;--nt-color-100: #87CEEB;--nt-color-101: #0000FF;--nt-color-102: #6495ED;--nt-color-103: #FDF5E6;--nt-color-104: #B8860B;--nt-color-105: #BA55D3;--nt-color-106: #C0C0C0;--nt-color-107: #000000;--nt-color-108: #F08080;--nt-color-109: #B0C4DE;--nt-color-110: #00008B;--nt-color-111: #6B8E23;--nt-color-112: #FFE4B5;--nt-color-113: #FFA07A;--nt-color-114: #9ACD32;--nt-color-115: #FFFFFF;--nt-color-116: #F5F5DC;--nt-color-117: #90EE90;--nt-color-118: #1E90FF;--nt-color-119: #7CFC00;--nt-color-120: #FF69B4;--nt-color-121: #F8F8FF;--nt-color-122: #F5FFFA;--nt-color-123: #00FF00;--nt-color-124: #D3D3D3;--nt-color-125: #DB7093;--nt-color-126: #DA70D6;--nt-color-127: #FF1493;--nt-color-128: #228B22;--nt-color-129: #FFEFD5;--nt-color-130: #4169E1;--nt-color-131: #191970;--nt-color-132: #9370DB;--nt-color-133: #483D8B;--nt-color-134: #FF8C00;--nt-color-135: #EEE8AA;--nt-color-136: #CD5C5C;--nt-color-137: #DC143C}:root{--nt-group-0-main: #000000;--nt-group-0-dark: #FFFFFF;--nt-group-0-light: #000000;--nt-group-0-main-bg: #F44336;--nt-group-0-dark-bg: #BA000D;--nt-group-0-light-bg: #FF7961;--nt-group-1-main: #000000;--nt-group-1-dark: #FFFFFF;--nt-group-1-light: #000000;--nt-group-1-main-bg: #E91E63;--nt-group-1-dark-bg: #B0003A;--nt-group-1-light-bg: #FF6090;--nt-group-2-main: #FFFFFF;--nt-group-2-dark: #FFFFFF;--nt-group-2-light: #000000;--nt-group-2-main-bg: #9C27B0;--nt-group-2-dark-bg: #6A0080;--nt-group-2-light-bg: #D05CE3;--nt-group-3-main: #FFFFFF;--nt-group-3-dark: #FFFFFF;--nt-group-3-light: #000000;--nt-group-3-main-bg: #673AB7;--nt-group-3-dark-bg: #320B86;--nt-group-3-light-bg: #9A67EA;--nt-group-4-main: #FFFFFF;--nt-group-4-dark: #FFFFFF;--nt-group-4-light: #000000;--nt-group-4-main-bg: #3F51B5;--nt-group-4-dark-bg: #002984;--nt-group-4-light-bg: #757DE8;--nt-group-5-main: #000000;--nt-group-5-dark: #FFFFFF;--nt-group-5-light: #000000;--nt-group-5-main-bg: #2196F3;--nt-group-5-dark-bg: #0069C0;--nt-group-5-light-bg: #6EC6FF;--nt-group-6-main: #000000;--nt-group-6-dark: #FFFFFF;--nt-group-6-light: #000000;--nt-group-6-main-bg: #03A9F4;--nt-group-6-dark-bg: #007AC1;--nt-group-6-light-bg: #67DAFF;--nt-group-7-main: #000000;--nt-group-7-dark: #000000;--nt-group-7-light: #000000;--nt-group-7-main-bg: #00BCD4;--nt-group-7-dark-bg: #008BA3;--nt-group-7-light-bg: #62EFFF;--nt-group-8-main: #000000;--nt-group-8-dark: #FFFFFF;--nt-group-8-light: #000000;--nt-group-8-main-bg: #009688;--nt-group-8-dark-bg: #00675B;--nt-group-8-light-bg: #52C7B8;--nt-group-9-main: #000000;--nt-group-9-dark: #FFFFFF;--nt-group-9-light: #000000;--nt-group-9-main-bg: #4CAF50;--nt-group-9-dark-bg: #087F23;--nt-group-9-light-bg: #80E27E;--nt-group-10-main: #000000;--nt-group-10-dark: #000000;--nt-group-10-light: #000000;--nt-group-10-main-bg: #8BC34A;--nt-group-10-dark-bg: #5A9216;--nt-group-10-light-bg: #BEF67A;--nt-group-11-main: #000000;--nt-group-11-dark: #000000;--nt-group-11-light: #000000;--nt-group-11-main-bg: #CDDC39;--nt-group-11-dark-bg: #99AA00;--nt-group-11-light-bg: #FFFF6E;--nt-group-12-main: #000000;--nt-group-12-dark: #000000;--nt-group-12-light: #000000;--nt-group-12-main-bg: #FFEB3B;--nt-group-12-dark-bg: #C8B900;--nt-group-12-light-bg: #FFFF72;--nt-group-13-main: #000000;--nt-group-13-dark: #000000;--nt-group-13-light: #000000;--nt-group-13-main-bg: #FFC107;--nt-group-13-dark-bg: #C79100;--nt-group-13-light-bg: #FFF350;--nt-group-14-main: #000000;--nt-group-14-dark: #000000;--nt-group-14-light: #000000;--nt-group-14-main-bg: #FF9800;--nt-group-14-dark-bg: #C66900;--nt-group-14-light-bg: #FFC947;--nt-group-15-main: #000000;--nt-group-15-dark: #FFFFFF;--nt-group-15-light: #000000;--nt-group-15-main-bg: #FF5722;--nt-group-15-dark-bg: #C41C00;--nt-group-15-light-bg: #FF8A50;--nt-group-16-main: #FFFFFF;--nt-group-16-dark: #FFFFFF;--nt-group-16-light: #000000;--nt-group-16-main-bg: #795548;--nt-group-16-dark-bg: #4B2C20;--nt-group-16-light-bg: #A98274;--nt-group-17-main: #000000;--nt-group-17-dark: #FFFFFF;--nt-group-17-light: #000000;--nt-group-17-main-bg: #9E9E9E;--nt-group-17-dark-bg: #707070;--nt-group-17-light-bg: #CFCFCF;--nt-group-18-main: #000000;--nt-group-18-dark: #FFFFFF;--nt-group-18-light: #000000;--nt-group-18-main-bg: #607D8B;--nt-group-18-dark-bg: #34515E;--nt-group-18-light-bg: #8EACBB}.nt-pastello{--nt-group-0-main: #000000;--nt-group-0-dark: #000000;--nt-group-0-light: #000000;--nt-group-0-main-bg: #EF9A9A;--nt-group-0-dark-bg: #BA6B6C;--nt-group-0-light-bg: #FFCCCB;--nt-group-1-main: #000000;--nt-group-1-dark: #000000;--nt-group-1-light: #000000;--nt-group-1-main-bg: #F48FB1;--nt-group-1-dark-bg: #BF5F82;--nt-group-1-light-bg: #FFC1E3;--nt-group-2-main: #000000;--nt-group-2-dark: #000000;--nt-group-2-light: #000000;--nt-group-2-main-bg: #CE93D8;--nt-group-2-dark-bg: #9C64A6;--nt-group-2-light-bg: #FFC4FF;--nt-group-3-main: #000000;--nt-group-3-dark: #000000;--nt-group-3-light: #000000;--nt-group-3-main-bg: #B39DDB;--nt-group-3-dark-bg: #836FA9;--nt-group-3-light-bg: #E6CEFF;--nt-group-4-main: #000000;--nt-group-4-dark: #000000;--nt-group-4-light: #000000;--nt-group-4-main-bg: #9FA8DA;--nt-group-4-dark-bg: #6F79A8;--nt-group-4-light-bg: #D1D9FF;--nt-group-5-main: #000000;--nt-group-5-dark: #000000;--nt-group-5-light: #000000;--nt-group-5-main-bg: #90CAF9;--nt-group-5-dark-bg: #5D99C6;--nt-group-5-light-bg: #C3FDFF;--nt-group-6-main: #000000;--nt-group-6-dark: #000000;--nt-group-6-light: #000000;--nt-group-6-main-bg: #81D4FA;--nt-group-6-dark-bg: #4BA3C7;--nt-group-6-light-bg: #B6FFFF;--nt-group-7-main: #000000;--nt-group-7-dark: #000000;--nt-group-7-light: #000000;--nt-group-7-main-bg: #80DEEA;--nt-group-7-dark-bg: #4BACB8;--nt-group-7-light-bg: #B4FFFF;--nt-group-8-main: #000000;--nt-group-8-dark: #000000;--nt-group-8-light: #000000;--nt-group-8-main-bg: #80CBC4;--nt-group-8-dark-bg: #4F9A94;--nt-group-8-light-bg: #B2FEF7;--nt-group-9-main: #000000;--nt-group-9-dark: #000000;--nt-group-9-light: #000000;--nt-group-9-main-bg: #A5D6A7;--nt-group-9-dark-bg: #75A478;--nt-group-9-light-bg: #D7FFD9;--nt-group-10-main: #000000;--nt-group-10-dark: #000000;--nt-group-10-light: #000000;--nt-group-10-main-bg: #C5E1A5;--nt-group-10-dark-bg: #94AF76;--nt-group-10-light-bg: #F8FFD7;--nt-group-11-main: #000000;--nt-group-11-dark: #000000;--nt-group-11-light: #000000;--nt-group-11-main-bg: #E6EE9C;--nt-group-11-dark-bg: #B3BC6D;--nt-group-11-light-bg: #FFFFCE;--nt-group-12-main: #000000;--nt-group-12-dark: #000000;--nt-group-12-light: #000000;--nt-group-12-main-bg: #FFF59D;--nt-group-12-dark-bg: #CBC26D;--nt-group-12-light-bg: #FFFFCF;--nt-group-13-main: #000000;--nt-group-13-dark: #000000;--nt-group-13-light: #000000;--nt-group-13-main-bg: #FFE082;--nt-group-13-dark-bg: #CAAE53;--nt-group-13-light-bg: #FFFFB3;--nt-group-14-main: #000000;--nt-group-14-dark: #000000;--nt-group-14-light: #000000;--nt-group-14-main-bg: #FFCC80;--nt-group-14-dark-bg: #CA9B52;--nt-group-14-light-bg: #FFFFB0;--nt-group-15-main: #000000;--nt-group-15-dark: #000000;--nt-group-15-light: #000000;--nt-group-15-main-bg: #FFAB91;--nt-group-15-dark-bg: #C97B63;--nt-group-15-light-bg: #FFDDC1;--nt-group-16-main: #000000;--nt-group-16-dark: #000000;--nt-group-16-light: #000000;--nt-group-16-main-bg: #BCAAA4;--nt-group-16-dark-bg: #8C7B75;--nt-group-16-light-bg: #EFDCD5;--nt-group-17-main: #000000;--nt-group-17-dark: #000000;--nt-group-17-light: #000000;--nt-group-17-main-bg: #EEEEEE;--nt-group-17-dark-bg: #BCBCBC;--nt-group-17-light-bg: #FFFFFF;--nt-group-18-main: #000000;--nt-group-18-dark: #000000;--nt-group-18-light: #000000;--nt-group-18-main-bg: #B0BEC5;--nt-group-18-dark-bg: #808E95;--nt-group-18-light-bg: #E2F1F8}.nt-group-0 .nt-plan-group-summary,.nt-group-0 .nt-timeline-dot{color:var(--nt-group-0-dark);background-color:var(--nt-group-0-dark-bg)}.nt-group-0 .period{color:var(--nt-group-0-main);background-color:var(--nt-group-0-main-bg)}.nt-group-1 .nt-plan-group-summary,.nt-group-1 .nt-timeline-dot{color:var(--nt-group-1-dark);background-color:var(--nt-group-1-dark-bg)}.nt-group-1 .period{color:var(--nt-group-1-main);background-color:var(--nt-group-1-main-bg)}.nt-group-2 .nt-plan-group-summary,.nt-group-2 .nt-timeline-dot{color:var(--nt-group-2-dark);background-color:var(--nt-group-2-dark-bg)}.nt-group-2 .period{color:var(--nt-group-2-main);background-color:var(--nt-group-2-main-bg)}.nt-group-3 .nt-plan-group-summary,.nt-group-3 .nt-timeline-dot{color:var(--nt-group-3-dark);background-color:var(--nt-group-3-dark-bg)}.nt-group-3 .period{color:var(--nt-group-3-main);background-color:var(--nt-group-3-main-bg)}.nt-group-4 .nt-plan-group-summary,.nt-group-4 .nt-timeline-dot{color:var(--nt-group-4-dark);background-color:var(--nt-group-4-dark-bg)}.nt-group-4 .period{color:var(--nt-group-4-main);background-color:var(--nt-group-4-main-bg)}.nt-group-5 .nt-plan-group-summary,.nt-group-5 .nt-timeline-dot{color:var(--nt-group-5-dark);background-color:var(--nt-group-5-dark-bg)}.nt-group-5 .period{color:var(--nt-group-5-main);background-color:var(--nt-group-5-main-bg)}.nt-group-6 .nt-plan-group-summary,.nt-group-6 .nt-timeline-dot{color:var(--nt-group-6-dark);background-color:var(--nt-group-6-dark-bg)}.nt-group-6 .period{color:var(--nt-group-6-main);background-color:var(--nt-group-6-main-bg)}.nt-group-7 .nt-plan-group-summary,.nt-group-7 .nt-timeline-dot{color:var(--nt-group-7-dark);background-color:var(--nt-group-7-dark-bg)}.nt-group-7 .period{color:var(--nt-group-7-main);background-color:var(--nt-group-7-main-bg)}.nt-group-8 .nt-plan-group-summary,.nt-group-8 .nt-timeline-dot{color:var(--nt-group-8-dark);background-color:var(--nt-group-8-dark-bg)}.nt-group-8 .period{color:var(--nt-group-8-main);background-color:var(--nt-group-8-main-bg)}.nt-group-9 .nt-plan-group-summary,.nt-group-9 .nt-timeline-dot{color:var(--nt-group-9-dark);background-color:var(--nt-group-9-dark-bg)}.nt-group-9 .period{color:var(--nt-group-9-main);background-color:var(--nt-group-9-main-bg)}.nt-group-10 .nt-plan-group-summary,.nt-group-10 .nt-timeline-dot{color:var(--nt-group-10-dark);background-color:var(--nt-group-10-dark-bg)}.nt-group-10 .period{color:var(--nt-group-10-main);background-color:var(--nt-group-10-main-bg)}.nt-group-11 .nt-plan-group-summary,.nt-group-11 .nt-timeline-dot{color:var(--nt-group-11-dark);background-color:var(--nt-group-11-dark-bg)}.nt-group-11 .period{color:var(--nt-group-11-main);background-color:var(--nt-group-11-main-bg)}.nt-group-12 .nt-plan-group-summary,.nt-group-12 .nt-timeline-dot{color:var(--nt-group-12-dark);background-color:var(--nt-group-12-dark-bg)}.nt-group-12 .period{color:var(--nt-group-12-main);background-color:var(--nt-group-12-main-bg)}.nt-group-13 .nt-plan-group-summary,.nt-group-13 .nt-timeline-dot{color:var(--nt-group-13-dark);background-color:var(--nt-group-13-dark-bg)}.nt-group-13 .period{color:var(--nt-group-13-main);background-color:var(--nt-group-13-main-bg)}.nt-group-14 .nt-plan-group-summary,.nt-group-14 .nt-timeline-dot{color:var(--nt-group-14-dark);background-color:var(--nt-group-14-dark-bg)}.nt-group-14 .period{color:var(--nt-group-14-main);background-color:var(--nt-group-14-main-bg)}.nt-group-15 .nt-plan-group-summary,.nt-group-15 .nt-timeline-dot{color:var(--nt-group-15-dark);background-color:var(--nt-group-15-dark-bg)}.nt-group-15 .period{color:var(--nt-group-15-main);background-color:var(--nt-group-15-main-bg)}.nt-group-16 .nt-plan-group-summary,.nt-group-16 .nt-timeline-dot{color:var(--nt-group-16-dark);background-color:var(--nt-group-16-dark-bg)}.nt-group-16 .period{color:var(--nt-group-16-main);background-color:var(--nt-group-16-main-bg)}.nt-group-17 .nt-plan-group-summary,.nt-group-17 .nt-timeline-dot{color:var(--nt-group-17-dark);background-color:var(--nt-group-17-dark-bg)}.nt-group-17 .period{color:var(--nt-group-17-main);background-color:var(--nt-group-17-main-bg)}.nt-group-18 .nt-plan-group-summary,.nt-group-18 .nt-timeline-dot{color:var(--nt-group-18-dark);background-color:var(--nt-group-18-dark-bg)}.nt-group-18 .period{color:var(--nt-group-18-main);background-color:var(--nt-group-18-main-bg)}.nt-error{border:2px dashed darkred;padding:0 1rem;background:#faf9ba;color:darkred}.nt-timeline{margin-top:30px}.nt-timeline .nt-timeline-title{font-size:1.1rem;margin-top:0}.nt-timeline .nt-timeline-sub-title{margin-top:0}.nt-timeline .nt-timeline-content{font-size:.8rem;border-bottom:2px dashed #ccc;padding-bottom:1.2rem}.nt-timeline.horizontal .nt-timeline-items{flex-direction:row;overflow-x:scroll}.nt-timeline.horizontal .nt-timeline-items>div{min-width:400px;margin-right:50px}.nt-timeline.horizontal.reverse .nt-timeline-items{flex-direction:row-reverse}.nt-timeline.horizontal.center .nt-timeline-before{background-image:linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%);background-repeat:no-repeat;background-size:100% 2px;background-position:0 center}.nt-timeline.horizontal.center .nt-timeline-after{background-image:linear-gradient(180deg, rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%);background-repeat:no-repeat;background-size:100% 2px;background-position:0 center}.nt-timeline.horizontal.center .nt-timeline-items{background-image:radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%);background-repeat:no-repeat;background-size:100% 2px;background-position:0 center}.nt-timeline.horizontal .nt-timeline-dot{left:50%}.nt-timeline.horizontal .nt-timeline-dot:not(.bigger){top:calc(50% - 4px)}.nt-timeline.horizontal .nt-timeline-dot.bigger{top:calc(50% - 15px)}.nt-timeline.vertical .nt-timeline-items{flex-direction:column}.nt-timeline.vertical.reverse .nt-timeline-items{flex-direction:column-reverse}.nt-timeline.vertical.center .nt-timeline-before{background:linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%}.nt-timeline.vertical.center .nt-timeline-after{background:linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat center/2px 100%}.nt-timeline.vertical.center .nt-timeline-items{background:radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%}.nt-timeline.vertical.center .nt-timeline-dot{left:calc(50% - 10px)}.nt-timeline.vertical.center .nt-timeline-dot:not(.bigger){top:10px}.nt-timeline.vertical.center .nt-timeline-dot.bigger{left:calc(50% - 20px)}.nt-timeline.vertical.left{padding-left:100px}.nt-timeline.vertical.left .nt-timeline-item{padding-left:70px}.nt-timeline.vertical.left .nt-timeline-sub-title{left:-100px;width:100px}.nt-timeline.vertical.left .nt-timeline-before{background:linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%}.nt-timeline.vertical.left .nt-timeline-after{background:linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat 30px/2px 100%}.nt-timeline.vertical.left .nt-timeline-items{background:radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%}.nt-timeline.vertical.left .nt-timeline-dot{left:21px;top:8px}.nt-timeline.vertical.left .nt-timeline-dot.bigger{top:0px;left:10px}.nt-timeline.vertical.right{padding-right:100px}.nt-timeline.vertical.right .nt-timeline-sub-title{right:-100px;text-align:left;width:100px}.nt-timeline.vertical.right .nt-timeline-item{padding-right:70px}.nt-timeline.vertical.right .nt-timeline-before{background:linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%}.nt-timeline.vertical.right .nt-timeline-after{background:linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat calc(100% - 30px)/2px 100%}.nt-timeline.vertical.right .nt-timeline-items{background:radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%}.nt-timeline.vertical.right .nt-timeline-dot{right:21px;top:8px}.nt-timeline.vertical.right .nt-timeline-dot.bigger{top:10px;right:10px}.nt-timeline-items{display:flex;position:relative}.nt-timeline-items>div{min-height:100px;padding-top:2px;padding-bottom:20px}.nt-timeline-before{content:"";height:15px}.nt-timeline-after{content:"";height:60px;margin-bottom:20px}.nt-timeline-sub-title{position:absolute;width:50%;top:4px;font-size:18px;color:var(--nt-color-50)}[data-md-color-scheme=slate] .nt-timeline-sub-title{color:var(--nt-color-51)}.nt-timeline-item{position:relative}.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item{padding-left:calc(50% + 40px)}.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title{left:0;padding-right:40px;text-align:right}.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd){padding-left:calc(50% + 40px)}.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title{left:0;padding-right:40px;text-align:right}.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even){text-align:right;padding-right:calc(50% + 40px)}.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title{right:0;padding-left:40px;text-align:left}.nt-timeline-dot{position:relative;width:20px;height:20px;border-radius:100%;background-color:#fc5b5b;position:absolute;top:0px;z-index:2;display:flex;justify-content:center;align-items:center;box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);border:3px solid #fff}.nt-timeline-dot:not(.bigger) .icon{font-size:10px}.nt-timeline-dot.bigger{width:40px;height:40px;padding:3px}.nt-timeline-dot .icon{color:#fff}@supports not (-moz-appearance: none){details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title,details .nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title{left:-40px}details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title{right:-40px}details .nt-timeline.vertical.center .nt-timeline-dot{left:calc(50% - 12px)}details .nt-timeline-dot.bigger{font-size:1rem !important}}.nt-timeline-item:nth-child(0) .nt-timeline-dot{background-color:var(--nt-color-0)}.nt-timeline-item:nth-child(1) .nt-timeline-dot{background-color:var(--nt-color-1)}.nt-timeline-item:nth-child(2) .nt-timeline-dot{background-color:var(--nt-color-2)}.nt-timeline-item:nth-child(3) .nt-timeline-dot{background-color:var(--nt-color-3)}.nt-timeline-item:nth-child(4) .nt-timeline-dot{background-color:var(--nt-color-4)}.nt-timeline-item:nth-child(5) .nt-timeline-dot{background-color:var(--nt-color-5)}.nt-timeline-item:nth-child(6) .nt-timeline-dot{background-color:var(--nt-color-6)}.nt-timeline-item:nth-child(7) .nt-timeline-dot{background-color:var(--nt-color-7)}.nt-timeline-item:nth-child(8) .nt-timeline-dot{background-color:var(--nt-color-8)}.nt-timeline-item:nth-child(9) .nt-timeline-dot{background-color:var(--nt-color-9)}.nt-timeline-item:nth-child(10) .nt-timeline-dot{background-color:var(--nt-color-10)}.nt-timeline-item:nth-child(11) .nt-timeline-dot{background-color:var(--nt-color-11)}.nt-timeline-item:nth-child(12) .nt-timeline-dot{background-color:var(--nt-color-12)}.nt-timeline-item:nth-child(13) .nt-timeline-dot{background-color:var(--nt-color-13)}.nt-timeline-item:nth-child(14) .nt-timeline-dot{background-color:var(--nt-color-14)}.nt-timeline-item:nth-child(15) .nt-timeline-dot{background-color:var(--nt-color-15)}.nt-timeline-item:nth-child(16) .nt-timeline-dot{background-color:var(--nt-color-16)}.nt-timeline-item:nth-child(17) .nt-timeline-dot{background-color:var(--nt-color-17)}.nt-timeline-item:nth-child(18) .nt-timeline-dot{background-color:var(--nt-color-18)}.nt-timeline-item:nth-child(19) .nt-timeline-dot{background-color:var(--nt-color-19)}.nt-timeline-item:nth-child(20) .nt-timeline-dot{background-color:var(--nt-color-20)}:root{--nt-scrollbar-color: #2751b0;--nt-plan-actions-height: 24px;--nt-units-background: #ff9800;--nt-months-background: #2751b0;--nt-plan-vertical-line-color: #a3a3a3ad}.nt-pastello{--nt-scrollbar-color: #9fb8f4;--nt-units-background: #f5dc82;--nt-months-background: #5b7fd1}[data-md-color-scheme=slate]{--nt-units-background: #003773}[data-md-color-scheme=slate] .nt-pastello{--nt-units-background: #3f4997}.nt-plan-root{min-height:200px;scrollbar-width:20px;scrollbar-color:var(--nt-scrollbar-color);display:flex}.nt-plan-root ::-webkit-scrollbar{width:20px}.nt-plan-root ::-webkit-scrollbar-track{box-shadow:inset 0 0 5px gray;border-radius:10px}.nt-plan-root ::-webkit-scrollbar-thumb{background:var(--nt-scrollbar-color);border-radius:10px}.nt-plan-root .nt-plan{flex:80%}.nt-plan-root.no-groups .nt-plan-periods{padding-left:0}.nt-plan-root.no-groups .nt-plan-group-summary{display:none}.nt-plan-root .nt-timeline-dot.bigger{top:-10px}.nt-plan-root .nt-timeline-dot.bigger[title]{cursor:help}.nt-plan{white-space:nowrap;overflow-x:auto;display:flex}.nt-plan .ug-timeline-dot{left:368px;top:-8px;cursor:help}.months{display:flex}.month{flex:auto;display:inline-block;box-shadow:rgba(0,0,0,.2) 0px 3px 1px -2px,rgba(0,0,0,.14) 0px 2px 2px 0px,rgba(0,0,0,.12) 0px 1px 5px 0px inset;background-color:var(--nt-months-background);color:#fff;text-transform:uppercase;font-family:Roboto,Helvetica,Arial,sans-serif;padding:2px 5px;font-size:12px;border:1px solid #000;width:150px;border-radius:8px}.nt-plan-group-activities{flex:auto;position:relative}.nt-vline{border-left:1px dashed var(--nt-plan-vertical-line-color);height:100%;left:0;position:absolute;margin-left:-0.5px;top:0;-webkit-transition:all .5s linear !important;-moz-transition:all .5s linear !important;-ms-transition:all .5s linear !important;-o-transition:all .5s linear !important;transition:all .5s linear !important;z-index:-2}.nt-plan-activity{display:flex;margin:2px 0;background-color:rgba(187,187,187,.2509803922)}.actions{height:var(--nt-plan-actions-height)}.actions{position:relative}.period{display:inline-block;height:var(--nt-plan-actions-height);width:120px;position:absolute;left:0px;background:#1da1f2;border-radius:5px;transition:all .5s;cursor:help;-webkit-transition:width 1s ease-in-out;-moz-transition:width 1s ease-in-out;-o-transition:width 1s ease-in-out;transition:width 1s ease-in-out}.period .nt-tooltip{display:none;top:30px;position:relative;padding:1rem;text-align:center;font-size:12px}.period:hover .nt-tooltip{display:inline-block}.period-0{left:340px;visibility:visible;background-color:#456165}.period-1{left:40px;visibility:visible;background-color:green}.period-2{left:120px;visibility:visible;background-color:pink;width:80px}.period-3{left:190px;visibility:visible;background-color:darkred;width:150px}.weeks>span,.days>span{height:25px}.weeks>span{display:inline-block;margin:0;padding:0;font-weight:bold}.weeks>span .week-text{font-size:10px;position:absolute;display:inline-block;padding:3px 4px}.days{z-index:-2;position:relative}.day-text{font-size:10px;position:absolute;display:inline-block;padding:3px 4px}.period span{font-size:12px;vertical-align:top;margin-left:4px;color:#000;background:rgba(255,255,255,.6588235294);border-radius:6px;padding:0 4px}.weeks,.days{height:20px;display:flex;box-sizing:content-box}.months{display:flex}.week,.day{height:20px;position:relative;border:1;flex:auto;border:2px solid #fff;border-radius:4px;background-color:var(--nt-units-background);cursor:help}.years{display:flex}.year{text-align:center;border-right:1px solid var(--nt-plan-vertical-line-color);font-weight:bold}.year:first-child{border-left:1px solid var(--nt-plan-vertical-line-color)}.year:first-child:last-child{width:100%}.quarters{display:flex}.quarter{width:12.5%;text-align:center;border-right:1px solid var(--nt-plan-vertical-line-color);font-weight:bold}.quarter:first-child{border-left:1px solid var(--nt-plan-vertical-line-color)}.nt-plan-group{margin:20px 0;position:relative}.nt-plan-group{display:flex}.nt-plan-group-summary{background:#2751b0;width:150px;white-space:normal;padding:.1rem .5rem;border-radius:5px;color:#fff;z-index:3}.nt-plan-group-summary p{margin:0;padding:0;font-size:.6rem;color:#fff}.nt-plan-group-summary,.month,.period,.week,.day,.nt-tooltip{border:3px solid #fff;box-shadow:0 2px 3px -1px rgba(0,0,0,.2),0 3px 3px 0 rgba(0,0,0,.14),0 1px 5px 0 rgba(0,0,0,.12)}.nt-plan-periods{padding-left:150px}.months{z-index:2;position:relative}.weeks{position:relative;top:-2px;z-index:0}.month,.quarter,.year,.week,.day,.nt-tooltip{font-family:Roboto,Helvetica,Arial,sans-serif;box-sizing:border-box}.nt-cards.nt-grid{display:grid;grid-auto-columns:1fr;gap:.5rem;max-width:100vw;overflow-x:auto;padding:1px}.nt-cards.nt-grid.cols-1{grid-template-columns:repeat(1, 1fr)}.nt-cards.nt-grid.cols-2{grid-template-columns:repeat(2, 1fr)}.nt-cards.nt-grid.cols-3{grid-template-columns:repeat(3, 1fr)}.nt-cards.nt-grid.cols-4{grid-template-columns:repeat(4, 1fr)}.nt-cards.nt-grid.cols-5{grid-template-columns:repeat(5, 1fr)}.nt-cards.nt-grid.cols-6{grid-template-columns:repeat(6, 1fr)}@media only screen and (max-width: 400px){.nt-cards.nt-grid{grid-template-columns:repeat(1, 1fr) !important}}.nt-card{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.2),0 1px 5px 0 rgba(0,0,0,.12)}.nt-card:hover{box-shadow:0 2px 2px 0 rgba(0,0,0,.24),0 3px 1px -2px rgba(0,0,0,.3),0 1px 5px 0 rgba(0,0,0,.22)}[data-md-color-scheme=slate] .nt-card{box-shadow:0 2px 2px 0 rgba(4,40,33,.14),0 3px 1px -2px rgba(40,86,94,.47),0 1px 5px 0 rgba(139,252,255,.64)}[data-md-color-scheme=slate] .nt-card:hover{box-shadow:0 2px 2px 0 rgba(0,255,206,.14),0 3px 1px -2px rgba(33,156,177,.47),0 1px 5px 0 rgba(96,251,255,.64)}.nt-card>a{color:var(--md-default-fg-color)}.nt-card>a>div{cursor:pointer}.nt-card{padding:5px;margin-bottom:.5rem}.nt-card-title{font-size:1rem;font-weight:bold;margin:4px 0 8px 0;line-height:22px}.nt-card-content{padding:.4rem .8rem .8rem .8rem}.nt-card-text{font-size:14px;padding:0;margin:0}.nt-card .nt-card-image{text-align:center;border-radius:2px;background-position:center center;background-size:cover;background-repeat:no-repeat;min-height:120px}.nt-card .nt-card-image.tags img{margin-top:12px}.nt-card .nt-card-image img{height:105px;margin-top:5px}.nt-card a:hover,.nt-card a:focus{color:var(--md-accent-fg-color)}.nt-card h2{margin:0}.span-table-wrapper table{border-collapse:collapse;margin-bottom:2rem;border-radius:.1rem}.span-table td,.span-table th{padding:.2rem;background-color:var(--md-default-bg-color);font-size:.64rem;max-width:100%;overflow:auto;touch-action:auto;border-top:.05rem solid var(--md-typeset-table-color);padding:.9375em 1.25em;vertical-align:top}.span-table tr:first-child td{font-weight:700;min-width:5rem;padding:.9375em 1.25em;vertical-align:top}.span-table td:first-child{border-left:.05rem solid var(--md-typeset-table-color)}.span-table td:last-child{border-right:.05rem solid var(--md-typeset-table-color)}.span-table tr:last-child{border-bottom:.05rem solid var(--md-typeset-table-color)}.span-table [colspan],.span-table [rowspan]{font-weight:bold;border:.05rem solid var(--md-typeset-table-color)}.span-table tr:not(:first-child):hover td:not([colspan]):not([rowspan]),.span-table td[colspan]:hover,.span-table td[rowspan]:hover{background-color:rgba(0,0,0,.035);box-shadow:0 .05rem 0 var(--md-default-bg-color) inset;transition:background-color 125ms}
diff --git a/docs/css/neoteroi-spantable.css b/docs/css/neoteroi-spantable.css
new file mode 100644
index 0000000..be069a8
--- /dev/null
+++ b/docs/css/neoteroi-spantable.css
@@ -0,0 +1,56 @@
+/**
+ * Extra CSS file recommended for MkDocs and neoteroi.spantable extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+.span-table-wrapper table {
+ border-collapse: collapse;
+ margin-bottom: 2rem;
+ border-radius: 0.1rem;
+}
+
+.span-table td,
+.span-table th {
+ padding: 0.2rem;
+ background-color: var(--md-default-bg-color);
+ font-size: 0.64rem;
+ max-width: 100%;
+ overflow: auto;
+ touch-action: auto;
+ border-top: 0.05rem solid var(--md-typeset-table-color);
+ padding: 0.9375em 1.25em;
+ vertical-align: top;
+}
+
+.span-table tr:first-child td {
+ font-weight: 700;
+ min-width: 5rem;
+ padding: 0.9375em 1.25em;
+ vertical-align: top;
+}
+
+.span-table td:first-child {
+ border-left: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table td:last-child {
+ border-right: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table tr:last-child {
+ border-bottom: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table [colspan],
+.span-table [rowspan] {
+ font-weight: bold;
+ border: 0.05rem solid var(--md-typeset-table-color);
+}
+
+.span-table tr:not(:first-child):hover td:not([colspan]):not([rowspan]),
+.span-table td[colspan]:hover,
+.span-table td[rowspan]:hover {
+ background-color: rgba(0, 0, 0, 0.035);
+ box-shadow: 0 0.05rem 0 var(--md-default-bg-color) inset;
+ transition: background-color 125ms;
+}
diff --git a/docs/css/neoteroi-timeline.css b/docs/css/neoteroi-timeline.css
new file mode 100644
index 0000000..488a37d
--- /dev/null
+++ b/docs/css/neoteroi-timeline.css
@@ -0,0 +1,341 @@
+/**
+ * Extra CSS file for MkDocs and the neoteroi.timeline extension.
+ *
+ * https://github.com/Neoteroi/mkdocs-plugins
+**/
+.nt-error {
+ border: 2px dashed darkred;
+ padding: 0 1rem;
+ background: #faf9ba;
+ color: darkred;
+}
+
+.nt-timeline {
+ margin-top: 30px;
+}
+.nt-timeline .nt-timeline-title {
+ font-size: 1.1rem;
+ margin-top: 0;
+}
+.nt-timeline .nt-timeline-sub-title {
+ margin-top: 0;
+}
+.nt-timeline .nt-timeline-content {
+ font-size: 0.8rem;
+ border-bottom: 2px dashed #ccc;
+ padding-bottom: 1.2rem;
+}
+.nt-timeline.horizontal .nt-timeline-items {
+ flex-direction: row;
+ overflow-x: scroll;
+}
+.nt-timeline.horizontal .nt-timeline-items > div {
+ min-width: 400px;
+ margin-right: 50px;
+}
+.nt-timeline.horizontal.reverse .nt-timeline-items {
+ flex-direction: row-reverse;
+}
+.nt-timeline.horizontal.center .nt-timeline-before {
+ background-image: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal.center .nt-timeline-after {
+ background-image: linear-gradient(180deg, rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal.center .nt-timeline-items {
+ background-image: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%);
+ background-repeat: no-repeat;
+ background-size: 100% 2px;
+ background-position: 0 center;
+}
+.nt-timeline.horizontal .nt-timeline-dot {
+ left: 50%;
+}
+.nt-timeline.horizontal .nt-timeline-dot:not(.bigger) {
+ top: calc(50% - 4px);
+}
+.nt-timeline.horizontal .nt-timeline-dot.bigger {
+ top: calc(50% - 15px);
+}
+.nt-timeline.vertical .nt-timeline-items {
+ flex-direction: column;
+}
+.nt-timeline.vertical.reverse .nt-timeline-items {
+ flex-direction: column-reverse;
+}
+.nt-timeline.vertical.center .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat center/2px 100%;
+}
+.nt-timeline.vertical.center .nt-timeline-dot {
+ left: calc(50% - 10px);
+}
+.nt-timeline.vertical.center .nt-timeline-dot:not(.bigger) {
+ top: 10px;
+}
+.nt-timeline.vertical.center .nt-timeline-dot.bigger {
+ left: calc(50% - 20px);
+}
+.nt-timeline.vertical.left {
+ padding-left: 100px;
+}
+.nt-timeline.vertical.left .nt-timeline-item {
+ padding-left: 70px;
+}
+.nt-timeline.vertical.left .nt-timeline-sub-title {
+ left: -100px;
+ width: 100px;
+}
+.nt-timeline.vertical.left .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat 30px/2px 100%;
+}
+.nt-timeline.vertical.left .nt-timeline-dot {
+ left: 21px;
+ top: 8px;
+}
+.nt-timeline.vertical.left .nt-timeline-dot.bigger {
+ top: 0px;
+ left: 10px;
+}
+.nt-timeline.vertical.right {
+ padding-right: 100px;
+}
+.nt-timeline.vertical.right .nt-timeline-sub-title {
+ right: -100px;
+ text-align: left;
+ width: 100px;
+}
+.nt-timeline.vertical.right .nt-timeline-item {
+ padding-right: 70px;
+}
+.nt-timeline.vertical.right .nt-timeline-before {
+ background: linear-gradient(rgba(252, 70, 107, 0) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-after {
+ background: linear-gradient(rgb(252, 70, 107) 0%, rgba(252, 70, 107, 0) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-items {
+ background: radial-gradient(circle, rgb(63, 94, 251) 0%, rgb(252, 70, 107) 100%) no-repeat calc(100% - 30px)/2px 100%;
+}
+.nt-timeline.vertical.right .nt-timeline-dot {
+ right: 21px;
+ top: 8px;
+}
+.nt-timeline.vertical.right .nt-timeline-dot.bigger {
+ top: 10px;
+ right: 10px;
+}
+
+.nt-timeline-items {
+ display: flex;
+ position: relative;
+}
+.nt-timeline-items > div {
+ min-height: 100px;
+ padding-top: 2px;
+ padding-bottom: 20px;
+}
+
+.nt-timeline-before {
+ content: "";
+ height: 15px;
+}
+
+.nt-timeline-after {
+ content: "";
+ height: 60px;
+ margin-bottom: 20px;
+}
+
+.nt-timeline-sub-title {
+ position: absolute;
+ width: 50%;
+ top: 4px;
+ font-size: 18px;
+ color: var(--nt-color-50);
+}
+
+[data-md-color-scheme=slate] .nt-timeline-sub-title {
+ color: var(--nt-color-51);
+}
+
+.nt-timeline-item {
+ position: relative;
+}
+
+.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item {
+ padding-left: calc(50% + 40px);
+}
+.nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title {
+ left: 0;
+ padding-right: 40px;
+ text-align: right;
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) {
+ padding-left: calc(50% + 40px);
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title {
+ left: 0;
+ padding-right: 40px;
+ text-align: right;
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) {
+ text-align: right;
+ padding-right: calc(50% + 40px);
+}
+.nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title {
+ right: 0;
+ padding-left: 40px;
+ text-align: left;
+}
+
+.nt-timeline-dot {
+ position: relative;
+ width: 20px;
+ height: 20px;
+ border-radius: 100%;
+ background-color: #fc5b5b;
+ position: absolute;
+ top: 0px;
+ z-index: 2;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: 0 2px 1px -1px rgba(0, 0, 0, 0.2), 0 1px 1px 0 rgba(0, 0, 0, 0.14), 0 1px 3px 0 rgba(0, 0, 0, 0.12);
+ border: 3px solid white;
+}
+.nt-timeline-dot:not(.bigger) .icon {
+ font-size: 10px;
+}
+.nt-timeline-dot.bigger {
+ width: 40px;
+ height: 40px;
+ padding: 3px;
+}
+.nt-timeline-dot .icon {
+ color: white;
+}
+
+/* Fix for webkit (Chrome, Safari) */
+@supports not (-moz-appearance: none) {
+ /*
+ This fix is necessary, for some reason, to render the timeline properly
+ inside `details` elements used by pymdownx. Firefox doesn't need this fix,
+ it renders elements properly.
+ */
+ details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(odd) .nt-timeline-sub-title,
+details .nt-timeline.vertical.center:not(.alternate) .nt-timeline-item .nt-timeline-sub-title {
+ left: -40px;
+ }
+ details .nt-timeline.vertical.center.alternate .nt-timeline-item:nth-child(even) .nt-timeline-sub-title {
+ right: -40px;
+ }
+ details .nt-timeline.vertical.center .nt-timeline-dot {
+ left: calc(50% - 12px);
+ }
+ details .nt-timeline-dot.bigger {
+ font-size: 1rem !important;
+ }
+}
+/* default colors */
+.nt-timeline-item:nth-child(0) .nt-timeline-dot {
+ background-color: var(--nt-color-0);
+}
+
+.nt-timeline-item:nth-child(1) .nt-timeline-dot {
+ background-color: var(--nt-color-1);
+}
+
+.nt-timeline-item:nth-child(2) .nt-timeline-dot {
+ background-color: var(--nt-color-2);
+}
+
+.nt-timeline-item:nth-child(3) .nt-timeline-dot {
+ background-color: var(--nt-color-3);
+}
+
+.nt-timeline-item:nth-child(4) .nt-timeline-dot {
+ background-color: var(--nt-color-4);
+}
+
+.nt-timeline-item:nth-child(5) .nt-timeline-dot {
+ background-color: var(--nt-color-5);
+}
+
+.nt-timeline-item:nth-child(6) .nt-timeline-dot {
+ background-color: var(--nt-color-6);
+}
+
+.nt-timeline-item:nth-child(7) .nt-timeline-dot {
+ background-color: var(--nt-color-7);
+}
+
+.nt-timeline-item:nth-child(8) .nt-timeline-dot {
+ background-color: var(--nt-color-8);
+}
+
+.nt-timeline-item:nth-child(9) .nt-timeline-dot {
+ background-color: var(--nt-color-9);
+}
+
+.nt-timeline-item:nth-child(10) .nt-timeline-dot {
+ background-color: var(--nt-color-10);
+}
+
+.nt-timeline-item:nth-child(11) .nt-timeline-dot {
+ background-color: var(--nt-color-11);
+}
+
+.nt-timeline-item:nth-child(12) .nt-timeline-dot {
+ background-color: var(--nt-color-12);
+}
+
+.nt-timeline-item:nth-child(13) .nt-timeline-dot {
+ background-color: var(--nt-color-13);
+}
+
+.nt-timeline-item:nth-child(14) .nt-timeline-dot {
+ background-color: var(--nt-color-14);
+}
+
+.nt-timeline-item:nth-child(15) .nt-timeline-dot {
+ background-color: var(--nt-color-15);
+}
+
+.nt-timeline-item:nth-child(16) .nt-timeline-dot {
+ background-color: var(--nt-color-16);
+}
+
+.nt-timeline-item:nth-child(17) .nt-timeline-dot {
+ background-color: var(--nt-color-17);
+}
+
+.nt-timeline-item:nth-child(18) .nt-timeline-dot {
+ background-color: var(--nt-color-18);
+}
+
+.nt-timeline-item:nth-child(19) .nt-timeline-dot {
+ background-color: var(--nt-color-19);
+}
+
+.nt-timeline-item:nth-child(20) .nt-timeline-dot {
+ background-color: var(--nt-color-20);
+}
+
diff --git a/docs/images/cli.png b/docs/images/cli.png
new file mode 100644
index 0000000..7872dd5
Binary files /dev/null and b/docs/images/cli.png differ
diff --git a/docs/images/jupyter.png b/docs/images/jupyter.png
new file mode 100644
index 0000000..c6a19aa
Binary files /dev/null and b/docs/images/jupyter.png differ
diff --git a/docs/images/logo-dark.png b/docs/images/logo-dark.png
new file mode 100644
index 0000000..9d7791f
Binary files /dev/null and b/docs/images/logo-dark.png differ
diff --git a/docs/images/logo.png b/docs/images/logo.png
new file mode 100644
index 0000000..014a63a
Binary files /dev/null and b/docs/images/logo.png differ
diff --git a/docs/images/string-intern/string_intern.png b/docs/images/string-intern/string_intern.png
new file mode 100644
index 0000000..6511978
Binary files /dev/null and b/docs/images/string-intern/string_intern.png differ
diff --git a/docs/images/tic-tac-toe/after_board_initialized.png b/docs/images/tic-tac-toe/after_board_initialized.png
new file mode 100644
index 0000000..616747f
Binary files /dev/null and b/docs/images/tic-tac-toe/after_board_initialized.png differ
diff --git a/docs/images/tic-tac-toe/after_row_initialized.png b/docs/images/tic-tac-toe/after_row_initialized.png
new file mode 100644
index 0000000..520d700
Binary files /dev/null and b/docs/images/tic-tac-toe/after_row_initialized.png differ
diff --git a/docs/images/usa.png b/docs/images/usa.png
new file mode 100644
index 0000000..fa68dcb
Binary files /dev/null and b/docs/images/usa.png differ
diff --git a/docs/images/website.png b/docs/images/website.png
new file mode 100644
index 0000000..3785138
Binary files /dev/null and b/docs/images/website.png differ
diff --git a/docs/images/world.png b/docs/images/world.png
new file mode 100644
index 0000000..78e031f
Binary files /dev/null and b/docs/images/world.png differ
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..551290d
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,43 @@
+---
+hide:
+ - navigation
+ - toc
+---
+
+
+What the f*ck Python! 😱
+Exploring and understanding Python through surprising snippets.
+
+## Sections
+
+
+::cards:: cols=5
+
+- title: English
+ content: Original language
+ image: images/usa.png
+ url: usa
+
+- title: Translations
+ content: Other languages
+ image: images/world.png
+ url: cn
+
+- title: Website
+ content: Interactive Website
+ image: images/website.png
+ url: https://wtfpython-interactive.vercel.app/
+
+- title: Notebook
+ content: Interactive Notebook
+ image: images/jupyter.png
+ url: https://colab.research.google.com/github/satwikkansal/wtfpython/blob/master/irrelevant/wtf.ipynb
+
+- title: CLI
+ content: Installation
+ image: images/cli.png
+ url: https://pypi.org/project/wtfpython/
+
+::/cards::
+
+
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..02d11d8
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,91 @@
+# Copyright (c) 2016-2022 Martin Donath
+
+# Project information
+site_name: What the f*ck Python!
+site_url: https://github.com/satwikkansal/wtfpython
+site_author: Francisco Alfaro
+site_description:
+
+# Repository
+repo_name: satwikkansal/wtfpython
+repo_url: https://github.com/satwikkansal/wtfpython
+edit_uri: ''
+
+# Markdown extensions
+markdown_extensions:
+ - pymdownx.superfences
+ - pymdownx.tabbed:
+ alternate_style: true
+ - neoteroi.cards
+ - neoteroi.timeline
+ - neoteroi.projects
+ - admonition
+ - pymdownx.details
+
+extra_css:
+ - css/neoteroi-mkdocs.css
+
+# Theme
+theme:
+ name: material
+ language: es
+ logo: https://www.svgrepo.com/show/405305/face-screaming-in-fear.svg
+ favicon: https://www.svgrepo.com/show/405305/face-screaming-in-fear.svg
+ palette:
+ - media: '(prefers-color-scheme: light)'
+ scheme: default
+ primary: deep purple
+ accent: deep purple
+ toggle:
+ icon: material/lightbulb
+ name: Switch to dark mode
+ - media: '(prefers-color-scheme: dark)'
+ scheme: slate
+ primary: deep purple
+ accent: deep purple
+ toggle:
+ icon: material/lightbulb-outline
+ name: Switch to light mode
+ features:
+ - search.suggest
+ - search.highlight
+ - content.tabs.link
+ - navigation.indexes
+ - content.tooltips
+ - navigation.path
+ - content.code.annotate
+ - content.code.copy
+ - content.code.select
+ - navigation.tabs
+
+# Customization
+extra:
+ social:
+ - icon: fontawesome/brands/github
+ link: https://github.com/satwikkansal
+ - icon: fontawesome/brands/twitter
+ link: https://twitter.com/satwikkansal
+ - icon: fontawesome/solid/globe
+ link: https://satwikkansal.xyz/
+
+# Plugins
+plugins:
+ - search
+
+# Extra javascript
+extra_javascript:
+ - javascripts/mathjax.js
+ - https://polyfill.io/v3/polyfill.min.js?features=es6
+ - https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
+
+#TOC
+nav:
+ - 🏠 Home: index.md
+ - 🐍 Original: USA.md
+ - 🌎 World:
+ - Chinese: CN.md
+ - Vietnamese: VI.md
+ - Korean: KO.md
+ - Russian: RU.md
+ - German: GE.md
+
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..c64c949
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,13 @@
+[tool.poetry]
+name = "docs"
+version = "0.1.0"
+description = "mkdocs - courses"
+authors = ["Francisco Alfaro "]
+license = "MIT"
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "^3.10"
+mkdocs-material = "*"
+neoteroi-mkdocs = "*"
+mkdocs-jupyter = "*"