# События lifespan { #lifespan-events }

Вы можете определить логику (код), которую нужно выполнить перед тем, как приложение начнет запускаться. Это означает, что этот код будет выполнен один раз, перед тем как приложение начнет получать HTTP-запросы.

Аналогично, вы можете определить логику (код), которую нужно выполнить, когда приложение завершает работу. В этом случае код будет выполнен один раз, после обработки, возможно, многих запросов.

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

Это может быть очень полезно для настройки ресурсов, которые нужны для всего приложения, которые разделяются между запросами и/или которые нужно затем очистить. Например, пул подключений к базе данных или загрузка общей модели Машинного обучения.

## Вариант использования { #use-case }

Начнем с примера варианта использования, а затем посмотрим, как это решить.

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

Эти же модели разделяются между запросами, то есть это не одна модель на запрос, не одна на пользователя и т.п.

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

Вы могли бы загрузить её на верхнем уровне модуля/файла, но это означало бы, что модель загружается даже если вы просто запускаете простой автоматический тест; тогда этот тест будет медленным, так как ему придется ждать загрузки модели перед запуском независимой части кода.

Именно это мы и решим: давайте загружать модель перед тем, как начнётся обработка запросов, но только непосредственно перед тем, как приложение начнет принимать запросы, а не во время загрузки кода.

## Lifespan { #lifespan }

Вы можете определить логику для startup и shutdown, используя параметр `lifespan` приложения `FastAPI` и «менеджер контекста» (через секунду покажу что это).

Начнем с примера, а затем разберём его подробнее.

Мы создаём асинхронную функцию `lifespan()` с `yield` примерно так:

{* ../../docs_src/events/tutorial003_py39.py hl[16,19] *}

Здесь мы симулируем дорогую операцию startup по загрузке модели, помещая (фиктивную) функцию модели в словарь с моделями Машинного обучения до `yield`. Этот код будет выполнен до того, как приложение начнет принимать запросы, во время startup.

А затем сразу после `yield` мы выгружаем модель. Этот код будет выполнен после того, как приложение закончит обрабатывать запросы, непосредственно перед shutdown. Это может, например, освободить ресурсы, такие как память или GPU.

/// tip | Совет

`shutdown` произойдёт, когда вы останавливаете приложение.

Возможно, вам нужно запустить новую версию, или вы просто устали от него. 🤷

///

### Функция lifespan { #lifespan-function }

Первое, на что стоит обратить внимание, — мы определяем асинхронную функцию с `yield`. Это очень похоже на Зависимости с `yield`.

{* ../../docs_src/events/tutorial003_py39.py hl[14:19] *}

Первая часть функции, до `yield`, будет выполнена до запуска приложения.

А часть после `yield` будет выполнена после завершения работы приложения.

### Асинхронный менеджер контекста { #async-context-manager }

Если присмотреться, функция декорирована `@asynccontextmanager`.

Это превращает функцию в «асинхронный менеджер контекста».

{* ../../docs_src/events/tutorial003_py39.py hl[1,13] *}

Менеджер контекста в Python — это то, что можно использовать в операторе `with`. Например, `open()` можно использовать как менеджер контекста:

```Python
with open("file.txt") as file:
    file.read()
```

В последних версиях Python есть также асинхронный менеджер контекста. Его используют с `async with`:

```Python
async with lifespan(app):
    await do_stuff()
```

Когда вы создаёте менеджер контекста или асинхронный менеджер контекста, как выше, он перед входом в блок `with` выполнит код до `yield`, а после выхода из блока `with` выполнит код после `yield`.

В нашем примере выше мы не используем его напрямую, а передаём его в FastAPI, чтобы он использовал его сам.

Параметр `lifespan` приложения `FastAPI` принимает асинхронный менеджер контекста, поэтому мы можем передать ему наш новый асинхронный менеджер контекста `lifespan`.

{* ../../docs_src/events/tutorial003_py39.py hl[22] *}

## Альтернативные события (устаревшие) { #alternative-events-deprecated }

/// warning | Предупреждение

Рекомендуемый способ обрабатывать startup и shutdown — использовать параметр `lifespan` приложения `FastAPI`, как описано выше. Если вы укажете параметр `lifespan`, обработчики событий `startup` и `shutdown` больше вызываться не будут. Либо всё через `lifespan`, либо всё через события — не одновременно.

Эту часть, скорее всего, можно пропустить.

///

Есть альтернативный способ определить логику, которую нужно выполнить во время startup и во время shutdown.

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

Эти функции можно объявить с `async def` или обычным `def`.

### Событие `startup` { #startup-event }

Чтобы добавить функцию, которую нужно запустить до старта приложения, объявите её как обработчик события `"startup"`:

{* ../../docs_src/events/tutorial001_py39.py hl[8] *}

В этом случае функция-обработчик события `startup` инициализирует «базу данных» items (это просто `dict`) некоторыми значениями.

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

И ваше приложение не начнет принимать запросы, пока все обработчики события `startup` не завершатся.

### Событие `shutdown` { #shutdown-event }

Чтобы добавить функцию, которую нужно запустить при завершении работы приложения, объявите её как обработчик события `"shutdown"`:

{* ../../docs_src/events/tutorial002_py39.py hl[6] *}

Здесь функция-обработчик события `shutdown` запишет строку текста `"Application shutdown"` в файл `log.txt`.

/// info | Информация

В функции `open()` параметр `mode="a"` означает «добавление» (append), то есть строка будет добавлена в конец файла, без перезаписи предыдущего содержимого.

///

/// tip | Совет

Обратите внимание, что в этом случае мы используем стандартную Python-функцию `open()`, которая взаимодействует с файлом.

То есть это I/O (ввод/вывод), требующий «ожидания» записи на диск.

Но `open()` не использует `async` и `await`.

Поэтому мы объявляем обработчик события обычным `def` вместо `async def`.

///

### `startup` и `shutdown` вместе { #startup-and-shutdown-together }

С высокой вероятностью логика для вашего startup и shutdown связана: вы можете хотеть что-то запустить, а затем завершить, получить ресурс, а затем освободить его и т.д.

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

Поэтому теперь рекомендуется использовать `lifespan`, как описано выше.

## Технические детали { #technical-details }

Немного технических подробностей для любопытных умников. 🤓

Под капотом, в ASGI-технической спецификации, это часть <a href="https://asgi.readthedocs.io/en/latest/specs/lifespan.html" class="external-link" target="_blank">Протокола Lifespan</a>, и он определяет события `startup` и `shutdown`.

/// info | Информация

Вы можете прочитать больше про обработчики `lifespan` в Starlette в <a href="https://www.starlette.dev/lifespan/" class="external-link" target="_blank">документации Starlette по Lifespan</a>.

Включая то, как работать с состоянием lifespan, которое можно использовать в других частях вашего кода.

///

## Подприложения { #sub-applications }

🚨 Имейте в виду, что эти события lifespan (startup и shutdown) будут выполнены только для основного приложения, а не для [Подприложения — Mounts](sub-applications.md){.internal-link target=_blank}.
