Inside Flask - signal 信号机制

singal 在平常的 flask web 开发过程中较少接触到,但对于使用 flask 进行框架级别的开发时,则必须了解相关的工作机制。flask 通过 singal 机制,通知上层代码当前 flask 正在进行的处理动作,以便上层代码在 flask 进行处理的前后进行相关的处理(类似于 java 中通过 AOP 拦截操作,在 before action 和 after action 中进行一些处理动作)。

singal 一般只用于通知目的,不应该修改内部数据。它的底层通过 blinker 库实现,如果没有安装这个库(需要额外通过 pip 安装,flask 默认的依赖中不包含 blinker ),那么信号机制将不起作用,此段处理代码为 ::

signals_available = False
try:
from blinker import Namespace
signals_available = True
except ImportError:
class Namespace(object):
def signal(self, name, doc=None):
return _FakeSignal(name, doc)

在 import blinker 库失败时,用内部的 _FakeSignal 类取代,它只有一个进行警告的作用,没有实际的处理 ::

class _FakeSignal(object):
...
def __init__(self, name, doc=None):
self.name = name
self.__doc__ = doc
def _fail(self, *args, **kwargs):
raise RuntimeError('signalling support is unavailable '
'because the blinker library is '
'not installed.')
send = lambda *a, **kw: None
connect = disconnect = has_receivers_for = receivers_for = \
temporarily_connected_to = connected_to = _fail
del _fail

此时 signal 的 connect 、 disconnect 、 has_receives_forreceviers_fortemporarily_connected_toconnected_to 等方法,都被替换为 _fail

除了 flask 自身的一些信号,其它插件也可以提供一些信号,像 flask-login 的信号机制

现在深入看看 flask 的信号机制和它所依赖的 blinker 库的信号机制实现。

blinker 库通过信号的方式,解除组件之间的耦合。blinker 非常小(只包括 3 个源文件),但提供了一个面向对象的消息机制,具体可见blinker 文档

blinker 的主要内容在 base.py 文件。该文件包含了 Signal 、 NamedSignal 、 Namespace 、 WeekNamespace 等几个主要的类。

Signal 类是最关键的类,表示一个特定的信号,提供了对信号的基本操作方法: connect 、 disconnect 、 send 等。它包含3个主要的概念: sender 、 receiver 、 signal 。sender 是指信号的发送者对象,receiver 是信号的接收者(一个 callable 的对象),receiver 通过 hash 方法计算一个 id 作为其在 Signal 中的 key,signal 则是当前的信号对象。它的初始化过程如下 ::

def __init__(self, doc=None):
...
if doc:
self.__doc__ = doc
...
self.receivers = {}
self._by_receiver = defaultdict(set)
self._by_sender = defaultdict(set)
self._weak_senders = {}

receivers 是一个 receiver 的 id 和引用(原始对象引用或弱引用 weakref)的字典。_by_receiver_by_sender 用于辅助查找,一个是通过 receiver id 查找对应的 sender id 集合,一个是通过 sender id 查找对应的 receiver id 集合。

订阅信号时,使用 connect 方法(或 connect_via 装饰器),处理过程如下 ::

def connect(self, receiver, sender=ANY, weak=True):
...
receiver_id = hashable_identity(receiver)
if weak:
receiver_ref = reference(receiver, self._cleanup_receiver)
receiver_ref.receiver_id = receiver_id
else:
receiver_ref = receiver
if sender is ANY:
sender_id = ANY_ID
else:
sender_id = hashable_identity(sender) self.receivers.setdefault(receiver_id, receiver_ref)
self._by_sender[sender_id].add(receiver_id)
self._by_receiver[receiver_id].add(sender_id)
...

首先,通过 hash 计算一个 receiver 的 id 作为 key ,并计算 sender 的 id 。然后,将 receiver id 和其引用保存到 receivers 字典,sender 与 receiver 的对应关系分别保存到 _by_sender_by_receiver 中,供后续查找时使用。添加成功时,会广播此次的 connection (每个 signal 有一个 receiver_connected 信号 ,全局还有一个) ::

if ('receiver_connected' in self.__dict__ and
self.receiver_connected.receivers):
try:
self.receiver_connected.send(self,
receiver=receiver,
sender=sender,
weak=weak)
except:
self.disconnect(receiver, sender)
raise
if receiver_connected.receivers and self is not receiver_connected:
try:
receiver_connected.send(self,
receiver_arg=receiver,
sender_arg=sender,
weak_arg=weak)
except:
self.disconnect(receiver, sender)
raise

成功使用 connect 订阅信号后,可以进行信号的发送,代码如下 ::

def send(self, *sender, **kwargs):
...
# Using '*sender' rather than 'sender=None' allows 'sender' to be
# used as a keyword argument- i.e. it's an invisible name in the
# function signature.
if len(sender) == 0:
sender = None
elif len(sender) > 1:
raise TypeError('send() accepts only one positional argument, '
'%s given' % len(sender))
else:
sender = sender[0]
if not self.receivers:
return []
else:
return [(receiver, receiver(sender, **kwargs))
for receiver in self.receivers_for(sender)]

在 send 的时候,Signal 查找 sender 对应的 receiver 列表,然后逐个调用。receivers_for 函数查找 sender 对应的 receiver 列表。

最后,如果要取消订阅,就用 disconnect 。

NamedSignal 是在 Signal 的基础上,加上一个 name 变量,作为命名的信号。

Namespace 是一个管理信号的字典,它提供 signal 工厂方法,并自动创建相应名字的 NamedSignal ,如下 ::

def signal(self, name, doc=None):
...
try:
return self[name]
except KeyError:
return self.setdefault(name, NamedSignal(name, doc))

