阅读目录

楔子

  我在之前的文章《flask源码解析之上下文》中对flask上下文流程进行了详细的说明,但是在学习的过程中我一直在思考flask上下文中为什么要使用栈完成对请求上下文和应用上下文的入栈和出栈操作,而且栈所维护的无非不就是一个列表,我直接用一个列表去存储请求上下文和应用上下文不可以吗?或者说我用一个变量、字典其他任何可存储数据的数据类型不行吗?对于这个问题的解答,是我在理解离线脚本和 flask多app应用中才理解flask上下文中使用栈的精髓。对于为什么使用栈进行存储上下文,请耐心看我之前对离线脚本和flask多app应用的铺垫。

离线脚本

  在项目的实际应用中,我们需要使用离线脚本完成不能作为后台功能的操作,例如:

    1. 每天凌晨对还款用户进行短信提醒

    2. 每天凌晨对数据库进行特定操作

    3. 在项目开发完成交付他人进行测试之前,使用一个脚本完成对数据库的初始化操作

  对于以上的所要实现的功能,他们都不能作为后台代码开发的一部分,只能使用一个离线的脚本完成上述操作。我们以需求2为例编写一个离线脚本:

点我下载demo项目

from sansa import db
from sansa.models import Users
db.session.add(Users(name="大萨达所"))
db.session.commit()
在项目未启动情况下,运行此脚本抛出以下错误:
"D:\Program Files\Python36\python.exe" D:/Demo/s8/demo/sansa/数据插入的离线脚本.py
Traceback (most recent call last):
File "D:\Program Files\Python36\lib\site-packages\sqlalchemy\util\_collections.py", line 999, in __call__
return self.registry[key]
KeyError: <greenlet.greenlet object at 0x0000000003723930> During handling of the above exception, another exception occurred: Traceback (most recent call last):
File "D:/Demo/s8/demo/sansa/数据插入的离线脚本.py", line 3, in <module>
db.session.add(Users(name="大萨达所"))
File "D:\Program Files\Python36\lib\site-packages\sqlalchemy\orm\scoping.py", line 158, in do
return getattr(self.registry(), name)(*args, **kwargs)
File "D:\Program Files\Python36\lib\site-packages\sqlalchemy\util\_collections.py", line 1001, in __call__
return self.registry.setdefault(key, self.createfunc())
File "D:\Program Files\Python36\lib\site-packages\sqlalchemy\orm\session.py", line 2950, in __call__
return self.class_(**local_kw)
File "D:\Program Files\Python36\lib\site-packages\flask_sqlalchemy\__init__.py", line 141, in __init__
self.app = app = db.get_app()
File "D:\Program Files\Python36\lib\site-packages\flask_sqlalchemy\__init__.py", line 912, in get_app
'No application found. Either work inside a view function or push'
RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/. Process finished with exit code 1

提示错误的原因是没有找到应用上下文对象的入栈操作,出现此问题的原因是:我们只是导入了db,但是项目原项目没有启动,更没有请求到来一说,我们执行的此离线脚本从头到尾就没有出现应用上下文,那么就更不会有应用上下文的入栈和出栈操作,所以db找到不到应用上下文,找不到应用上下文就无法导入连接数据库的配置信息。现在既然我们知道了出现问题的原因,解决此问题的方法就是我们手动执行应用上下文的创建、入栈和出栈操作,即要执行以下步骤:

1. app_ctx = self.app.app_context()

2.  app_ctx.push()

3.  app_ctx.pop(exc)

因此,编写的离线脚本为:

from sansa import db,create_app
from sansa.models import Users # 获取到生成app
app = create_app().app
# 创建app_ctx
app_ctx = app.app_context()
# app_ctx 入栈
app_ctx.push()
# 对数据库进行操作
db.session.add(Users(name="大萨达所"))
# 提交
db.session.commit()
app_ctx.pop()

由于 app_ctx 有  __enter__ 和  __exit__ 方法:

 def __enter__(self):
self.push()
return self
    def __exit__(self, exc_type, exc_value, tb):
self.pop(exc_value) if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)

根据以上方法,离线脚本还可以这样写:

