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. [转]linux下svn命令大全

    FROM:http://www.jb51.net/os/RedHat/2461.html 1.将文件checkout到本地目录 svn checkout path(path是服务器上的目录) 例如:s ...

  2. [ JS 进阶 ] test, exec, match, replace

    https://segmentfault.com/a/1190000003497780 对了,这篇文章可能会涉及到正则表达式相关知识,所以推荐没有正则基础的去看看这篇入门文章:正则表达式30分钟入门教 ...

  3. 读书笔记:javascript高级技巧(一)

    一.安全的类型检测 javascript内置的类型检测机制并非完全可靠,由于浏览器或者作用域等原因,经常会发生错误.大家知道,在任何值调用toString()方法都会返回一个[object Nativ ...

  4. Javascript 多浏览器兼容性问题及解决方案

    一.document.formName.item(”itemName”) 问题 问题说明:IE下,可以使用 document.formName.item(”itemName”) 或 document. ...

  5. Codeforces Round #207 (Div. 1) A. Knight Tournament(STL)

    脑子又卡了...来一发set的,STL真心不熟. #include <stdio.h> #include <string.h> #include <iostream> ...

  6. osg中的视点控制

    osg中的视点控制 osg的视点控制基类是CameraManipulator, 它是一个虚基类, 有用的方法都跟home有关. 在这个类里面有三个重要的成员变量: osg::Vec3d _homeEy ...

  7. Windows Phone Data Protection

    To encrypt the PIN // Convert the PIN to a byte[]. byte[] PinByte = Encoding.UTF8.GetBytes(TBPin.Tex ...

  8. HighchartsJS创建环形带标识的图表实例

    HighchartsJS 是一款功能强大.开源.美观.图表丰富.兼容绝大多数浏览器的纯js图表库,目前最新版本库是Highcharts 4.1.9.另外,HighchartsJS还提供很多的插件和第三 ...

  9. Hadoop_初识

    一. 什么是Hadoop Hadoop是一个由Apache基金会所开发的分布式系统基础架构. 用户可以在不了解分布式底层细节的情况下,开发分布式程序. 充分利用集群的威力进行高速运算和存储. Hado ...

  10. Js文字特效—文字段逐个变色循环

    自己用来练习的,附上详细注释,如果有和我一样喜欢并想要学习Dom特效创作的朋友,推荐先系统了解Javascript中Html Dom Object部分的内容,包括常用方法及属性. <!DOCTY ...