WeekNamespace 中是 Namespace 的弱引用改进版本,继承自 WeakValueDictionary 。

在 flask 中,flask 定义了自己的 Namespace 用于隔离,然后建立一系列内置的信号。在 flask/signals.py 中 ::

_signals = Namespace()
...
template_rendered = _signals.signal('template-rendered')
before_render_template = _signals.signal('before-render-template')
request_started = _signals.signal('request-started')
request_finished = _signals.signal('request-finished')
request_tearing_down = _signals.signal('request-tearing-down')
got_request_exception = _signals.signal('got-request-exception')
appcontext_tearing_down = _signals.signal('appcontext-tearing-down')
appcontext_pushed = _signals.signal('appcontext-pushed')
appcontext_popped = _signals.signal('appcontext-popped')
message_flashed = _signals.signal('message-flashed')

每个信号的具体使用见 http://docs.jinkan.org/docs/flask/signals.html

Inside Flask - signal 信号机制的更多相关文章

  1. linux下 signal信号机制的透彻分析与各种实例讲解

    转自:http://blog.sina.com.cn/s/blog_636a55070101vs2d.html 转自:http://blog.csdn.net/tiany524/article/det ...

  2. Django signal 信号机制的使用

    Django中提供了"信号调度",用于在框架执行操作时解耦,当某些动作发生的时候,系统会根据信号定义的函数执行相应的操作 一.Django中内置的 signal 类型主要包含以下几 ...

  3. Inside Flask - flask.__init__.py 和核心组件

    Inside Flask - flask.__init__.py 和核心组件 简单的示例 首先看看一个简单的示例.使用 Flask ,通常是从 flask 模块导入 Flask . request 等 ...

  4. python使用信号机制实例:

    python使用信号机制实例: 程序会一直等待,直到其他程序发送CTRL-C信号给本进程.需要其他程序配合测试. 或者打开新的终端使用kill -sig PID 向一个进程发送信号,来测试. from ...

  5. Flask笔记:信号机制

    Flask中有内置的一些信号,也可以通过三方库blinker自定义信号,其实Flask内置的信号也是优先使用的blinker库,如果没有安装blinker才会使用自定义的信号机制.可以通过点击任意导入 ...

  6. Linux 信号signal处理机制

    信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念.Linux对信号机制的大致实现方法.如何使用信号,以及有关信号的几个系统调用. 信号机制是进程之间相互传递消息的一种方法,信号全 ...

  7. Linux信号signal处理机制

    信号机制是进程之间相互传递消息的一种方法,信号全称为软中断信号,也有人称作软中断.从它的命名可以看出,它的实质和使用很象中断.所以,信号可以说是进程控制的一部分.         一.信号的基本概念 ...

  8. xenomai内核解析之信号signal(一)---Linux信号机制

    版权声明:本文为本文为博主原创文章,转载请注明出处.如有错误,欢迎指正.博客地址:https://www.cnblogs.com/wsg1100/ 目录 1. Linux信号 1.1注册信号处理函数 ...

  9. C语言编程技巧-signal(信号)[转]

    自 http://www.uml.org.cn/c++/200812083.asp 信号是Linux编程中非常重要的部分,本文将详细介绍信号机制的基本概念.Linux对信号机制的大致实现方法.如何使用 ...

随机推荐

  1. [数据库]关于MAX()函数的一个坑

    之前写了这么一条sql语句,目的是取出表中itemid最大的那一条数据. SELECT date, MAX(itemid) AS itemid, group FROM mytable GROUP BY ...

  2. POJ 2407 (欧拉函数)

    题目链接: http://poj.org/problem?id=2407 题目大意:求小于n且与n互质的正整数个数. 解题思路: 欧拉函数=小于n且与n互质的正整数个数. 公式=n*(1-1/P1)* ...

  3. 转载:CSS3 Flexbox可视化指南

    0. 目录 目录 引言 正文 1 引入 2 基础 3 使用 4 弹性容器Flex container属性 41 flex-direction 42 flex-wrap 43 flex-flow 44 ...

  4. 常用JS正则表达式

    常用JS正则表达式 收集一些常用的JavaScript正则表达式匹配规则,比如匹配电话号码.Email.中文字符.身份证号.邮编.QQ号.过滤空白行.匹配特定数字等.觉得这玩意是很有用的,只不过自己水 ...

  5. 纯CSS 实现tooltip 内容提示信息效果

    Tooltip 也就是内容的提示信息,合理使用可以给用户比较好的体验. 实现方法有很多种,有很多JS 插件,我这里介绍的是纯CSS实现的方法,兼容性也比较靠谱,IE8+均可正常显示.实现方法也非常简单 ...

  6. RecyclerView android:layout_width="match_parent" 无效

    使用RecyclerView 时,在xml文件中设置宽度match_parent无效. View view = mInflater.from(mContext).inflate(R.layout.it ...

  7. NOIP 2005 等价表达式 (TYVJ P1060)

    做题记录: 2016-08-10 23:35:09 背景 NOIP2005 提高组 第四道 描述 明明进了中学之后,学到了代数表达式.有一天,他碰到一个很麻烦的选择题.这个题目的题干中首先给出了一个代 ...

  8. 移动端页面0.5px border的实现

    移动端上经常发现1px边框异常的粗,因此,决定用伪类配合css3来实现0.5px边框 代码如下: <!doctype html> <html lang="en"& ...

  9. BZOJ3835: [Poi2014]Supercomputer

    Description Byteasar has designed a supercomputer of novel architecture. It may comprise of many (id ...

  10. java中instanceof和getClass()的区别分析

    class A { } class B extends A { } Object o1 = new A();  Object o2 = new B(); o1 instanceof A => t ...