Comments 26
Ситуацию немного исправило добавление специальных директив async и await (как в JavaScript, что важно)
Вы ничего не перепутали?
* Python 3.5 was released on September 13, 2015
* ES8 was released on 2017
Моей основной проблемой оказалась необходимость разрабатывать логику, которая может работать и синхронно, и асинхронно.
Python 3.7 was released on June 27, 2018
The new provisional asyncio.run() function can be used to run a coroutine from synchronous code by automatically creating and destroying the event loop. (Contributed by Yury Selivanov in bpo-32314.)
asyncio.run() выполняет корутины, я никогда не заставлю обычную функцию выполнятся асинхронно, так что по сути не решает проблему никак
P.S. Ошибка, нашёл инфу о том, что официальная поддержка async/await появилась только в ES8. Спасибо, исправляю
я никогда не заставлю обычную функцию выполнятся асинхронно
docs.python.org/3/library/asyncio-eventloop.html#executing-code-in-thread-or-process-pools
asyncio.run() выполняет корутины, я никогда не заставлю обычную функцию выполнятся асинхронно, так что по сути не решает проблему никак
А как необходимость запускать блокирующую функцию в асинхронном окружении соотносится с вашей исходной задачей? Изначально вы поставили задачу — написать код один раз так, что бы его можно было использовать либо в асинхронном окружении, либо в синхронном. Тут совсем нет речи про то, что надо из асинхронного окружения вызывать какой-то блокирующий код. Это совсем другая задача.
В исходной задаче асинхронную версию библиотеки можно использовать в качестве основной и базовой реализации. А работу с ним из синхронного окружения либо переложить на пользователей (пускай сами явно используют asyncio.run() или loop.run_until_complete()). Или сделать какой-то декоратор, который ко всем вашим функциям и методам добавит синхронный враппер, использующий внутри себя asyncio.run(). Например такой враппер можно сохранять в свойство самой функции, что бы вызывать это как-то так:
my_async_func.sync()
PS: Стоит заметить, что asyncio.run(), вероятно, будет добавлять какой-то оверхед на создание евент-лупа и его удаление при каждом вызове ваших методов из синхронного окружения. Поэтому требуется тестирование. Как вариант можно попробовать реализовать однократное создание евент-лупа для вашей библиотеки с помощью какой-то функции для "настройки" работы библиотеки в синхронном окружении. И в дальнейшем использовать loop.run_until_complete().
Вот ещё один взгляд на ту же тему, но с другой стороны.
https://habr.com/ru/company/oleg-bunin/blog/512650/
Автору все же не мешало бы понять как различаются параллельное и асинхронное исполнение.
Да и с работой goroutine в Golang тоже стоит разобраться, что бы не сравнивать ее с асинхронностью в питоне.
Нет, но разве GIL как-то влияет на асинхронный код?
Он в итоге не дает работать более чем в один поток. Хотите больше чем в один — subprocess и запуск несколких отдельных рантаймов питона. Все что решает async — это просто смена концепции исполнения кода с синхронного на асинхронный вариант.
Зато с GIL не надо парится про параллельный доступ к разделяемым данным.
Goroutine — является по сути блоком данных, являющийся стеком. А исполняют их воркеры, которые рантайм Golang запускает при старте программы по числу доступных потоов процессора (если не ограничено явно). И вот в goroutine (если воркеров более одного) и есть параллельное исполнение (и асинхронное тоже). Но это не бесплатно — при параллельном исполнении нужно внимательно следить и защищать данные используемые разными goroutene-ами. Ситуацию чуть упрощает использование каналов, но они конечно же не покрывают все необходимости. Поэтому в Golang кроме каналов и атомики и мьютексы тоже используются.
К сожалению GIL не избавляет от необходимости следить за параллельным доступом к вашим данным. GIL защищает от параллельного доступа исключительно внутренности самого интерпретатора. А данные вашей программы он защитить не может, т.к. не знает где и как они используются.
P.S. Ну в общем если считать, что асинхронность в Python основана на генераторах и на возвращении управлении в цикл, то очевидно, что сравнивать их и нельзя, совсем по разному работают
Покажу небольшой пример шаблона, который предполагает возможность делать запросы в google:
К сожалению, шаблон получился длиннее и сложнее для восприятия, чем синхронная и асинхронная функция по отдельности.
Не проще ли просто продублировать код в разных стилях?
# синхронная функция в вашей библиотеке
from instalib import get_photos
# асинхронная
from instalib import async_get_photos
# асинхронная вариант 2
from instalib_async import get_photos
да, конечно это дублирование кода для вас, как автора библиотеки, но, если библиотека станет популярной, то усилия по поддержке кода в библиотеке многократно окупятся сэкономленным временем ваших благодарных пользователей. Кроме того, синхронный и асинхронный код будет проще покрывать тестами по отдельности.
Моей основной проблемой оказалась необходимость разрабатывать логику, которая может работать и синхронно, и асинхронно.
Если все же вы настаиваете на библиотеке функций, которые могут вызываться как из синхронного, так и из асинхронного кода, то моей первой мыслью было бы создать для каждой экспортируемой функции фабрику, которая бы отдавала синхронную или асинхронную версию функции в зависимости от наличия запущенного Event Loop. Хотя нет, это плохая идея уже потому, что функция получится грязной (зависящей от внешних условий).
Может быть просто скрыть ваш шаблон в импортированном модуле, а пользователю отдавать функцию, которая внутри содержит фабрику, которая и будет запускать функцию асинхронно или синхронно?
Как-то так:
from instalib import get_photos
get_photos(id, 'sync')
# or
async get_photos(id, 'async')
Насколько я вижу, все эти сложности приводят к тому, что библиотеки питона для работы с сетью четко делятся на синхронные и асинхронные.
P.S. Позвольте порекомендовать вам к чтению вот эту статью: https://habr.com/ru/post/479186/
python язык с отличным синтаксисом… (пишу на обоих, если что)
Итого — нужен питоно-образный golang, а Сиpython надо переписать на go, чтоб на несколько ядер забегал. (на заметку людям, зачем-то совершающим подвиги создания идентичных клонов go (имею ввиду vlang и umka) ) вместо {{см.выше}} В конце-концов go это же такой новый Си. Пора, пора на нем питон забабахать! Не знаю, как остальные, а я бы конечно хотел питон с горутинами и канальями :-)
вот, написал, в качестве концепта :-)
print в golang:
package main
import "fmt"
func main() {
bar := 789
var c int
c = 987
print := fmt.Printf
fmt.Printf("fmt.Printf ==> %d", c)
print("\n python print ==> %d", c)
print("\n python print bar ==> %d", bar)
}
Итого — нужен питоно-образный golang, а Сиpython надо переписать на go, чтоб на несколько ядер забегал.
В сторону Julia не пробовали смотреть?
Сиpython надо переписать на go
Я вас умоляю, для этого больше подойдет Rust.
Послушайте, мне одному кажется, что автор проводит знак равенства между асинхронизмом и параллелизмом?
Кажется. Я сам перечитываю и у меня есть такое ощущение, но нет, я просто хотел показать, как где-то любая параллельная работа выполняется красиво, а где-то — нет
Асинхронизм(отложенные вычисления) может/могут быть построен/ы на многопоточности(java case) а могут и на EventLoop(который так же часто называют частной надстройкой поверх многопоточности) или на связке компилятор/"многопоточность" (.net case). В питоне отложенные вычисления базируются на EventLoop, но если я не ошибаюсь, то за все годы многопоточность в питон так и не завезли, есть многозадачность, но это немного не то.
Нет как таковой проблемы дублирования кода, код API одинаковый, разные только адаптеры (requests и aiohttp)
Шаблонные функции в Python, которые могут выполняться синхронно и асинхронно