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之不要红头文件(2)
Sep 28 Python
python中while循环语句用法简单实例
May 07 Python
Python numpy 常用函数总结
Dec 07 Python
Python基于辗转相除法求解最大公约数的方法示例
Apr 04 Python
深入了解和应用Python 装饰器 @decorator
Apr 02 Python
在Pycharm中调试Django项目程序的操作方法
Jul 17 Python
Python程序暂停的正常处理方法
Nov 07 Python
python实现FTP文件传输的方法(服务器端和客户端)
Mar 20 Python
Python GUI编程学习笔记之tkinter界面布局显示详解
Mar 30 Python
python+adb命令实现自动刷视频脚本案例
Apr 23 Python
python新手学习使用库
Jun 11 Python
django 实现后台从富文本提取纯文本
Jul 02 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读取html并截取字符串的简单代码
2009/11/30 PHP
php函数array_merge用法一例(合并同类数组)
2013/02/03 PHP
ThinkPHP中RBAC类的四种用法分析
2014/11/24 PHP
从sohu弄下来的flash中展示图片的代码
2007/04/27 Javascript
javascript 隔行换色函数代码
2010/10/24 Javascript
通过正则格式化url查询字符串实现代码
2012/12/28 Javascript
jquery 文本上下无缝滚动,鼠标放上去就停止 小例子
2013/06/05 Javascript
JavaScript字符串对象charAt方法入门实例(用于取得指定位置的字符)
2014/10/17 Javascript
在linux中使用包管理器安装node.js
2015/03/13 Javascript
require.js的用法详解
2015/10/20 Javascript
总结jQuery插件开发中的一些要点
2016/05/16 Javascript
BootStrap 附加导航组件
2016/07/22 Javascript
Angular Module声明和获取重载实例代码
2016/09/14 Javascript
JavaScript实现提交模式窗口后刷新父窗口数据的方法
2017/06/16 Javascript
基于jQuery实现手风琴菜单、层级菜单、置顶菜单、无缝滚动效果
2017/07/20 jQuery
Js面试算法详解
2018/04/08 Javascript
JavaScript数组,JSON对象实现动态添加、修改、删除功能示例
2018/05/26 Javascript
通过js示例讲解时间复杂度与空间复杂度
2019/08/06 Javascript
jQuery Datatables 动态列+跨列合并实现代码
2020/01/30 jQuery
vue实现列表拖拽排序的功能
2020/11/02 Javascript
Django 跨域请求处理的示例代码
2018/05/02 Python
python验证身份证信息实例代码
2019/05/06 Python
Python使用sklearn库实现的各种分类算法简单应用小结
2019/07/04 Python
python3 selenium自动化 下拉框定位的例子
2019/08/23 Python
Python 实现数组相减示例
2019/12/27 Python
python3实现将json对象存入Redis以及数据的导入导出
2020/07/16 Python
优秀应届生推荐信
2013/11/09 职场文书
群众路线教育实践活动整改落实情况汇报
2014/10/28 职场文书
2014年人事科工作总结
2014/11/19 职场文书
送达通知书
2015/04/25 职场文书
研讨会通知
2015/04/27 职场文书
幼儿园班级工作总结2015
2015/05/25 职场文书
代理词怎么写
2015/05/25 职场文书
老舍《猫》教学反思
2016/02/17 职场文书
MySQL令人大跌眼镜的隐式转换
2021/08/23 MySQL
「玫瑰之王的葬礼」舞台剧主视觉图公开
2022/03/21 日漫