Как стать автором
Обновить

Асинхронный django. Resurrections

Время на прочтение3 мин
Количество просмотров6.4K

Поздравляю с годом Кролика и желаю сбычи ваших мечт! Хочу вас обрадовать, что, судя по всему, в новом году будет продолжение франшизы про асинхронный django. Версия на гринлетах скоро получит новую, более изящную форму. И новое название - fibers (старое, greenhack, никуда не годилось).

Вот как это выглядит:

async def myview(request):
    async with fibers:
        # Здесь можно использовать django
        obj = MyObject.objects.get()
        print(obj.related_obj)

    # Здесь можно писать асинхронный код
    await notify_somebody(request)

Теперь, использовать django внутри асинхронной функции можно будет под (асинхронным) контекстным менеджером. По-моему, юзабилити сильно выигрывает. И семантически - тоже понятнее, что происходит, нет?

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

Так вот, пример c кодом неплохо смотрится, правда? Вы спросите - почему было сразу так не сделать? Дело в том, что мне только недавно пришло в голову, что это технически возможно. В том случае, если вспомнить, как реализованы корутины в питоне (это генераторы), и воспользоваться этой особенностью реализации. Для нетерпеливых - вот ссылка на gist с кодом.

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

async def hi():
    print('hi')

Несмотря на то, что перед нами асинхронная функция, не обязательно иметь event loop, чтобы запустить её. Можно сделать так:

try:
    gen = hi()
    gen.send(None)
except StopIteration:
    pass

Будет напечатано hi.

У контекстного менеджера fibers - такой же принцип. Он устроен так, что весь код внутри него можно выполнить вызовом gen.send() (вот эта строчка). Дальше, мы оборачиваем этот вызов в гринлет - и применяем наш обычный подход.

Вы знаете, как выглядит простейший Awaitable? Вот один из вариантов, не самый типичный:

class Simple:
    def __await__(self):
        return 'hi'
        yield

await Simple()вернёт'hi'.

__await__- это генератор. В отличие от корутин, он может делать как yield, так и yield from. Тогда как корутины могут делать только await, что соответствует yield from.

Контекстный менеджер fibers тоже использует кастомные Awaitable. Они делают yield специальных значений start и end. Event loop их не поймёт. Поэтому нам нужно обернуть какую-нибудь корутину (например, верхнего уровня), чтобы она не пропускала их наружу. Сделать это относительно несложно: например, если у Вас - ASGI приложение, то можно обернуть это приложение целиком.

Иллюстрацию подхода можно посмотреть в gist. По сути, это просто ещё один "фронтенд" к библиотеке greenhack: для него даже не потребовалось вносить в неё изменения. Удобный API для того же самого, больше ничего.

Когда всё будет готово? Всё упирается в асинхронный бэкенд для django. Он готов, но не протестирован: нужно пройти testsuite для бэкендов django. Там около 70 тестов - когда они станут проходить, проект будет готов к продакшну. Обещать по срокам ничего не буду, но буду писать недельные отчёты (нет, не на хабре - на гитхабе). К сожалению, рабочие проекты не связаны с этой темой.

Кстати, названием проект обязан языку Ruby, точнее, Ruby Fibers. Дело в том, что в Ruby нет async/await, для асинхронности там в принципе не используются "функции другого цвета". Так достигается совместимость блокирующего кода с асинхронным - от которой в питоне отказались. К лучшему или нет - я не берусь сказать. Но я знаю, как это "исправить" - для тех случаев, когда эта совместимость нужна.

UPDATE: Пришло в голову, что такую же штуку можно проделать и с запуском в другом потоке. По типу sync_to_async или asyncify - то, что сейчас документация django рекомендует. Что-то вроде:

async with asyncify:
    # Блокирующий код
    ...

Забавно, конечно, было бы кому-нибудь объяснять, что, на самом деле, этот код выполняется в другом потоке. Зато смотрится эффектно!

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Как вам эти пляски с генераторами?
50% Да, ловкий подход20
37.5% Не согласен: явное лучше неявного15
12.5% Всё очень плохо5
Проголосовали 40 пользователей. Воздержались 14 пользователей.
Теги:
Хабы:
Всего голосов 6: ↑5 и ↓1+4
Комментарии1

Публикации

Истории

Работа

Python разработчик
193 вакансии
Data Scientist
94 вакансии

Ближайшие события

19 августа – 20 октября
RuCode.Финал. Чемпионат по алгоритмическому программированию и ИИ
МоскваНижний НовгородЕкатеринбургСтавропольНовосибрискКалининградПермьВладивостокЧитаКраснорскТомскИжевскПетрозаводскКазаньКурскТюменьВолгоградУфаМурманскБишкекСочиУльяновскСаратовИркутскДолгопрудныйОнлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
24 – 25 октября
One Day Offer для AQA Engineer и Developers
Онлайн
25 октября
Конференция по росту продуктов EGC’24
МоскваОнлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань