Рубрики
Без рубрики

atexit – Обратные вызовы завершения работы программы

Автор оригинала: Doug Hellmann.

Цель:

Зарегистрируйте функцию (ы), которая будет вызываться при закрытии программы.

Модуль atexit предоставляет интерфейс для регистрации функций, которые будут вызываться при нормальном завершении программы.

Регистрация обратных вызовов при выходе

Это пример явной регистрации функции путем вызова register () .

atexit_simple.py

import atexit


def all_done():
    print('all_done()')


print('Registering')
atexit.register(all_done)
print('Registered')

Поскольку программа больше ничего не делает, сразу вызывается all_done () .

$ python3 atexit_simple.py

Registering
Registered
all_done()

Также можно зарегистрировать более одной функции и передавать аргументы зарегистрированным функциям. Это может быть полезно для полного отключения от баз данных, удаления временных файлов и т. Д. Вместо ведения списка ресурсов, которые необходимо освободить, для каждого ресурса можно зарегистрировать отдельную функцию очистки.

atexit_multiple.py

import atexit


def my_cleanup(name):
    print('my_cleanup({})'.format(name))


atexit.register(my_cleanup, 'first')
atexit.register(my_cleanup, 'second')
atexit.register(my_cleanup, 'third')

Функции выхода вызываются в порядке, обратном их регистрации. Этот метод позволяет очищать модули в порядке, обратном их импорту (и, следовательно, регистрировать свои функции atexit ), что должно уменьшить конфликты зависимостей.

$ python3 atexit_multiple.py

my_cleanup(third)
my_cleanup(second)
my_cleanup(first)

Синтаксис декоратора

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

atexit_decorator.py

import atexit


@atexit.register
def all_done():
    print('all_done()')


print('starting main program')

Поскольку функция регистрируется в том виде, в котором она определена, также важно убедиться, что она работает правильно, даже если модуль не выполняет никакой другой работы. Если ресурсы, которые предполагается очистить, никогда не инициализировались, вызов обратного вызова выхода не должен вызывать ошибки.

$ python3 atexit_decorator.py

starting main program
all_done()

Отмена обратных вызовов

Чтобы отменить обратный вызов выхода, удалите его из реестра с помощью unregister () .

atexit_unregister.py

import atexit


def my_cleanup(name):
    print('my_cleanup({})'.format(name))


atexit.register(my_cleanup, 'first')
atexit.register(my_cleanup, 'second')
atexit.register(my_cleanup, 'third')

atexit.unregister(my_cleanup)

Все вызовы одного и того же обратного вызова отменяются, независимо от того, сколько раз он был зарегистрирован.

$ python3 atexit_unregister.py

Удаление ранее не зарегистрированного обратного вызова не считается ошибкой.

atexit_unregister_not_registered.py

import atexit


def my_cleanup(name):
    print('my_cleanup({})'.format(name))


if False:
    atexit.register(my_cleanup, 'never registered')

atexit.unregister(my_cleanup)

Поскольку он молча игнорирует неизвестные обратные вызовы, unregister () может использоваться, даже если последовательность регистраций может быть неизвестна.

$ python3 atexit_unregister_not_registered.py

Когда не вызываются обратные вызовы atexit?

Обратные вызовы, зарегистрированные с помощью atexit , не вызываются, если выполняется любое из этих условий:

  • Программа умирает из-за сигнала.
  • os._exit () вызывается напрямую.
  • В интерпретаторе обнаружена фатальная ошибка.

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

atexit_signal_parent.py

import os
import signal
import subprocess
import time

proc  subprocess.Popen('./atexit_signal_child.py')
print('PARENT: Pausing before sending signal...')
time.sleep(1)
print('PARENT: Signaling child')
os.kill(proc.pid, signal.SIGTERM)

Потомок устанавливает обратный вызов atexit и затем засыпает, пока не поступит сигнал.

atexit_signal_child.py

import atexit
import time
import sys


def not_called():
    print('CHILD: atexit handler should not have been called')


print('CHILD: Registering atexit handler')
sys.stdout.flush()
atexit.register(not_called)

print('CHILD: Pausing to wait for signal')
sys.stdout.flush()
time.sleep(5)

При запуске это результат.

$ python3 atexit_signal_parent.py

CHILD: Registering atexit handler
CHILD: Pausing to wait for signal
PARENT: Pausing before sending signal...
PARENT: Signaling child

Дочерний элемент не печатает сообщение, встроенное в not_called () .

Если в программе используется os._exit () , она может избежать вызова обратных вызовов atexit .

atexit_os_exit.py

import atexit
import os


def not_called():
    print('This should not be called')


print('Registering')
atexit.register(not_called)
print('Registered')

print('Exiting...')
os._exit(0)

Поскольку в этом примере не используется обычный путь выхода, обратный вызов не выполняется. Вывод на печать также не сбрасывается, поэтому пример запускается с параметром -u , чтобы включить небуферизованный ввод-вывод.

$ python3 -u atexit_os_exit.py

Registering
Registered
Exiting...

Чтобы гарантировать выполнение обратных вызовов, позвольте программе завершить работу, закончив операторы для выполнения или вызвав sys.exit () .

atexit_sys_exit.py

import atexit
import sys


def all_done():
    print('all_done()')


print('Registering')
atexit.register(all_done)
print('Registered')

print('Exiting...')
sys.exit()

В этом примере вызывается sys.exit () , поэтому вызываются зарегистрированные обратные вызовы.

$ python3 atexit_sys_exit.py

Registering
Registered
Exiting...
all_done()

Обработка исключений

Трассировки для исключений, возникающих в обратных вызовах atexit , выводятся на консоль, и последнее возникшее исключение повторно возникает, чтобы стать последним сообщением об ошибке программы.

atexit_exception.py

import atexit


def exit_with_exception(message):
    raise RuntimeError(message)


atexit.register(exit_with_exception, 'Registered first')
atexit.register(exit_with_exception, 'Registered second')

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

$ python3 atexit_exception.py

Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "atexit_exception.py", line 11, in exit_with_exception
    raise RuntimeError(message)
RuntimeError: Registered second
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "atexit_exception.py", line 11, in exit_with_exception
    raise RuntimeError(message)
RuntimeError: Registered first

Обычно лучше всего обрабатывать и незаметно регистрировать все исключения в функциях очистки, так как иметь ошибки дампа программы при выходе – это беспорядок.

Смотрите также