1
0
mirror of https://github.com/satwikkansal/wtfpython synced 2024-06-01 19:08:04 +02:00
wtfpython/translations/README-ru.md

736 lines
36 KiB
Markdown
Raw Normal View History

<p align="center"><img src="/images/logo.png#gh-light-mode-only" alt=""><img src="/images/logo-dark.png#gh-dark-mode-only" alt=""></p>
<h1 align="center">What the f*ck Python! 😱</h1>
<p align="center">Изучение и понимание Python с помощью нестандартного поведения и "магического" поведения.</p>
Переводы: [English Original](https://github.com/satwikkansal/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) | [Russian Русский](https://github.com/nifadyev/wtfpython/tree/main/translations/README-ru.md) | [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].)
Альтернативные способы: [Интерактивный сайт](https://wtfpython-interactive.vercel.app) | [Интерактивный Jupiter notebook](https://colab.research.google.com/github/satwikkansal/wtfpython/blob/master/irrelevant/wtf.ipynb) | [CLI](https://pypi.python.org/pypi/wtfpython)
Python, будучи прекрасно спроектированным высокоуровневым языком программирования, предоставляет множество возможностей для удобства программиста. Но иногда результаты работы Python кода могут показаться неочевидными на первый взгляд.
**wtfpython** задуман как проект, пытающийся объяснить, что именно происходит под капотом некоторых неочевидных фрагментов кода и менее известных возможностей Python.
Если вы опытный программист на Python, вы можете принять это как вызов и правильно объяснить WTF ситуации с первой попытки. Возможно, вы уже сталкивались с некоторыми из них раньше, и я смогу оживить ваши старые добрые воспоминания! 😅
PS: Если вы уже читали **wtfpython** раньше, с изменениями можно ознакомиться [здесь](https://github.com/satwikkansal/wtfpython/releases/) (примеры, отмеченные звездочкой - это примеры, добавленные в последней основной редакции).
Ну что ж, приступим...
# Содержание
- [Содержание](#содержание)
- [Структура примера](#структура-примера)
2024-01-25 14:43:04 +01:00
- [Применение](#применение)
- [👀 Примеры](#-примеры)
- [Секция: Напряги мозги!](#секция-напряги-мозги)
- [▶ Первым делом!](#-первым-делом)
- [💡 Обьяснение](#-обьяснение)
- [▶ Строки иногда ведут себя непредсказуемо](#-строки-иногда-ведут-себя-непредсказуемо)
- [💡 Объяснение](#-объяснение)
# Структура примера
Все примеры имеют следующую структуру:
> ### ▶ Какой-то заголовок
>
> ```py
> # Неочевидный фрагмент кода
> # Подготовка к магии...
> ```
>
> **Вывод (Python версия):**
>
> ```py
> >>> triggering_statement
> Неожиданные результаты
> ```
>
> (Опционально): Краткое описание неожиданного результата
>
>
> #### 💡 Объяснение
>
> * Краткое объяснение того, что происходит и почему это происходит.
>
> ```py
> # Код
> # Дополнительные примеры для дальнейшего разъяснения (если необходимо)
> ```
>
> **Вывод (Python версия):**
>
> ```py
> >>> trigger # какой-нибудь пример, позволяющий легко раскрыть магию
> # обоснованный вывод
> ```
**Важно:** Все примеры протестированы на интерактивном интерпретаторе Python 3.5.2, и они должны работать для всех версий Python, если это явно не указано перед выводом.
2024-01-25 14:43:04 +01:00
# Применение
Хороший способ получить максимальную пользу от этих примеров - читать их последовательно, причем для каждого из них важно:
- Внимательно изучить исходный код. Если вы опытный программист на Python, то в большинстве случаев сможете предугадать, что произойдет дальше.
- Прочитать фрагменты вывода и,
- Проверить, совпадают ли выходные данные с вашими ожиданиями.
- Убедиться, что вы знаете точную причину, по которой вывод получился именно таким.
- Если ответ отрицательный (что совершенно нормально), сделать глубокий вдох и прочитать объяснение (а если пример все еще непонятен, и создайте issue [здесь](https://github.com/satwikkansal/wtfpython/issues/new)).
- Если "да", ощутите мощь своих познаний в Python и переходите к следующему примеру.
PS: Вы также можете читать WTFPython в командной строке, используя [pypi package](https://pypi.python.org/pypi/wtfpython),
```sh
pip install wtfpython -U
wtfpython
```
# 👀 Примеры
## Секция: Напряги мозги!
### ▶ Первым делом!
<!-- Example ID: d3d73936-3cf1-4632-b5ab-817981338863 -->
<!-- read-only -->
По какой-то причине "моржовый оператор" (англ. walrus) `:=` в Python 3.8 стал довольно популярным. Давайте проверим его,
1\.
```py
# Python version 3.8+
>>> a = "wtf_walrus"
>>> a
'wtf_walrus'
>>> a := "wtf_walrus"
File "<stdin>", line 1
a := "wtf_walrus"
^
SyntaxError: invalid syntax
>>> (a := "wtf_walrus") # А этот код работает
'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 # Типичная распаковка
>>> a, b
(6, 9)
>>> (a, b = 16, 19) # Упс
File "<stdin>", line 1
(a, b = 16, 19)
^
SyntaxError: invalid syntax
>>> (a, b := 16, 19) # На выводе получаем странный кортеж из 3 элементов
(6, 16, 19)
>>> 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)
```
**Вывод (> 3.8):**
```py
5
5
5
```
Использование `:=` сэкономило одну строку кода и неявно предотвратило вызов `some_func` дважды.
- "выражение присваивания", не обернутое в скобки, иначе говоря использование моржового оператора, ограничено на верхнем уровне, отсюда `SyntaxError` в выражении `a := "wtf_walrus"` в первом фрагменте. После оборачивания в скобки, `a` было присвоено значение, как и ожидалось.
- В то же время оборачивание скобками выражения, содержащего оператор `=`, не допускается. Отсюда синтаксическая ошибка в `(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)`, которое есть не что иное, как кортеж из 3 элементов.
---
### ▶ Строки иногда ведут себя непредсказуемо
<!-- Example ID: 30f1d3fc-e267-4b30-84ef-4d9e7091ac1a --->
1\.
```py
>>> a = "some_string"
>>> id(a)
140420665652016
>>> id("some" + "_" + "string") # Обратите внимание, оба идентификатора одинаковы
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 # Актуально для версий Python, кроме 3.7.x
True
>>> a = "wtf!"; b = "wtf!"
>>> a is b # Выражение вернет True или False в зависимости вызываемой среды (python shell / ipython / скрипт).
False
```
```py
# На этот раз в файле
a = "wtf!"
b = "wtf!"
print(a is b)
# Выводит True при запуске модуля
```
4\.
**Output (< Python3.7 )**
```py
>>> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
>>> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
```
Логично, правда?
#### 💡 Объяснение
- Поведение в первом и втором фрагментах связано с оптимизацией CPython (называемой интернированием строк ((англ. string interning))), которая пытается использовать существующие неизменяемые объекты в некоторых случаях вместо того, чтобы каждый раз создавать новый объект.
- После "интернирования" многие переменные могут ссылаться на один и тот же строковый объект в памяти (тем самым экономя память).
- В приведенных выше фрагментах строки неявно интернированы. Решение о том, когда неявно интернировать строку, зависит от реализации. Правила для интернирования строк следующие:
- Все строки длиной 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!"`, но одинаковы при вызове в модуле.
- Резкое изменение в выводе четвертого фрагмента связано с [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).
---
### ▶ Осторожнее с цепочкой операций
<!-- Example ID: 07974979-9c86-4720-80bd-467aa19470d9 --->
```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 и 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`
<!-- Example ID: 230fa2ac-ab36-4ad1-b675-5f5a1c1a6217 --->
Ниже приведен очень известный пример.
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)**
```py
>>> a, b = 257, 257
>>> a is b
False
```
#### 💡 Объяснение:
**Разница между `is` и `==`**.
* Оператор `is` проверяет, ссылаются ли оба операнда на один и тот же объект (т.е. проверяет, совпадают ли идентификаторы операндов или нет).
* Оператор `==` сравнивает значения обоих операндов и проверяет, одинаковы ли они.
* Таким образом, оператор `is` предназначен для равенства ссылок, а `==` - для равенства значений. Пример, чтобы прояснить ситуацию,
```py
>>> class A: pass
>>> A() is A() # 2 пустых объекта в разных ячейках памяти
False
```
**`256` - существующий объект, а `257` - нет**.
При запуске python числа от `-5` до `256` записываются в память. Эти числа используются часто, поэтому имеет смысл просто иметь их наготове.
Перевод цитаты из [документации](https://docs.python.org/3/c-api/long.html)
> Текущая реализация хранит массив целочисленных объектов для всех целых чисел от -5 до 256, когда вы создаете int в этом диапазоне, вы просто получаете обратно ссылку на существующий объект.
```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` ссылаются на один и тот же объект при инициализации одним и тем же значением в одной и той же строкеi**.
**Вывод**
```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) для получения обновлений.
---
### ▶ Мистическое хэширование
<!-- Example ID: eb17db53-49fd-4b61-85d6-345c5ca213ff --->
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"
```
Так почему же Python повсюду?
#### 💡 Объяснение
* Уникальность ключей в словаре 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` в словаре, содержащем `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)) и ухудшает производительность постоянного времени, которую обычно обеспечивает хэширование).
---
### ▶ В глубине души мы все одинаковы.
<!-- Example ID: 8f99a35f-1736-43e2-920d-3b78ec35da9b --->
```py
class WTF:
pass
```
**Вывод:**
```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.
* Но почему выражение с оператором `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
```
Как вы можете заметить, все дело в порядке уничтожения объектов.
---
### ▶ Беспорядок внутри порядка *
<!-- Example ID: 91bff1f8-541d-455a-9de4-6cd8ff00ea66 --->
```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 # a == b
True
>>> dictionary == another_ordered_dict # b == c
True
>>> ordered_dict == another_ordered_dict # почему же c != a ??
False
# Мы все знаем, что множество состоит только из уникальных элементов,
# давайте попробуем составить множество из этих словарей и посмотрим, что получится...
>>> len({dictionary, ordered_dict, another_ordered_dict})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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`. Перевод цитаты из [документации](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) # используем объекты из фрагмента кода выше
>>> 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`.
---
2024-04-19 11:36:33 +02:00
### ▶ Продолжай пытаться... *
<!-- Example ID: b4349443-e89f-4d25-a109-82616be9d41a --->
```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 occurred", e)
```
**Результат:**
```py
>>> some_func()
'from_finally'
>>> another_func()
Finally!
Finally!
Finally!
>>> 1 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> one_more_func()
Iteration 0
```
#### 💡 Объяснение:
- Когда один из операторов `return`, `break` или `continue` выполняется в блоке `try` оператора "try...finally", на выходе также выполняется пункт `finally`.
- Возвращаемое значение функции определяется последним выполненным оператором `return`. Поскольку блок `finally` выполняется всегда, оператор `return`, выполненный в блоке `finally`, всегда будет последним.
- Предостережение - если в блоке `finally` выполняется оператор `return` или `break`, то временно сохраненное исключение отбрасывается.
---