Проектирование асинхронного API в Python
(Примечание: этот вопрос строго касается дизайна API, а не того, как его реализовать, то есть я забочусь только о том, что видит клиент моего API, а не о том, что я должен сделать, чтобы он работал.)
В простых терминах: я хочу знать установленный шаблон - если есть - для явных фьючерсов (aka promises, aka отложенные, а также имена задач варьируются в зависимости от на основе) в Python. Ниже приведено более подробное описание.
Рассмотрим простой API-интерфейс Python, например:
def read_line():
...
s = read_line()
print(s)
Это синхронная версия - она блокирует, если линия еще не доступна. Предположим теперь, что я хочу предоставить соответствующую асинхронную (неблокирующую) версию, которая позволяет зарегистрировать обратный вызов, который будет вызываться после завершения операции. Например. простая версия может выглядеть так:
def read_line_async(callback):
...
read_line_async(lambda s: print(s))
Теперь, на других языках и в рамках фреймворка, часто существуют утвержденные или, по крайней мере, устоявшиеся шаблоны для таких API. Например, в .NET до версии 4 обычно предоставляется пара методов BeginReadLine
/EndReadLine
и используется интерфейс запаса IAsyncResult
для регистрации обратных вызовов и передачи результирующих значений. В .NET 4+ используется System.Threading.Tasks
, чтобы включить все операции объединения задач (WhenAll
и т.д.) И подключиться к функции С# 5.0 async
.
В другом примере, в JavaScript, в стандартной библиотеке нет ничего, чтобы покрыть это, но jQuery популяризировал интерфейс "отложенного обещания", который теперь отдельно указан. Поэтому, если я должен написать async readLine
в JS, я бы назвал его readLineAsync
и внедрил метод then
в возвращаемое значение.
Что, если таковые имеются, является установленным образцом на земле Питона? Просматривая стандартную библиотеку, я вижу несколько модулей, предлагающих асинхронные API, но между ними нет согласованного шаблона и ничего подобного стандартизованному протоколу для "задач" или "promises". Возможно, существует некоторая модель, которая может быть получена из популярных сторонних библиотек?
Я также видел (часто упоминаемый в этом контексте) Deferred класс в Twisted, но, похоже, он переустроен для универсального обещания API и, скорее, адаптированы к конкретным потребностям этой библиотеки. Это не похоже на то, что я мог бы легко клонировать интерфейс (не принимая на себя зависимость от них), чтобы наш promises хорошо взаимодействовал, если клиент использует обе библиотеки вместе в своем приложении. Есть ли какая-либо другая популярная библиотека или фреймворк, для которой явно разработан API для этого, я мог бы копировать (и взаимодействовать с) без прямой зависимости?
Ответы
Ответ 1
Хорошо, поэтому я нашел PEP-3148, который имеет класс Future
. Я не могу использовать его так, как есть, насколько я вижу, потому что правильные экземпляры создаются только Executor
, и это класс для преобразования существующих синхронных API-интерфейсов в асинхронные, например. перемещение синхронного вызова в фоновый поток. Тем не менее, я могу точно реплицировать методы, предоставляемые объектами Future
, - они очень точно соответствуют тому, что я ожидаю, т.е. Способности (блокировать) запрос для результата, отмены и добавления обратного вызова.
Это звучит как разумный подход? Если это, возможно, будет сопровождаться предложением добавить абстрактный базовый класс для общей "будущей" концепции в стандартную библиотеку Python, так же как коллекции Python имеют свои ABCs.
Ответ 2
Прочитайте различные "серверные" библиотеки для подсказок.
Хорошим примером является BaseHTTPServer
В частности, определение класса HTTPServer показывает, как предоставляется "класс обработчика".
Каждый запрос создает экземпляр класса обработчика. Затем этот объект обрабатывает запрос.
Если вы хотите написать "асинхронный ввод-вывод" с "обратным вызовом", вы должны предоставить класс ReadHandler
для вашего читателя.
class AsyncReadHandler( object ):
def input( self, line, server ):
print( line )
read_line_async( AsyncReadHandler )
Что-то вроде этого будет следовать некоторым установленным шаблонам проектирования.
Ответ 3
Вы еще не посмотрели декораторы
from threading import Thread
def addCallback(function):
def result(parameters,callback):
# Run the function.
result = function(parameters)
# Run the callback asynchronously.
Thread(target=callback).start()
# Run the callback synchronously.
#callback()
# Return the value of the function.
return result
return result
@ addCallback
def echo(value):
print value
def callback():
print 'Callback'
echo('Hello World!',callback)