在Python中设计异步API

12
(注:此问题严格限于 API 的设计,不涉及如何实现它;即我只关心我的 API 的客户端在这里看到什么,而不是我必须做什么才能使它正常工作。)
简单来说:我想知道 Python 中 显式 future(又名 promises、deferreds、tasks——名称取决于框架)的已建立模式(如果有的话)。以下是更详细的描述。
考虑这样一个简单的 Python API:
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 等),并连接到 C# 5.0 的 async 特性。
另一个例子是,在 JavaScript 中,标准库中没有涵盖这个方面的内容,但 jQuery 已经推广了 "deferred promise" 接口,现在已经 单独指定。因此,如果我要在 JS 中编写异步的 readLine,我将把它命名为 readLineAsync,并在返回值上实现 then 方法。
那么,在 Python 中,是否有已经确立的模式呢?浏览标准库,我看到有几个模块提供异步 API,但它们之间没有一致的模式,也没有像 "tasks" 或 "promises" 的标准化协议。也许可以从流行的第三方库中推导出某些模式?

我也看到了Twisted中经常提到的Deferred类,但它似乎对于通用的promise API来说过于复杂,并且更适合于此库的特定需求。它看起来不像是我可以轻松克隆接口的东西(而不需要依赖它们),这样如果客户端在应用程序中同时使用这两个库,我们的promises就可以很好地互操作。是否有其他流行的库或框架专门为此设计API,我可以复制(并与之互操作),而不需要直接依赖?

3个回答

6

好的,我找到了PEP-3148,其中包含一个Future类。据我所见,我不能直接使用它,因为正确的实例只能由Executor创建,而这是一个将现有同步API转换为异步性的类,例如将同步调用移到后台线程中。然而,我可以完全复制Future对象提供的方法——它们非常接近我的预期,即能够(阻塞式)查询结果、取消并添加回调。

这个方案听起来合理吗?或许应该伴随着向Python标准库添加一个抽象基类来表示通用“future”概念的提议,就像Python集合有它们的ABCs一样。


3
经过进一步搜索,我找到了这个帖子 - http://mail.python.org/pipermail/python-ideas/2010-September/008054.html - 在那里Guido确认了 concurrent.futures.Future 是正确的选择。 - Pavel Minaev
concurrent.futures已经从3.2回溯到2.6+ https://code.google.com/p/pythonfutures/ - ddotsenko

1

阅读各种“服务器”库以获取提示。

一个很好的例子是BaseHTTPServer

具体来说,HTTPServer类定义显示了如何提供“处理程序类”。

每个请求都会实例化处理程序类的一个实例。然后该对象处理请求。

如果您想使用“回调”编写“异步I/O”,则应向您的读取器提供ReadHandler类。

class AsyncReadHandler( object ):
    def input( self, line, server ):
        print( line )

read_line_async( AsyncReadHandler )

这样的东西会遵循一些已经建立的设计模式。


其他标准的异步库是否遵循相同的模式(即,您是否可以轻松地以通用方式组合它们的 Promise 对象)?内联续体呢,例如 lambda 表达式或本地函数?要求客户端定义一个类来提供单个回调似乎有些过度设计。 - Pavel Minaev
1
@Pavel Minaev:这并没有帮助。已经建立的模式仍然是一个类。你还想要什么?不同的“已建立”模式吗?代码就是代码,库就是库。如果你的评论是反对已建立的模式,我很抱歉。它仍然是已建立的模式。如果你想要一个不同的已建立的模式,请删除你问题中的整个“已建立的模式”部分,只关注维基百科文章,不要涉及其他内容。 - S.Lott
你已经展示了(例如服务器)响应处理程序的既定模式。我不是在寻找响应处理程序的既定模式。事实上,即使Python设计者也认为这两个东西是不同的,这就是为什么http://www.python.org/dev/peps/pep-3148/现在存在的原因。看一下它,你就会明白我的意思。 - Pavel Minaev
1
我已经编辑了问题,表明我正在寻找Python中“期货的已建立模式”。有各种形式的异步处理,它们不能都合理地被单一模式覆盖。你展示的是一种模式,它涵盖了注册一个单一的“处理事件”处理程序来处理多个传入请求的情况。我要找的是一种情况,即注册一个单一的“操作完成”(又称续集)处理程序,在相关操作完成时调用一次。这两者完全不同。 - Pavel Minaev
1
此外,你断言这是 Python 中此类事情的模式,但缺乏实质性证据。你引用了 BaseHTTPServer 类,但它在问题的上下文中的相关性是可疑的。我在回应中引用了 PEP-3148(请注意,这是 Python 3.2 的一部分),它展示了完全不同的东西。仅凭后者就足以证明你所推崇的模式并没有被“确立”,即使假设它在这里适用。所以,抱歉,我不能接受这个作为答案——它并没有帮助——也不能接受问题是“有争议的”的说法。 - Pavel Minaev
显示剩余4条评论

0

你看过装饰器了吗?

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)

1
重申一下:我目前不关注实现技术。我想要的是API设计反馈 - 简单来说,最好的、最符合“Pythonic”的方法是什么,用于提供一个继续回调(以及可能的其他相关信息,如阻塞结果和取消请求对象)。 - Pavel Minaev
那么,是的,装饰器正是您要找的东西。它们是目前将回调功能嫁接到现有函数的最优雅的方法。 - user873728
我不是想将回调功能添加到现有函数中。我正在编写自己的函数,这些函数已经天生异步(即我正在以某种比仅在后台线程上旋转同步操作并等待其完成更少浪费的方式处理异步操作),我想知道如何正确地将其暴露给我的API的客户端。 - Pavel Minaev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接