Python Web框架Flask信号机制(signals)介绍


Posted in Python onJanuary 01, 2015

信号(signals)

Flask信号(signals, or event hooking)允许特定的发送端通知订阅者发生了什么(既然知道发生了什么,那我们可以知道接下来该做什么了)。

Flask提供了一些信号(核心信号)且其它的扩展提供更多的信号。信号是用于通知订阅者,而不应该鼓励订阅者修改数据。相关信号请查阅文档。

信号依赖于Blinker库。

钩子(hooks)

Flask钩子(通常出现在蓝图或应用程序现存的方法中,比如一些内置装饰器,例如before_request)不需要Blinker库并且允许你改变请求对象(request)或者响应对象(response)。这些改变了应用程序(或者蓝图)的行为。比如before_request()装饰器。

信号看起来和钩子做同样的事情。然而在工作方式上它们存在不同。譬如核心的before_request()处理程序以特定的顺序执行,并且可以在返回响应之前放弃请求。相比之下,所有的信号处理器是无序执行的,并且不修改任何数据。

一般来说,钩子用于改变行为(比如,身份验证或错误处理),而信号用于记录事件(比如记录日志)。

创建信号

因为信号依赖于Blinker库,请确保已经安装。

如果你要在自己的应用中使用信号,你可以直接使用Blinker库。最常见的使用情况是命名一个自定义的Namespace的信号。这也是大多数时候推荐的做法:

from blinker import Namespace

my_signals = Namespace()

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

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

这里使用唯一的信号名并且简化调试。可以用name属性来访问信号名。

对扩展开发者:

如果你正在编写一个Flask扩展,你想优雅地减少缺少Blinker安装的影响,你可以这样做使用flask.signals.Namespace类。

订阅信号

你可以使用信号的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]

发送信号

如果你想要发送信号,你可以通过调用 send() 方法来达到目的。它接受一个发件者作为第一个参数和一些可选的被转发到信号用户的关键字参数:

class Model(object):

    ...
    def save(self):

        model_saved.send(self)

永远尝试选择一个合适的发送者。如果你有一个发出信号的类,把 self 作为发送者。如果你从一个随机的函数发出信号,把current_app._get_current_object()作为发送者。

基于信号订阅的装饰器

在Blinker 1.1中通过使用新的connect_via()装饰器你也能够轻易地订阅信号:

from flask import template_rendered
@template_rendered.connect_via(app)

def when_template_rendered(sender, template, context, **extra):

    print 'Template %s is rendered with %s' % (template.name, context)

举例

模板渲染

template_rendered信号是Flask核心信号。

当一个模版成功地渲染,这个信号会被发送。这个信号与模板实例 template 和上下文的词典(名为 context )一起调用。

信号发送:

def _render(template, context, app):

    """Renders the template and fires the signal"""

    rv = template.render(context)

    template_rendered.send(app, template=template, context=context)

    return rv

订阅示例:
def log_template_renders(sender, template, context, **extra):

    sender.logger.debug('Rendering template "%s" with context %s',

                        template.name or 'string template',

                        context)
from flask import template_rendered

template_rendered.connect(log_template_renders, app)


帐号追踪

user_logged_in是Flask-User中定义的信号,当用户成功登入之后,这个信号会被发送。

发送信号:

# Send user_logged_in signal

user_logged_in.send(current_app._get_current_object(), user=user)

下面这个例子追踪登录次数和登录IP:
# This code has not been tested
from flask import request

from flask_user.signals import user_logged_in
@user_logged_in.connect_via(app)

def _track_logins(sender, user, **extra):

    user.login_count += 1

    user.last_login_ip = request.remote_addr

    db.session.add(user)

    db.session.commit()

小结

信号可以让你在一瞬间安全地订阅它们。例如,这些临时的订阅对测试很有帮助。使用信号时,不要让信号订阅者(接收者)发生异常,因为异常会造成程序中断。

