Ссылка на PEP: https://peps.python.org/pep-0765
Одна из самых сломанных частей питона была пофикшена в 3.14
В чем проблема?
Ранее такой код вел себя крайне странно:
>>> def some():
... try:
... return 1
... finally:
... return 2
>>> some()
2
Так как из функции может быть только один
return, а код в finally всегда выполняется – то получаем, что получаем.Или такой код:
>>> def other():
... try:
... 1 / 0
... finally:
... return 2
>>> other()
2
Тут вообще жесть. У нас подавляется исключение без
except 🫠Аналогично можно делать не только с
return, но и с break / continue в циклах:
>>> def cycle():
... for i in range(2):
... try:
... i / 0
... finally:
... print(i)
... continue
... return 2
>>> cycle()
# prints: 0
# prints: 1
# returns: 2
Такой код позволял создавать ошибки на ровном месте.
Как исправили?
Теперь такой код генерирует
SyntaxWarning в 3.14:
>>> def some():
... try:
... return 1
... finally:
... return 2
<python-input-14>:5: SyntaxWarning: 'return' in a 'finally' block
Скоро – будет
SyntaxError, в будущих версиях.Но, WPS, например, запрещал такое делать уже очень давно.
Как работает?
Сам патч очень маленький и простой:
1. Добавляет список текущего контекста
_Py_c_array_t cf_finally; в ast_opt.cХраним внутри структуры вида:
typedef struct {
bool in_finally; // мы в `finally`?
bool in_funcdef; // мы в `def` или `async def`?
bool in_loop; // мы в `for`, `async for` или `while`?
} ControlFlowInFinallyContext;
2. При обходе дерева, добавляет нужные данные в текущий контекст: находимся ли мы в
finally, функции, цикле3. Если находим
return, break или continue, то выполняем проверку синтаксиса; вот код для return:
static int
before_return(_PyASTOptimizeState *state, stmt_ty node_)
{
if (state->cf_finally_used > 0) {
ControlFlowInFinallyContext *ctx = get_cf_finally_top(state);
// если нашли `return` в `finally`, но не во вложенной функции,
// то показываем warning пользователю:
if (ctx->in_finally && ! ctx->in_funcdef) {
if (!control_flow_in_finally_warning("return", node_, state)) {
return 0;
}
}
}
return 1;
}
4. Внутри
control_flow_in_finally_warning используем специальное АПИ для SyntaxWarning:
static int
control_flow_in_finally_warning(const char *kw, stmt_ty n, _PyASTOptimizeState *state)
{
PyObject *msg = PyUnicode_FromFormat("'%s' in a 'finally' block", kw);
if (msg == NULL) {
return 0;
}
int ret = _PyErr_EmitSyntaxWarning(msg, state->filename, n->lineno,
n->col_offset + 1, n->end_lineno,
n->end_col_offset + 1);
Py_DECREF(msg);
return ret < 0 ? 0 : 1;
}
Готово!
Обсуждение: вы знали про такую проблему? Стреляли так себе в ногу?
P.S. Пока писал пост, нашел багу в питоне и важную опечатку :)
| Поддержать | YouTube | GitHub | Чат |