Спасибо за оценку! Добавлю, что во втором примере скорее корректнее будет сказать, что кто-то другой сообщает о том, что кино скачалось, например, на телефон приходит пуш и тот запускает цепочку других, завязанных на него действий. Иначе это будет неэффективный подход, где в цикле while True идет проверка на получение всех данных.
Доброе утро! В статье я указал, что речь идет про асинхронный однопоточный подход, но вы правы, я не связал это явно с Симой и Асей, а без этого не понятно как используются ресурсы. Например, в примере с кофеваркой, речь идет про разные кофеварки и разные инструкции к ним и соответственно никто никого не ждет. Внес правку, надеюсь сейчас станет понятнее, что я хотел сказать.
Также согласен, что потокобезопасность - это очень важная тема, но во-первых она достаточно сложна для вводной статьи, на мой взгляд, а во вторых, так как статья в основном про Python, то GIL позволяет нам не погружаться глубоко в синхронизацию доступа к памяти. Постарался намекнуть, что такая особенность возникает и очень важна, в части статьи про сравнение с Go, но решил не раскрывать до конца. Сейчас добавил слово "потокобезопасность" в статью, чтобы интересующиеся могли как минимум загуглить термин)
Спасибо, что дали фидбек и подсветили неясные места!
Спасибо большое за оценку и за замечание про память в многопоточности! Внес это предложение в момент финальных правок и, видимо, на автомате провел аналогию из процессов к потокам. Исправил)
Добрый вечер и спасибо за комментарий! Как-раз читал вашу сегодняшнюю статью в момент финальных правок по своей)
Постараюсь обосновать по пунктам, почему я написал именно так: 1) Спасибо за одобрение Симы и Аси, надеюсь вышло наглядно) 2) Говоря о параллелизме я хотел донести, что это всегда про многопоточные приложения, то есть как раз про те которые копируют память полностью или частично при запуске параллельного вычисления. В контексте того параграфа, мне важнее было донести, что параллелизм требует накладных расходов на вспомогательные сущности (потоки/процессы), чем погружаться в полный разбор как параллелизм можно организовать. 3) В пункте о скачивании файлов по-чанково я действительно немного слукавил и поэтому специально пометил курсивом текст "стандартными средствами". Например, в том же Django (говорю про версию < 4) стандартным средством, по моему мнению, для обработки загружаемого файла является форма с полем forms.FileField и соответственно стандартные хендлеры загрузки, логику работы которых необходимо менять, чтобы появилась возможность обрабатывать файл по кусочкам на стороне view. 4) Насчет ответа по вебсокету - согласен, но я и не исключал этой возможности в синхронном подходе. Синхронный сервис в принципе может дождаться всего, чего захочет, но будет ли это ожидание эффективно? Скорее всего нет. Вы сами пишите, что используете прослойку для работы с вебсокет-соединениям. Long polling можно не использовать, если у вас есть такая возможность. VK и Telegram, например, не позволяют установить websocket канал для получения событий, а функционал webhook'ов в ходе наши долгих наблюдений, оказался менее надежен и быстр, чем Long Polling (имею в виду именно доставку сообщений до нашего приложения). 5) Насчет Django - я сказал лишь то, что поддержка асинхронности последнее время становилась все более полной, но возможно, как вы сказали, этот процесс остановится. Про полностью блокирующий ORM - не совсем согласен. Я, скорее всего не углублялся в текущую ситуацию с ORM также как вы, но руководствуюсь информацией из официальной документации, где сказано, что частично это уже реализовано, но возможно что это лишь интерфейс для совместимости. 6) Про Голыгиных я написал специально, так как в свое время проводил вебинар по этому материалу и многим слушателям было интересно именно некое, пусть немного субъективное, сравнение с Golang и nodejs, так что "новички" в целом не должны поразиться сложности материала. С nodejs я не стал проводить сравнение по причинам крайней похожести модели асинхронности, а вот с Go провел по двум причинам (при этом почти не обсуждая преимущества и недостатки реализации именно асинхронной модели). Во-первых, обозначить минусы разработки на Go по отношению к Python и сделать акцент на то, что иногда важнее скорость разработки, а не скорость работы программы. Во-вторых, развенчать некий миф, что приложения на Python - это всегда что-то медленное и не подающееся ускорению. Именно поэтому и прикрепил ссылку на бенчмарки "в вакууме" - не для того, чтобы показать, что Python лучше всех в идеальных условиях, а для того, чтобы продемонстрировать, что существуют инструменты (например, сишное API), которые позволяют программам на Python показывать схожую Go производительность, как минимум в искусственных задачах.
Надеюсь, что мне удалось, хотя бы в некоторых пунктах, объяснить вам почему я написал именно так, а не иначе, но если я не прав, то, пожалуйста, укажите на мои ошибки.
Привет! Спасибо за замечания, действительно, вы указали на не совсем явные моменты. Прокомментирую по пунктам: 1) Асинхронный контекстный менеджер осознанно не использовался, так как статья больше обучающая, а я по своему опыту изучения языка знаю, что контекстные менеджеры изначально немного пугают (может, конечно, только у меня такой опыт с ними) и скорее всего потребовалось бы отдельно про них рассказывать. Но использовать его конечно было бы удобнее и безопаснее 2) Асинхронность нужна, как минимум библиотеке aiohttp для работы - если перейти в source-код базового View, то там будет такие строчки:
Но конечно это не главное - даже при передаче небольших html-файлов мы должны использовать асинхронность в асинхронном приложении. Если у нас будет медленный принимающий клиент, то даже передача нескольких сотен байт может заблокировать наш event loop на значительное время. Надеюсь я правильно интерпретировал смысл вашего комментария 3) Насчет генератора спорно - asyncio.gather принимает на вход список корутин, то есть генератор все-равно придется распаковать перед вызовом gather. Не могу сказать без измерений что эффективнее, но мне кажется, что разница должна быть не такой значительной 4) Да, действительно, поправлю 5) Геттеры и сеттеры тоже не слишком простая тема, но главной мотивацией для их не использования я бы назвал то, что нам не надо обновлять таймаут при записи в соединение, поэтому их применение кажется не совсем верным здесь
Если Ваше замечание касается локальных импортов, то Вы правы - здесь они излишни. Видимо они были добавлены в первую итерацию написания кода для этой статьи и в свое время "развязывали" циклические импорты. В текущей версии кода они не нужны, поправил это, спасибо за замечание.
Для новичков можно сказать, что использования локальных импортов необходимо в двух случаях: 1. При кольцевых импортах. То есть когда разные модули вашего приложения импортят друг друга по кругу. 2. Для повышения скорости первоначального запуска вашего приложения. "Ленивые" импорты позволяют не загружать при запуске все модули, а подгружать их по мере необходимости.
Критической ошибки в локальных импортах нет - код в большинстве случаев будет исполняться как и задумано. Хочу заметить три нюанса из моей практики, связанных с локальными импортами: 1. В одном файле два объекта могут импортировать один и тот же модуль локально - соответственно он будет импортирован два раза локально, вместо одного глобального, что повлияет на производительность. 2. Сколько раз будет исполнен код с импортом - столько раз модуль будет импортирован. Это может повлиять на производительность. В статье функции с локальными импортами исполняются один раз, поэтому разницы между глобальным и локальным импортом немного. 3. Импорт наверху файла улучшает читаемость кода - при взгляде на файл, сразу видно что он использует и с чем он связан.
1. Вы правы, порядок модулей в requirements.txt неверен, поправил.
2. Здесь мной также допущена ошибка, миграции действительно не автогенерировались в таком окружении. Дело в том, что Alembic видит только те модели, которые были импортированы в момент генерации миграций. Соответственно в коде статьи модель Message не была импортирована и Alembic ее не видел. Скорее всего импорт был, но так как явно его ничего не использовало, то он был убран при оптимизации. Чтобы таких ситуации больше не возникало и миграции могли быть автоматически сгенерированы, я добавил в функцию run_migrations_online в файл migrations/env.py импорт и инстанцирование PostgresAccessor, который в свою очередь импортирует все нужные модели. Теперь модели будут в памяти при генерации миграций и импорт будет использован явно.
Большое спасибо Вам, что заметили ошибки и настояли на своем мнении!
Начал добавлять к статье Ваши замечания и понял, что они не совсем актуальны: 1. SQLAlchemy по материалам статьи не устанавливается явно - она устанавливается, как зависимость Gino, что гарантирует, что версия SQLAlchemy будет совместима с версией Gino. 2. Действительно функции upgrade() и downgrade() можно написать самостоятельно, но по материалам статьи они автогенерируются связкой SQLAlchemy + Alembic (см. 7 — Генерируем миграцию)
В любом случае постараюсь побольше акцентировать внимание на этих вещах. Еще раз спасибо!
Да, действительно SQLAlchemy, начиная с 1.4 уже имеет встроенную поддержку asyncio, что позволяет не использовать Gino в большинстве asyncio-проектов. При этом в статье мы рассказываем про Gino по двум причинам:
2) Большинство проектов нашей компании используют Gino, так как стартовали до выхода SQLAlchemy 1.4. Эту статью мы даем студентам наших курсов - потенциальным будущим стажерам - в качестве доп. материала и поэтому нам хотелось бы, чтобы они учились тому, что используем мы.
Спасибо за вопрос, добавлю в статью упоминание о SQLAlchemy 1.4 для полной картины
Спасибо за замечание, структура приложения в этой статье — заготовка для дальнейшей разработки, например в следующей части в main.py добавляется больше разного по функциональности кода, например настройка routes, middlewares и signals. Поэтому было решение заранее разделить код по разным функциям, а не мешать в одну кучу. Насчет лишнего импорта согласен — он используется только во второй части статьи и возможно не стоило оставлять его здесь. Сейчас исправлю.
Спасибо за комментарий, согласен что проще дернуть руками, чем делать интерфейс. На одной из лекций нашей школы мы как-раз рассказываем о том, как настроить генерацию swagger-а и поднять swagger UI, с помощью которого большинство студентов и взаимодействуют с API. Однако, наша главная задача заключается в том, чтобы в конце курса у обучающихся получился MVP с разными фичами, а графический интерфейс доступный другим пользователям — это хороший плюс к проекту. Это только первая часть статьи, в которой акцент именно на то, как начать работать с aiohttp, в следующих частях будут рассмотрены более практичные для начинающего aiohttp-разработчика вещи.
Да, с точки зрения профессиональной разработки это действительно несет не много смысла в современных реалиях. Но в контексте обучения начинающих backend-разработчиков, html-шаблоны позволяют, например, сделать простейший интерфейс для приложения человеку с минимальными познаниями во фронтенде и без дополнительного развертывания раздающего статику веб-сервера.
Спасибо за оценку!
Добавлю, что во втором примере скорее корректнее будет сказать, что кто-то другой сообщает о том, что кино скачалось, например, на телефон приходит пуш и тот запускает цепочку других, завязанных на него действий. Иначе это будет неэффективный подход, где в цикле while True идет проверка на получение всех данных.
Доброе утро!
В статье я указал, что речь идет про асинхронный однопоточный подход, но вы правы, я не связал это явно с Симой и Асей, а без этого не понятно как используются ресурсы. Например, в примере с кофеваркой, речь идет про разные кофеварки и разные инструкции к ним и соответственно никто никого не ждет. Внес правку, надеюсь сейчас станет понятнее, что я хотел сказать.
Также согласен, что потокобезопасность - это очень важная тема, но во-первых она достаточно сложна для вводной статьи, на мой взгляд, а во вторых, так как статья в основном про Python, то GIL позволяет нам не погружаться глубоко в синхронизацию доступа к памяти. Постарался намекнуть, что такая особенность возникает и очень важна, в части статьи про сравнение с Go, но решил не раскрывать до конца. Сейчас добавил слово "потокобезопасность" в статью, чтобы интересующиеся могли как минимум загуглить термин)
Спасибо, что дали фидбек и подсветили неясные места!
Спасибо за замечание! Перепутал с IP, исправил
Спасибо большое за оценку и за замечание про память в многопоточности!
Внес это предложение в момент финальных правок и, видимо, на автомате провел аналогию из процессов к потокам. Исправил)
Добрый вечер и спасибо за комментарий! Как-раз читал вашу сегодняшнюю статью в момент финальных правок по своей)
Постараюсь обосновать по пунктам, почему я написал именно так:
1) Спасибо за одобрение Симы и Аси, надеюсь вышло наглядно)
2) Говоря о параллелизме я хотел донести, что это всегда про многопоточные приложения, то есть как раз про те которые копируют память полностью или частично при запуске параллельного вычисления. В контексте того параграфа, мне важнее было донести, что параллелизм требует накладных расходов на вспомогательные сущности (потоки/процессы), чем погружаться в полный разбор как параллелизм можно организовать.
3) В пункте о скачивании файлов по-чанково я действительно немного слукавил и поэтому специально пометил курсивом текст "стандартными средствами". Например, в том же Django (говорю про версию < 4) стандартным средством, по моему мнению, для обработки загружаемого файла является форма с полем forms.FileField и соответственно стандартные хендлеры загрузки, логику работы которых необходимо менять, чтобы появилась возможность обрабатывать файл по кусочкам на стороне view.
4) Насчет ответа по вебсокету - согласен, но я и не исключал этой возможности в синхронном подходе. Синхронный сервис в принципе может дождаться всего, чего захочет, но будет ли это ожидание эффективно? Скорее всего нет. Вы сами пишите, что используете прослойку для работы с вебсокет-соединениям.
Long polling можно не использовать, если у вас есть такая возможность. VK и Telegram, например, не позволяют установить websocket канал для получения событий, а функционал webhook'ов в ходе наши долгих наблюдений, оказался менее надежен и быстр, чем Long Polling (имею в виду именно доставку сообщений до нашего приложения).
5) Насчет Django - я сказал лишь то, что поддержка асинхронности последнее время становилась все более полной, но возможно, как вы сказали, этот процесс остановится. Про полностью блокирующий ORM - не совсем согласен. Я, скорее всего не углублялся в текущую ситуацию с ORM также как вы, но руководствуюсь информацией из официальной документации, где сказано, что частично это уже реализовано, но возможно что это лишь интерфейс для совместимости.
6) Про Голыгиных я написал специально, так как в свое время проводил вебинар по этому материалу и многим слушателям было интересно именно некое, пусть немного субъективное, сравнение с Golang и nodejs, так что "новички" в целом не должны поразиться сложности материала.
С nodejs я не стал проводить сравнение по причинам крайней похожести модели асинхронности, а вот с Go провел по двум причинам (при этом почти не обсуждая преимущества и недостатки реализации именно асинхронной модели).
Во-первых, обозначить минусы разработки на Go по отношению к Python и сделать акцент на то, что иногда важнее скорость разработки, а не скорость работы программы.
Во-вторых, развенчать некий миф, что приложения на Python - это всегда что-то медленное и не подающееся ускорению. Именно поэтому и прикрепил ссылку на бенчмарки "в вакууме" - не для того, чтобы показать, что Python лучше всех в идеальных условиях, а для того, чтобы продемонстрировать, что существуют инструменты (например, сишное API), которые позволяют программам на Python показывать схожую Go производительность, как минимум в искусственных задачах.
Надеюсь, что мне удалось, хотя бы в некоторых пунктах, объяснить вам почему я написал именно так, а не иначе, но если я не прав, то, пожалуйста, укажите на мои ошибки.
Добрый день! Спасибо за рекомендацию, не слышал раньше о таком фреймворке, познакомлюсь.
Привет! Спасибо за замечания, действительно, вы указали на не совсем явные моменты. Прокомментирую по пунктам:
1) Асинхронный контекстный менеджер осознанно не использовался, так как статья больше обучающая, а я по своему опыту изучения языка знаю, что контекстные менеджеры изначально немного пугают (может, конечно, только у меня такой опыт с ними) и скорее всего потребовалось бы отдельно про них рассказывать. Но использовать его конечно было бы удобнее и безопаснее
2) Асинхронность нужна, как минимум библиотеке aiohttp для работы - если перейти в source-код базового View, то там будет такие строчки:
Но конечно это не главное - даже при передаче небольших html-файлов мы должны использовать асинхронность в асинхронном приложении. Если у нас будет медленный принимающий клиент, то даже передача нескольких сотен байт может заблокировать наш event loop на значительное время. Надеюсь я правильно интерпретировал смысл вашего комментария
3) Насчет генератора спорно - asyncio.gather принимает на вход список корутин, то есть генератор все-равно придется распаковать перед вызовом gather. Не могу сказать без измерений что эффективнее, но мне кажется, что разница должна быть не такой значительной
4) Да, действительно, поправлю
5) Геттеры и сеттеры тоже не слишком простая тема, но главной мотивацией для их не использования я бы назвал то, что нам не надо обновлять таймаут при записи в соединение, поэтому их применение кажется не совсем верным здесь
Еще раз спасибо вам за развернутый комментарий!
Добрый день!
Если Ваше замечание касается локальных импортов, то Вы правы - здесь они излишни. Видимо они были добавлены в первую итерацию написания кода для этой статьи и в свое время "развязывали" циклические импорты. В текущей версии кода они не нужны, поправил это, спасибо за замечание.
Для новичков можно сказать, что использования локальных импортов необходимо в двух случаях:
1. При кольцевых импортах. То есть когда разные модули вашего приложения импортят друг друга по кругу.
2. Для повышения скорости первоначального запуска вашего приложения. "Ленивые" импорты позволяют не загружать при запуске все модули, а подгружать их по мере необходимости.
Критической ошибки в локальных импортах нет - код в большинстве случаев будет исполняться как и задумано. Хочу заметить три нюанса из моей практики, связанных с локальными импортами:
1. В одном файле два объекта могут импортировать один и тот же модуль локально - соответственно он будет импортирован два раза локально, вместо одного глобального, что повлияет на производительность.
2. Сколько раз будет исполнен код с импортом - столько раз модуль будет импортирован. Это может повлиять на производительность. В статье функции с локальными импортами исполняются один раз, поэтому разницы между глобальным и локальным импортом немного.
3. Импорт наверху файла улучшает читаемость кода - при взгляде на файл, сразу видно что он использует и с чем он связан.
Добрый день!
1. Вы правы, порядок модулей в requirements.txt неверен, поправил.
2. Здесь мной также допущена ошибка, миграции действительно не автогенерировались в таком окружении. Дело в том, что Alembic видит только те модели, которые были импортированы в момент генерации миграций. Соответственно в коде статьи модель Message не была импортирована и Alembic ее не видел. Скорее всего импорт был, но так как явно его ничего не использовало, то он был убран при оптимизации. Чтобы таких ситуации больше не возникало и миграции могли быть автоматически сгенерированы, я добавил в функцию run_migrations_online в файл migrations/env.py импорт и инстанцирование
PostgresAccessor
, который в свою очередь импортирует все нужные модели. Теперь модели будут в памяти при генерации миграций и импорт будет использован явно.Большое спасибо Вам, что заметили ошибки и настояли на своем мнении!
Начал добавлять к статье Ваши замечания и понял, что они не совсем актуальны:
1. SQLAlchemy по материалам статьи не устанавливается явно - она устанавливается, как зависимость Gino, что гарантирует, что версия SQLAlchemy будет совместима с версией Gino.
2. Действительно функции upgrade() и downgrade() можно написать самостоятельно, но по материалам статьи они автогенерируются связкой SQLAlchemy + Alembic (см. 7 — Генерируем миграцию)
В любом случае постараюсь побольше акцентировать внимание на этих вещах. Еще раз спасибо!
Спасибо большое за замечания!
В ближайшем времени добавлю их в статью.
Добрый день!
Да, действительно SQLAlchemy, начиная с 1.4 уже имеет встроенную поддержку asyncio, что позволяет не использовать Gino в большинстве asyncio-проектов. При этом в статье мы рассказываем про Gino по двум причинам:
1) Gino не только обеспечивает асинхронность для SQLAlchemy - он также добавляет различные фичи, поэтому он не станет "мертвым грузом" в проекте. Вот, например, что пишут сами разработчики Gino по поводу добавления поддержки asyncio в SQLAlchemy и как они планируют развивать Gino дальше: https://python-gino.org/docs/en/1.0/how-to/faq.html#sqlalchemy-1-4-supports-asyncio-what-will-gino-be
2) Большинство проектов нашей компании используют Gino, так как стартовали до выхода SQLAlchemy 1.4. Эту статью мы даем студентам наших курсов - потенциальным будущим стажерам - в качестве доп. материала и поэтому нам хотелось бы, чтобы они учились тому, что используем мы.
Спасибо за вопрос, добавлю в статью упоминание о SQLAlchemy 1.4 для полной картины