Python 相关文章推荐
在Python操作时间和日期之asctime()方法的使用
May 22 Python
Python-嵌套列表list的全面解析
Jun 08 Python
Python模拟登陆淘宝并统计淘宝消费情况的代码实例分享
Jul 04 Python
彻彻底底地理解Python中的编码问题
Oct 15 Python
Python3 关于pycharm自动导入包快捷设置的方法
Jan 16 Python
flask应用部署到服务器的方法
Jul 12 Python
python中的反斜杠问题深入讲解
Aug 12 Python
python3.6中@property装饰器的使用方法示例
Aug 17 Python
wxpython绘制圆角窗体
Nov 18 Python
python将数组n等分的实例
Dec 02 Python
15款Python编辑器的优缺点,别再问我“选什么编辑器”啦
Oct 19 Python
如何基于Python爬虫爬取美团酒店信息
Nov 03 Python
Python模拟登录12306的方法
Dec 30 #Python
python执行shell获取硬件参数写入mysql的方法
Dec 29 #Python
简单的抓取淘宝图片的Python爬虫
Dec 25 #Python
简单使用Python自动生成文章
Dec 25 #Python
Python 抓取动态网页内容方案详解
Dec 25 #Python
利用Psyco提升Python运行速度
Dec 24 #Python
Python解决鸡兔同笼问题的方法
Dec 20 #Python
You might like
写出高质量的PHP程序
2012/02/04 PHP
PHP开发中常见的安全问题详解和解决方法(如Sql注入、CSRF、Xss、CC等)
2014/04/21 PHP
php读取文件内容的方法汇总
2015/01/24 PHP
详解Laravel视图间共享数据与视图Composer
2016/08/04 PHP
php基于单例模式封装mysql类完整实例
2016/10/18 PHP
Laravel框架中缓存的使用方法分析
2019/09/06 PHP
php上传后台无法收到数据解决方法
2019/10/28 PHP
ExtJS 2.2.1的grid控件在ie6中的显示问题
2009/05/04 Javascript
禁止页面刷新让F5快捷键及右键都无效
2014/01/22 Javascript
JS+CSS实现简易实用的滑动门菜单效果
2015/09/18 Javascript
浅谈json取值(对象和数组)
2016/06/24 Javascript
AngularJs 指令详解及示例代码
2016/09/01 Javascript
JS操作input标签属性checkbox全选的实现代码
2017/03/02 Javascript
jquery append与appendTo方法比较
2017/05/24 jQuery
理解 javascript 中的函数表达式与函数声明
2017/07/07 Javascript
vue实现动态按钮功能
2019/05/13 Javascript
js prototype和__proto__的关系是什么
2019/08/23 Javascript
Layui给switch添加响应事件的例子
2019/09/03 Javascript
详解vue页面首次加载缓慢原因及解决方案
2019/11/06 Javascript
JS array数组检测方式解析
2020/05/19 Javascript
[47:45]Liquid vs OG 2018国际邀请赛小组赛BO2 第二场 8.16
2018/08/17 DOTA
Python运行的17个时新手常见错误小结
2012/08/07 Python
Python实现3行代码解简单的一元一次方程
2014/08/18 Python
Python自动连接ssh的方法
2015/03/07 Python
Python实现的数据结构与算法之队列详解
2015/04/22 Python
解决Python下imread,imwrite不支持中文的问题
2018/12/05 Python
Python编程快速上手——选择性拷贝操作案例分析
2020/02/28 Python
使用SQLAlchemy操作数据库表过程解析
2020/06/10 Python
Python dict的常用方法示例代码
2020/06/23 Python
python-jwt用户认证食用教学的实现方法
2021/01/19 Python
食品厂厂长岗位职责
2014/01/30 职场文书
药品采购员岗位职责
2014/02/08 职场文书
小学英语课后反思
2014/04/26 职场文书
给校长的建议书200字
2014/05/16 职场文书
有限责任公司股东合作协议书范本
2014/10/30 职场文书
Win10 heic文件怎么打开 ? Win10 heic文件打开教程
2022/04/06 数码科技