└── 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` с дополнительными данными. --------------------------------------------------------------------------------