exit и компания

Выхода нет. Человек стучится в закрытую дверь, одиноко стоящую в поле (хотя может ее обойти).
>>> exit

У каждого, наверное, было: пишешь в интерпретаторе exit, а он:

>>> exit
Use exit() or Ctrl-D (i.e. EOF) to exit

Что же такое exit? Оказывается это такой класс, а текст — это всего лишь его repr:

>>> type(exit)
<class '_sitebuiltins.Quitter'>
>>> repr(exit)
'Use exit() or Ctrl-D (i.e. EOF) to exit'

А еще есть quit – он тоже из этой семьи:

>>> type(quit)
<class '_sitebuiltins.Quitter'>

Что же приходит при вывозе такого класса? Просто бросается исключение SystemExit, которое, между прочим, можно поймать. Попробуйте:

try:
    # выбери любое из:
    exit()
    quit()
except SystemExit:
    print('Невозможно покинуть Омск')

Есть еще sys.exit, который тоже бросает SystemExit, что может быть пойман.

🛑 Вывод: нельзя надеятся на exit() для гарантированного завершения программы, ведь ваш код может быть обернут в try / except Exception, который может подавить SystemExit. Как же быть? Есть способ – это os._exit, который завершит программу на системном уровне:

import os
try:
    os._exit(-1)
except SystemExit:
    print('Невозможно покинуть Омск')
finally:
    print('Я свободен!')

Ни первый, ни второй print не сработают!

✋ Надо упомянуть еще os.abort(), которая также немедленно завершает программу сигналом SIGABRT, что еще дополнительно приводит к созданию дампа памяти. Причем, не будет вызван даже обработчик сигнала, установленный через signal.signal(). Функция os.abort() подходит только для аварийного завершения приложения.

🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Многоликий else

Все знают, что ключевое слово else служит для выполнения альтернативной ветки кода, если условие if не выполнилось:

x = 5
if x < 3:
    print("x < 3")
else:
    print("x >= 3")

Но знали ли вы, что есть еще два примения else?

1. for/else, while/else

Если поставить else после тела цикла, то код по else будет выполнен только в том случае, если цикл завершился «нормально», т.е. в цикле не исполнилось break. Пример:

stack = [1, 3, 5, 7]
while stack:
    if stack.pop() == 4:
        break
else:
    print('not found!')

Пример. Простые числа и множители:

for n in range(2, 10):
    for x in range(2, n):
        if n % x == 0:
            # есть делитель, уходим
            print(n, '=', x, '*', n/x)
            break
    else:
        # цикл не нашел делителей
        print(n, 'простое число!')
2. try/else

В блоке try код else выполняется только в том случае, если не возникло исключений. else можно написать только после блока except, без него – нельзя. Порядок выполнения кода соответствует порядку написания сверху вниз: tryexcept или elsefinally.

try:
    ...
except Exception:
    print('Exception!')
else:
    print('Ok!')
finally:
    print('Bye!')

Причем else не будет также вызван, если сработавшее исключение не подпало под перечисленные except и не было обработано.

🧙 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Тонкости try

Что вернет функция foo()?
def foo():
    try:
        return 'try'
    finally:
        return 'finally'

foo()

Правильный ответ будет ‘finally’:

Дело в том, что функция возвращает результат последнего выполненного return. А, учитывая, что блок finally всегда выполняется, то будет выполнено два return, последний из них будет return ‘finally’.

Что будет при вложенных блоках finally?
# вспомогательная функция, чтобы считать return-ы
def returner(s):
    print(f'  return {s}')
    return s

def foo():
    try:
        return returner('try')
    finally:
        return returner('finally')

print('Result: ', foo())

print('-' * 50)

def baz():
    try:
        try:
            return returner('try')
        finally:
            return returner('finally inner')
    finally:
        return returner('finally outer')

print('Result: ', baz())

Вывод:

  return try
  return finally
Result:  finally
--------------------------------------------------
  return try
  return finally inner
  return finally outer
Result:  finally outer

Как видим срабатывают все return (срабатывают, значит вычисляются аргументы выражения return), но будет возвращен из функции результат только последнего return.

Еще один коварный вопрос про try и finally.

Что будет при выполнении кода?


for i in range(10):
    try:
        print(1 / i)
    finally:
        print('finally')
        break

На первой итерации цикла произойдет исключение из-за деления на 0. Блока except нет. Но тем не менее исключение все равно будет подавлено, потому что в блоке finally есть break. Вот такая особенность языка. В будущих версиях (3.8+) тоже самое должно работать и с конструкцией continue.

🧙 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