from sansa import db,create_app
from sansa.models import Users
app = create_app().app with app.app_context():
# 对数据库进行操作
db.session.add(Users(name="大萨达所"))
# 提交
db.session.commit()
# 释放当前的连接
db.session.remove()

这样,此脚本可实现在项目离线的情况下,按照指定的时间点执行该离线脚本完成对数据库的操作。

flask的多app应用

  博主有在《flask源码解析之DispatcherMiddleware》一文中对flask多app应用的使用和源码流程进行过详细阐述,flask多app所实现的功能与蓝图相同,就是完成路由分发的功能。例如:

from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
from flask import Flask, current_app,request app1 = Flask('app01') app2 = Flask('app02') @app1.route('/index/')
def index():
return "app01" @app2.route('/index2/')
def index2():
"""
存在的问题:
def __call__(self, environ, start_response):
# 获取当前请求的URL,script == '/index/'
script = environ.get('PATH_INFO', '')
path_info = ''
# '/' 在 '/index/' 中
while '/' in script:
# self.mounts == {'/sec': app2,}, '/' 不在 self.mounts 中
if script in self.mounts:
app = self.mounts[script]
break
script, last_item = script.rsplit('/', 1)
path_info = '/%s%s' % (last_item, path_info)
else:
app = self.mounts.get(script, self.app)
original_script_name = environ.get('SCRIPT_NAME', '')
environ['SCRIPT_NAME'] = original_script_name + script
environ['PATH_INFO'] = path_info # 这里得到的知识不包含前缀的url,那这样不是就丢失了吗
return app(environ, start_response)
:return:
"""
print(request.full_path) # 得到的是 /index2/? 且没有request.path_info
return "app2" # http://www.oldboyedu.com/index
# http://www.oldboyedu.com/sec/index2
dm = DispatcherMiddleware(app1, {
'/sec': app2,
}) if __name__ == "__main__":
run_simple('localhost', 5000, dm)

注意事项:

  在DispatcherMiddleware类的实例参数中,第一个参数不能是一个字典,即第一个参数只能是一个app实例,即没有前缀的。但是后面的APP实例都可以是有前缀的app,存放至字典中

源码分析

请求每次进来执行DispatcherMiddleware的__call__方法,实现分发的原理是:获取本次请求的url,对url进行分割,直到分割出来的部分url(前缀)在保存了分发路由关系字典的self.mounts中时,分割结束,根据前缀找到要执行的函数,并做后续的操作。

为什么用栈

  在 执行离线脚本 + 多app应用下,执行如下脚本程序:

from sansa import db,create_app
from sansa.models import Users
app1 = create_app1().app
app2 = create_app2().app with app1.app_context():
# 对数据库进行操作
db.session.add(Users(name="大萨达所"))
# 提交
db.session.commit()
# 释放当前的连接
db.session.remove()
with app2.app_context():
db.session.add(Users(name="xxx"))
# 提交
db.session.commit()
# 释放当前的连接
db.session.remove()

对于上述离线脚本程序,入栈顺序为: app1.app_context() --->  app2.app_context() ,由于执行当前脚本程序只开了一个线程,因此在两个应用上下文入栈的时候会存放至同一个线程id所维护的栈中;出栈的时候按照当前线程id去匹配此线程id所对维护的栈,每一个应用上下文在出栈的时候都会获取栈顶元素,即出栈顺序为: app2.app_context() --->  app1.app_context() ,完成了后进先出的栈特点,因此需要栈的数据结构。

https://www.cnblogs.com/liuyinzhou/p/9779518.html#_label0

