信号 — Flask 文档

来自菜鸟教程
Flask/docs/2.0.x/signals
跳转至:导航、​搜索

信号

0.6 版中的新功能。


从 Flask 0.6 开始,Flask 集成了对信令的支持。 这种支持由优秀的 blinker 库提供,如果它不可用,它将优雅地回退。

什么是信号? 信号通过在核心框架或其他 Flask 扩展中的其他地方发生动作时发送通知来帮助您解耦应用程序。 简而言之,信号允许某些发送者通知订阅者发生了某些事情。

Flask 带有几个信号,其他扩展可能会提供更多。 另请记住,信号旨在通知订阅者,不应鼓励订阅者修改数据。 你会注意到有些信号看起来和一些内置装饰器做同样的事情(例如:request_startedbefore_request() 非常相似)。 但是,它们的工作方式存在差异。 例如,核心 before_request() 处理程序以特定顺序执行,并且能够通过返回响应提前中止请求。 相反,所有信号处理程序都以未定义的顺序执行并且不修改任何数据。

信号相对于处理程序的一大优势是您可以安全地订阅它们,只需一秒钟。 例如,这些临时订阅有助于单元测试。 假设您想知道作为请求的一部分呈现了哪些模板:信号允许您做到这一点。

订阅信号

要订阅信号,您可以使用信号的 connect() 方法。 第一个参数是发出信号时应调用的函数,可选的第二个参数指定发送者。 要取消订阅信号,您可以使用 disconnect() 方法。

对于所有核心 Flask 信号,发送者是发出信号的应用程序。 当你订阅一个信号时,一定要同时提供一个发送者,除非你真的想收听所有应用程序的信号。 如果您正在开发扩展程序,则尤其如此。

例如,这是一个辅助上下文管理器,可用于单元测试以确定呈现哪些模板以及将哪些变量传递给模板:

from flask import template_rendered
from contextlib import contextmanager

@contextmanager
def captured_templates(app):
    recorded = []
    def record(sender, template, context, **extra):
        recorded.append((template, context))
    template_rendered.connect(record, app)
    try:
        yield recorded
    finally:
        template_rendered.disconnect(record, app)

这现在可以很容易地与测试客户端配对:

with captured_templates(app) as templates:
    rv = app.test_client().get('/')
    assert rv.status_code == 200
    assert len(templates) == 1
    template, context = templates[0]
    assert template.name == 'index.html'
    assert len(context['items']) == 10

确保使用额外的 **extra 参数进行订阅,以便在 Flask 向信号引入新参数时调用不会失败。

with块主体中应用程序app发布的代码中的所有模板渲染现在都将记录在templates变量中。 每当呈现模板时,模板对象和上下文都会附加到它上面。

此外,还有一个方便的辅助方法 (connected_to()),它允许您临时将函数订阅到带有上下文管理器的信号。 因为不能以这种方式指定上下文管理器的返回值,所以您必须将列表作为参数传入:

from flask import template_rendered

def captured_templates(app, recorded, **extra):
    def record(sender, template, context):
        recorded.append((template, context))
    return template_rendered.connected_to(record, app)

上面的例子看起来像这样:

templates = []
with captured_templates(app, templates, **extra):
    ...
    template, context = templates[0]

闪烁 API 更改

connected_to() 方法到达 Blinker 1.1 版。


创建信号

如果想在自己的应用中使用信号,可以直接使用blinker库。 最常见的用例是自定义 Namespace.. 这是大多数时候推荐的:

from blinker import Namespace
my_signals = Namespace()

现在您可以创建这样的新信号:

model_saved = my_signals.signal('model-saved')

此处信号的名称使其独一无二,也简化了调试。 您可以使用 name 属性访问信号的名称。

对于扩展开发人员

如果你正在编写一个 Flask 扩展并且你想优雅地降低缺少的闪光灯安装,你可以通过使用 flask.signals.Namespace 类来做到这一点。


发送信号

如果要发出信号,可以通过调用 send() 方法来实现。 它接受一个发送者作为第一个参数和可选的一些转发给信号订阅者的关键字参数:

class Model(object):
    ...

    def save(self):
        model_saved.send(self)

尽量选择一个好的发件人。 如果您有一个发出信号的类,请将 self 作为发送方传递。 如果您从随机函数发出信号,则可以将 current_app._get_current_object() 作为发送方传递。

将代理作为发件人传递

永远不要将 current_app 作为信号的发送者传递。 请改用 current_app._get_current_object()。 这样做的原因是 current_app 是一个代理而不是真正的应用程序对象。


信号和 Flask 的请求上下文

信号完全支持接收信号时的请求上下文。 上下文局部变量在 request_startedrequest_finished 之间始终可用,因此您可以根据需要依赖 flask.g 和其他变量。 请注意 发送信号request_tearing_down 信号中描述的限制。


基于装饰器的信号订阅

使用 Blinker 1.1,您还可以使用新的 connect_via() 装饰器轻松订阅信号:

from flask import template_rendered

@template_rendered.connect_via(app)
def when_template_rendered(sender, template, context, **extra):
    print(f'Template {template.name} is rendered with {context}')

核心信号

查看 Signals 以获取所有内置信号的列表。