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. mysql 连接超时解决

    修改my.cnf文件即可. ************************************ 在/etc/my.cnf下添加如下两行代码: wait_timeout=31536000inter ...

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

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

  3. 直接用Qt写soap

    直接用Qt写soap 最近的项目里用到了webservice, 同事用的是`gSoap`来搞的. 用这个本身没什么问题, 但这货生成的代码实非人类可读, 到处都是`__`和`_`, 看得我眼晕.... ...

  4. Crystal Reports 2008(水晶报表) JDBC连接mysql数据库

    在本blog中,主要介绍的是Crystal Reports 2008使用JDBC连接mysql数据库. 在连接之间,首先要确认你电脑上面都安装了mysql数据库. 其次,就是jdbc连接数据时候所使用 ...

  5. 500-internal server error 错误提示到配置文件的某行,并显示乱码

    UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,又称万国码.UTF-8用1到4个字节编码UNICODE字符.用在网页上可 ...

  6. LightOJ 1245 数学题,找规律

    1.LightOJ 1245   Harmonic Number (II)   数学题 2.总结:看了题解,很严谨,但又确实恶心的题 题意:求n/1+n/2+....+n/n,n<=2^31. ...

  7. 关于C#引用Dll后,找不到命名空间的问题

    在引用里明确添加了一个Dll,能够看到该Dll详细信息,可就是用using找不到命名空间.并且发现刚引用时是有该命名空间,一编译就消失了. 最后发现原因如下: 原目标框架为.Net Framework ...

  8. How does controller listen to service?

    Polling. The Controller periodically asks the Service for the latest data. IMHO, this option sucks, ...

  9. Missing number

    Missing number 题目: Description There is a permutation without two numbers in it, and now you know wh ...

  10. [LintCode] Minimum Size Subarray Sum 最小子数组和的大小

    Given an array of n positive integers and a positive integer s, find the minimal length of a subarra ...