strerror() 和perror()函数的更多相关文章

  1. strerror和perror函数详解

    /*#include <string.h> char *strerror(int errnum); 它返回errnum的值所对应的错误提示信息,例如errnum等于12的话,它就会返回&q ...

  2. Linux/Unix C编程之的perror函数,strerror函数,errno

    #include <stdio.h> // void perror(const char *msg); #include <string.h> // char *strerro ...

  3. 文件IO之——阻塞和非阻塞及perror函数

    读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回.从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用r ...

  4. c中perror函数

    写代码这么久,竟然很少用到perror函数,忘记了其强大的功能. 所在头文件: #include<stdio.h> 函数定义: void perror(const char *str); ...

  5. perror()函数的使用

    perror()函数的功能是打印一个系统错误信息.        perror()函数在Linux系统中属于库函数,在头文件中有如下定义: #include <stdio.h>       ...

  6. C/C++错误分析errno,perror,strerror和GetLastError()函数返回的错误代码的意义

    在C语言编译中,经常会出现一些系统的错误,这些错误如果在编译的时候不能很好的“预见”,会使系统“崩溃”,常见的捕获错误函数有: errno #include<errno.h> 这个变量是程 ...

  7. 【转载++】C/C++错误分析errno,perror,strerror和GetLastError()函数返回的错误代码的意义

    本文是上一篇“fopen返回0(空指针NULL)且GetLastError是0”的侧面回应.听赶来多么地正确和不容置疑,返回NULL时调用GetLastError来看看报错啊,但当时却返回了0,大家都 ...

  8. vim命令以及gcc编译器的常用cmd

    Gcc常用命令:         -c    仅对源文件进行编译,不链接生成可执行文件.常用于查错和只生成目标文件.     -o    经过gcc处理过后的结果保存在-o后面的文件中,可以是多种文件 ...

  9. 信号处理篇alarm ferror kill mkfifo pause pclose perror pipe popen sigaction sigaddset sigdelset sigemptyset signal sleep strerror

    alarm(设置信号传送闹钟) 相关函数 signal,sleep 表头文件 #include<unistd.h> 定义函数 unsigned int alarm(unsigned int ...

随机推荐

  1. zabbix 触发器描述列表count

    触发器描述列表: count: count(600): 最近10分钟的值的个数 count(600,12): 最近10分钟,值等于12的个数 count(600,12,"gt"): ...

  2. struts2的DevMode(开发模式)模式

    本文转自:http://blog.csdn.net/q1054261752/article/details/48687119 在实际应用开发或者是产品部署的时候,对应着两种模式: ① 开发模式(dev ...

  3. C 标准IO 库函数与Unbuffered IO函数

    先来看看C标准I/O库函数是如何用系统调用实现的. fopen(3) 调用open(2)打开指定的文件,返回一个文件描述符(就是一个int 类型的编号),分配一 个FILE 结构体, 通常里面包含了: ...

  4. hdu 4971/ 2014多校/最大权闭合图

    题意:n个项目(每一个相应获得一定价值).m个技术问题(每一个须要支出一定价值),每一个项目必须要攻克若干个技术问题.技术难题之间有拓扑关系. 关键是建图.一看,第一感觉就是最大权闭合图,马上建好了图 ...

  5. shell学习四十八天----文件校验和匹配

    文件校验和匹配 要是你怀疑可能有非常多文件具有同样的内文,而是用cmp或diff进行比較全部横队的比較,导致所花费的时间会随着文件数目增长成次方的增长. 这是能够使用file checksum(文件校 ...

  6. jmeter 发送http请求,并把获取到的请求的订单信息保存到文件中

    有一个任务,需要频繁发送订单请求,并分析订单请求中有没有存在重复订单号,思路是用jmeter 发送http请求,使用正则表达式获取到订单号,并把订单号和线程号作为参数提供给java请求,在java请求 ...

  7. python中杂乱问题

    1 字符串格式化中的格式指定 format_spec ::= [[fill]align][sign][#][][width][,][.precision][type] fill ::= <any ...

  8. linux内存管理之DMA

    说起DMA我们并不陌生,但是实际编程中去用的人不多吧,最多就是网卡驱动里的环形buffer,再有就是设备的dma,下面我们就分析分析.   DMA用来在设备内存和内存之间直接数据交互.而无需cpu干预 ...

  9. linux安装rzsz(lrzsz)

    lrzsz是一个unix通信套件提供的,X,Y和ZModem文件传输协议,可以用在Windows与linux系统之间的文件传输,体积小速度快,可以与xshell工具配合使用. (1)在线安装 yum ...

  10. C程序-进程内存结构分析

    1. 每个进程都运行在自己私有的内存空间中(即虚拟地址空间).在32位系统中,4GB的进程地址东健被分为用户空间和内核空间两个部分.用户空间占据着0~3GB(用16进制表示为0xC0000000),而 ...