└── README.md
/README.md:
--------------------------------------------------------------------------------
1 | # Шпаргалка про исключения в Питоне
2 |
3 | **План**
4 |
5 | 1. [Метафора](#kino) "аварийный выход" (как в кинотеатре).
6 | 2. [Термины](#words)
7 | 2. [Схема применения](#brief)
8 | 3. Допустимые [форматы](#formats) для бросков
9 | - Запрет на броски базовых исключений
10 | - Запрет на броски без описания проблемы
11 | 4. [Жонглирование](#juggling) исключениями
12 | - *Последний рубеж* (где перехватывать всех)
13 | - *Игнорирование* (не знаешь чем дополнить, не перехватывай)
14 | - *Уточнения* (если можешь дополнить, создай новое)
15 | - *Исключение не про ошибку*
16 | 5. Когда/как [создавать](#create) новые классы исключений?
17 | 6. [Логгирование](#log) исключений
18 | 7. [Гард-блоки](#guard) и броски
19 | 8. [Передача](#break) управления в пределах функции
20 |
21 | ## Метафора "аварийный выход" (как в кинотеатре).
22 |
23 | У зрителей в кинотеатре есть
24 | штатный выход (там где входили)
25 | и еще
26 | аварийный (часто на другой этаж или даже на улицу).
27 | Так и у функций есть
28 | штатный возврат через `return`
29 | и еще
30 | аварийный через `raise`.
31 |
32 | Удобно представлять себе, что `raise` это специализированный аналог `return`.
33 | Внутри функции у них одинаковые смыслы - закончить выполнение и вернуть какие-то данные.
34 | А вот для окружающих они различаются:
35 |
36 | - От `return` данные приходят в "присвоение" `переменная = функция(параметры)`.
37 | - А от `raise` данные хранит Питон.
38 | Можно их либо игнорировать, либо анализировать после "перехвата" дополнительным кодом.
39 |
40 | ## Термины
41 |
42 | - **Бросок**/throw/подъем/`raise`.
43 | Общепринятые и всем понятные термины "бросок", "бросать исключение".
44 | В Питоне у команды `raise` смысл подъем/поднимать. Можно говорить и так.
45 | Только при переходе в другой язык придется переучиваться.
46 | - **Контролируемый участок** кода.
47 | В Питоне такой участок выделяется блоком `try:`.
48 | - **Перехват**/catch/перехватывать/**ловить**.
49 | В Питоне это `except [Тип [as объект]]:`
50 | - **Реакция на перехват**.
51 | Это содержимое блока `except`.
52 | Изредка, реакция даже бывает пустой (`pass` или `...`), если это нужно алгоритму.
53 |
54 | ## Схема применения
55 |
56 | ```python
57 | if проверка:
58 | raise ТипБроска(параметры) # (1) (2)
59 | try:
60 | #<контролируемый код> # (3)
61 | except: # (4)
62 | except ТипПерехвата: # (5)
63 | except Тип as имя: # (6)
64 | #<код с реакцией на перехват>
65 | raise ТипБроска(параметры) # (7)
66 | except (Тип1, Тип2): # (8)
67 | ```
68 |
69 | *Замечания:*
70 |
71 | - (1) Запрещены броски типов **не наследников** от `Exception`.
72 | - (2) Параметры для исключения не обязательны.
73 | Но, почти всегда, это один строковый параметр с **описанием проблемы**.
74 | - (3) Какие исключения прилетают от "контроллируемого кода"?
75 | Это нужно выяснять по докам к каждой применяемой детали.
76 | - В Питоне допускается вольности:
77 | - (4) Можно "**ловить все**", если писать `except:код`.
78 | - (5) Можно "**ловить тип**, но не объект", если писать `except Тип:код`.
79 | - (6) Обычно ловят тип с объектом `except Тип as объект:код`.
80 | - (5)(6)(8) Перехват "*с типом*" ловит только исключения,
81 | которые являются **наследниками** от указанного(ых).
82 | Например, `except Exception` ловит все исключения.
83 | - (4)(5)(6)(8) Блоков `except` может быть несколько - **они конкурируют**!
84 | Исключение достанется первому, у которого подходящие ожидания.
85 | Поэтому нужно располагать самые *узкоожидающие* сверху,
86 | а самые *охватывающие* (например, `except:`) снизу.
87 | - (7) Броски в реакциях уместны.
88 | Тип для броска не обязан совпадать с типом для перехвата.
89 | - (8) Если реакции совпадают, то можно (*нужно!*) объединить перехваты.
90 |
91 | ## Допустимые форматы для бросков
92 |
93 | 1. **Запрет** на броски строковых значений (они же не наследники `Exception`).
94 | 2. **Запрет** на броски исключений базового типа `Exception`.
95 | Это лишает вызывающую сторону гибкого поведения.
96 | Она будет не в состоянии отличить такой бросок от других сбоев.
97 | Нужно
98 | - либо подобрать штатный тип (это лучше),
99 | - либо создать своего наследника (это допустимо).
100 | 3. **Запрет** на броски без описания проблемы.
101 | Один только тип не дает конкретики.
102 |
103 | ## Жонглирование исключениями
104 |
105 | 1. **Последний рубеж** – место для перехвата всех исключений.
106 | Если ваш код содержит головную функцию с бесконечным циклом,
107 | то в ней обязательно нужен перехват всех долетевших исключений.
108 | Поймать все, залоггировать каждое – дать циклу работать дальше.
109 |
110 | 2. **Игнорирование**.
111 | Это самое контр-интуитивное правило.
112 | Если к пролетающему исключению добавить нечего, то его обязательно **пропустить мимо**.
113 | Если не знаете, как правильно реагировать, обязаны **пропустить**.
114 | Частое нарушение этого правила - избыточный перехват (например, по `Exception`).
115 | Следствие из этого правила: *контролируемый код лучше делать минимальным*.
116 |
117 | 3. **Уточнения**.
118 |
119 | - Если есть что добавить к описанию проблемы,
120 | то после перехвата бросается **новое исключение** с дополненным описанием.
121 | - Если тип пролетающего исключение из либы, известной только внутри функции/модуля,
122 | а бросок будет перехватываться там, где про эту либу не знают, **замена типа** обязательна!
123 | *Пример*. Извлечение страницы по урлу выполняется спец-либой (например, `requests`),
124 | импортированной в текущий модуль. Тогда пропускать мимо сбой нельзя.
125 | Нужно поймать и поменять тип на независимый от этой либы.
126 |
127 | 4. **Исключение не про ошибку**.
128 | Во многих случаях "перехват" нужен для обнаружения ошибки, но при этом исключение ее не описывает.
129 | *Пример*. Параметр функции используется как ключ к внутреннему словарю - может возникнуть `KeyError`.
130 | Но проблема не в промахе ключа, а в значении параметра.
131 | Поэтому нужно не только сформировать новое описание для исключения, но и задать ему новый тип.
132 |
133 | ## Когда/как создавать новые классы исключений?
134 | Если среди штатных типов исключений есть *идеально подходящий к ситуации*, лучше применять его.
135 | Учтите, что штатные типы вызывающей стороне более удобны - их не нужно дополнительно импортировать.
136 | Если штатного нет, создать свой, наследуя его от *одного из штатных*.
137 |
138 | Тут есть "*подводные камни*".
139 |
140 | - *Что такое "идеально подходящий к ситуации"?*
141 | Нужно рассмотреть проблему с максимального удаления,
142 | то есть выделить в ней самое абстрактное ядро.
143 | Если для такого "ядра" есть штатный тип, то его и нужно применять.
144 | А все детали ситуации, которые вне абстрактного ядра,
145 | описать текстом при создании исключения.
146 | - *От какого типа наследовать?*
147 | Опять нужно выделить в ситуации "абстрактное ядро".
148 | И опять наследовать от типа, подходящего к такому ядру.
149 | Обычно применяется наследование от `Exception` (подходит для всего).
150 |
151 | При создании своего класса-исключения важно знать про "ошибку новичка":
152 | *Добавлять конструктор вредно!*
153 | Должен работать конструктор от базового класса - он гибкий,
154 | так как готов принимать разнообразные параметры.
155 | Если свой конструктор действительно нужен,
156 | то в нем обязательно должен быть вызов "от базового" через
157 | `super().__init__(параметры)`.
158 |
159 | ## Логгирование исключений
160 | Недопустимо логгировать "голое исключение".
161 | Всегда нужно вставлять его текст в описание ситуации.
162 |
163 | ## Гард-блоки и броски
164 | Так как бросок для функции это полный аналог возврата,
165 | броски прекрасно сочетаются с гард-блоками,
166 | то есть с организацией кода, при которой делаются проверки для раннего возврата.
167 |
168 | ```python
169 | if есть-недостаток:
170 | raise ТипИсключения('<описание проблемы>')
171 | if есть-недостаток:
172 | return что-то
173 | if есть-недостаток:
174 | raise ТипИсключения('<описание проблемы>')
175 | ```
176 |
177 | ## Передача управления в пределах функции
178 | Кроме работы аналогом `return` броски могут передавать управление внутри функции,
179 | то есть работать аналогами `break` с дополнительными данными.
--------------------------------------------------------------------------------