1、第一个Flask程序
from flask import Flask
from flask import render_template,request,redirect
app = Flask(__name__) # 实例化Flask对象
@app.route('/test') # 将路径和函数的对应关系添加到路由中
def test():
return render_template('test.html')
if __name__ == '__main__':
app.run(debug=True) # 开启debug模式,监听用户的请求,有请求到来执行__call__方法(类加括号相当于执行__call__方法)
app.debug = True # 自动重启
2、模板的渲染和跳转
render_template('test.html') # 模板渲染,可以传递参数
render_template("test.html",**{"msg":"error"}) # 渲染模板参数加 "**"
redirect("http://www.baidu.com") # 跳转,直接指明跳转的路径
3、请求方式和起别名
@app.route('/login/',methods=["GET","POST"],endpoint="name")
# methods 定义视图函数可执行请求的方式
# endpoint 对路径进行取别名,和Django的 name属性别名一样,需要配合url_for使用。url_for(别名).
当需要传参数时直接写nid=1。
# 表示url上传递的参数,变量名称是id,并要求是int型。当然也可以是其他类型float、path。
同时需要在函数的加上该形参,但是不支持正则表达式
@app.route装饰器实际上在底层调用add_url_rule()方法
其他的参数:
app.add_url_rule("/register","n2",register,methods=["GET","POST"],
defaults={"nid":"6666"},strict_slashes=False,redirect_to="/pp")
# defaults 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
# strict_slashes 对URL最后的 / 符号是否严格要求
@app.route('/index',strict_slashes=False),访问 http://www.xx.com/index/ 或 http://www.xx.com/index均可.
@app.route('/index',strict_slashes=True)仅访问 http://www.xx.com/index.
# redirect_to 重定向到指定地址
# subdomain=None 子域名访问 如果subdomain=admin此时在访问就需要 admin/域名
如果想用正则匹配也是可以的,不过Flask提供的足够使用。
from werkzeug.routing import BaseConverter
class Regex(BaseConverter): # 继承这个类
def __init__(self, map, regex):
super().__init__(map)
self.regex = regex
def to_python(self, value): # 路由匹配成功时调用此函数
return int(value) # 在这里对匹配到的值进行加工
def to_url(self, value): # 反向路由匹配成功时调用此函数
return super().to_url(value)
app.url_map.converters["regex"] = Regex
@app.route("/tree/")
4、请求和响应
request.form # 获取请求体中所有的数据
request.form.get("") # 获取请求中的参数值
from flask import make_response
# 请求相关信息
request.method # 请求方式
request.args # 接收get请求的参数
request.form # 接收post请求的参数
request.values # 接收所有的参数
request.cookies # 请求的cookie
request.headers # 请求头
request.path # 请求的路径
request.full_path # 请求的全路径会带上get请求的参数
request.url # 请求的url (加上http的格式)
request.base_url # 请求的
request.url_root # 请求的根路径
request.host_url # 请求主机的url
request.host # 请求的主机
request.files # 接受文件
# 还可以对接收的文件直接进行保存
obj = request.files['the_file_name']
obj.save('/var/www/uploads/' + secure_filename(obj.filename))
# 响应相关信息
return "字符串" # 返回一个字符串
return render_template('html模板路径',**{}) # 渲染模板
return redirect('/index.html') # 跳转到其他路径
return jsonify({k:v}) # 返回json字符串
# 设置响应头
response = make_response(render_template('index.html'))
# response是flask.wrappers.Response类型
# response.delete_cookie('key') # 删除cookie
# response.set_cookie('key', 'value') 设置cookie
# response.headers['X-Something'] = 'A value' 设置请求头
return response
5、jinjia2一些语法上的不同
{% for k,item in user.items() %} # 前台页面进行遍历需要加括号
{{ item["name"] }} # 三种方法进行取值,这里和Django不同
{{ item.name }}
{{ item.get("name") }}
{% endfor %}
6、session
session["name"] = "123" # 设置session的值
session.get("name") # 获取session中的值
7、关于配置文件的写法
方式一: 直接在app.py中写,但是这样配置信息多了以后就不方便
app.debug = True
方式二: 通过写settings文件,在app.py中在引入, 但是这样不方便后面的调试
app.config.from_pyfile("settings.py")
方式三: 通过settings文件中写类的形式引入, 这样只需引入不同的类就可到达不同的调试环境
app.config.form_object(settings.developConfig)
---------------------------------------------------------
settings.py
class Config(object):
DEBUG = False
TESTING = False
class ProductionConfig(Config):
pass
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
---------------------------------------------------------
PS:settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录
8、路由系统
Flask的路由系统是由装饰器实现的,究其本质由add_url_rule 实现的。
def register():
return "注册"
app.add_url_rule("/register","N2",register,method=["GET","POST"]) # 这样也可以添加一个路由关系,装饰器的本质是调用add_url_rule方法
如果不传endpoint(别名),Flask内部会自动将endpoint命名为 view_func.__name__(函数名)
9、CBV模式
class Register(views.MethodView):
method = ["GET","POST"]
decorator = [auth,] # 在这里定义装饰器
def get(self):
return "注册"
def post(self):
return ""
app.add_url_rule("/register",view_func=Register.as_view(name="re")) # 这里的name就是别名。
# 和Django一样调用as_view方法
当然也可以继承其他类。
class Register(views.View): # 继承这个类需要继承dispatcher_request()方法,自己做分发
def dispatch_request(self, *args, **kwargs):
return "get"
app.add_url_rule("/register",view_func=Register.as_view(name="re"))
10、Flask不仅能传参数到前台页面,还可以传函数
前台页面:
{{ ff("123")|safe }} # 加上safe防止xss攻击
后台页面:
def proj(arg):
return ""
@app.route("/pp")
def pp():
return render_template("test.html",ff=proj)
在后台也可以做好防止xss攻击: 这里的Markup和Django的make_safe的作用一样
from flask import Masrkup
def proj(arg):
return Markup("")
更高级的还有宏定义:
{% macro ff(value)%} # 在这里定义一个函数
# 用来显示的HTML
{% endmacro %}
{{ ff("999") }} # 在这里调用
11、请求扩展
@app.before_request # 请求之前的动作,视图函数执行之前
def process_request():
print("进来了")
return None
# return "栏截" 当请求来不符合条件就会有返回值(进行拦截),返回值为none就代表放行
@app.route("/index")
def index():
print("index")
return render_template("test.html")
@app.after_request # 请求之后的动作,视图函数执行之后
def process_after(response):
print("走了")
return response # 必须返回response
当有多个before_request 和 after_request 方法时的执行流程
@app.before_request
def process_request1():
print("进来了1")
return None
@app.before_request
def process_request2():
print("进来了2")
return None
@app.route("/index")
def index():
print("index")
return render_template("test.html")
@app.after_request
def process_after1(response):
print("走了1")
return response
@app.after_request
def process_after2(response):
print("走了2")
return response
执行顺序:
>>> 进来了1
>>> 进来了2
>>> index
>>> 走了2
>>> 走了1
※:当有一个before_request方法有返回值时,后面的before_request 方法都不会执行 而 after_requets方法会全部执行,可以有多个函数。
@app.before_first_request # 有返回值也不会进行栏截
def first():
return "只在第一次请求前生效"
12、flash方法
app.secret_key = "23333" # 使用flash方法必须定义secret_key,因为这是基于session做的
@app.route("/get")
def get():
# get_flashed_messages() 从某个地方获取所有值,并清除
print(get_flashed_messages("wo")) # 当有参数时,按照分类去取
return "Get"
@app.route("/set")
def set():
# flash() 向某个地方设置值
flash("超市",category="wo") # category 分类的意思
return "Set"
# session基于用户隔离数据
13、错误页面的展示
@app.errorhandler(404) # 定制错误信息,需要传错误代码
def error(*arg): # 需要一个参数
print(arg)
return "404错误"
14、自定义标签和过滤器
@app.template_global() # 自定义模板
def sb():
return "傻逼韩宇乾"
@app.template_filter() # 自定义过滤器
def adds(a1,a2):
return a1+a2
调用方式:
{{ sb() }}
{{ 1|adds (2) }}
15、数据库连接池 (DBUtils 模块使用)
此连接池有两种连接模式:
import pymysql
from DBUtils.PersistentDB import PersistentDB
POOL = PersistentDB(
creator=pymysql, # 使用链接数据库的模块
maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制
setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
ping=0,
# ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
closeable=False,
# 如果为False时, conn.close() 实际上被忽略,供下次使用,再线程关闭时,才会自动关闭链接。如果为True时, conn.close()则关闭链接,那么再次调用pool.connection时就会报错,因为已经真的关闭了连接(pool.steady_connection()可以获取一个新的链接)
threadlocal=None, # 本线程独享值得对象,用于保存链接对象,如果链接对象被重置
host='127.0.0.1',
port=3306,
user='root',
password='',
database='',
charset='utf8'
)
def func():
conn = POOL.connection(shareable=False) # 是否可以共享
cursor = conn.cursor()
cursor.execute('select * from lab')
result = cursor.fetchall()
print(result)
cursor.close()
conn.close()
func()
import pymysql
from DBUtils.PooledDB import PooledDB, SharedDBConnection
POOL = PooledDB(
creator=pymysql, # 使用链接数据库的模块
maxconnections=6, # 连接池允许的最大连接数,0和None表示不限制连接数
mincached=2, # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
maxcached=5, # 链接池中最多闲置的链接,0和None不限制
maxshared=3, # 链接池中最多共享的链接数量,0和None表示全部共享。PS: 无用,因为pymysql和MySQLdb等模块的 threadsafety都为1,所有值无论设置为多少,_maxcached永远为0,所以永远是所有链接都共享。
blocking=True, # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
maxusage=None, # 一个链接最多被重复使用的次数,None表示无限制
setsession=[], # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
ping=0, # 一般用4或7,ping为0 表示并不保证连接一定可用
# ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
host='127.0.0.1',
port=3306,
user='root',
password='',
database='',
charset='utf8'
)
def func():
# 检测当前正在运行连接数的是否小于最大链接数,如果不小于则:等待或报raise TooManyConnections异常
# 否则
# 则优先去初始化时创建的链接中获取链接 SteadyDBConnection。
# 然后将SteadyDBConnection对象封装到PooledDedicatedDBConnection中并返回。
# 如果最开始创建的链接没有链接,则去创建一个SteadyDBConnection对象,再封装到PooledDedicatedDBConnection中并返回。
# 一旦关闭链接后,连接就返回到连接池让后续线程继续使用。
conn = POOL.connection()
# print(th, '链接被拿走了', conn1._con)
# print(th, '池子里目前有', pool._idle_cache, '\r\n')
cursor = conn.cursor()
cursor.execute('select * from lab')
result = cursor.fetchall()
print(result)
conn.close()
func()
实际使用的方式分为两种:
1、通过@staticment 装饰器:通过加静态属性装饰器
class DBhandler:
@staticmethod
def fetch_one(sql,args): # 动态传值
conn = POOL.connection()
cursor = conn.cursor()
cursor.execute(sql,args)
result = cursor.fetchall()
cursor.close()
conn.close()
return result
class DBhandler:
def __init__(self): # 初始化时就去创建连接
self.conn = POOL.connection()
self.cursor = self.conn.cursor()
def fetch_one(self, sql, args):
self.cursor.execute(sql, args)
result = self.cursor.fetchall()
return result
def close(self): # 关闭游标的方法
self.cursor.close()
self.conn.close()
16、请求上下文、应用上下文
1. 在这里进行了请求初始化 ctx 封装了请求相关的数据 此时就可以用ctx对象获取请求相关的内容
ctx = self.request_context(environ)
2. 请求进行ctx的上下文操作
ctx.push()
error = None
try:
try:
3. 找到视图函数并执行
response = self.full_dispatch_request()
except Exception as e:
error = e
4. 错误处理
response = self.handle_exception(e)
except:
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
5. 将请求清除
ctx.auto_pop(error)
在这里一共分为了5步,我们先来说第一步:
第一步的self.request_context(),初始化了一个类
def request_context(self, environ):
return RequestContext(self, environ) # 返回一个类
-------------------------------------------------------------------
class RequestContext(object):
def __init__(self, app, environ, request=None):
self.app = app
if request is None: # 第一次进来request必定为空
request = app.request_class(environ) # 在这里进行初始化了Request类
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
-------------------------------------------------------------------
request_class = Request
-------------------------------------------------------------------
class Request(RequestBase):
url_rule = None
view_args = None
routing_exception = None
_is_old_module = False
第二步:
def push(self):
top = _request_ctx_stack.top # 2.1 取请求上下文的栈顶元素
if top is not None and top.preserved:
top.pop(top._preserved_exc)
app_ctx = _app_ctx_stack.top # 2.2 取应用请求上下文的栈顶元素
if app_ctx is None or app_ctx.app != self.app:
app_ctx = self.app.app_context() # 2.3 初始化一个应用上下文,此时app_ctx是Appcontext对象。
app_ctx.push() # 2.4 将应用上下文的信息压栈
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_request_ctx_stack.push(self) # 2.5 将请求上下文的信息压栈
# 2.6 session相关的请求
self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
2.1 先进行初始化 _request_ctx_stack
_request_ctx_stack = LocalStack() # 初始化了LocalStack()类
------------------------------------------------------------------
class LocalStack(object):
def __init__(self):
self._local = Local() # 在LocalStack类中又初始化了Local()类
------------------------------------------------------------------
class Local(object):
# local()类用来保存所有数据
# 之所以以这种方式通过调用父类进行初始化,为了不触发类内部的__setattr___()方法
def __init__(self):
object.__setattr__(self, '__storage__', {}) # 在这里初始化了一个字典
object.__setattr__(self, '__ident_func__', get_ident) # 初始化了一个线程的唯一标识,用于区分各个请求
现在我们再回来,通过列表的 -1 的特性取栈顶元素
@property
def top(self):
try:
return self._local.stack[-1] # 取栈顶元素
except (AttributeError, IndexError):
return None
2.2 先进行初始化_app_ctx_stack
_app_ctx_stack = LocalStack() # 也初始化了一个LocalStack()类
-------------------------------------------------------------
class LocalStack(object):
def __init__(self):
self._local = Local() # 初始化了一个Local()类, Local类是实际存放数据的地方
---------------------------------------------------------------
class Local(object):
# 做了和上面一样的事情
def __init__(self):
object.__setattr__(self, '__storage__', {})
object.__setattr__(self, '__ident_func__', get_ident)
现在再回来,通过列表的 -1 的特性取栈顶元素
@property
def top(self):
try:
return self._local.stack[-1] # 取栈顶元素
except (AttributeError, IndexError):
return None
2.3 app.app_context()方法
def app_context(self):
return AppContext(self) # 初始化了一个AppContext类,self 是app
------------------------------------------------------------
class AppContext(object):
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class() # 这里相当于一个全局变量
self._refcnt = 0
※:先来说一下 self.g :相当于一个空字典,请求到来会创建g,请求走的时候会销毁g, 每个请求周期都会创建一个用于在请求周期中传递至的一个容器,在一个请求中进行传值时用g,而不用request。
2.4 app_ctx.push()
class AppContext(object):
def push(self):
self._refcnt += 1
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_app_ctx_stack.push(self) # 调用了LocalStack类的push()方法
appcontext_pushed.send(self.app) # 这是一个信号
-----------------------------------------------------------------
class LocalStack(object):
def push(self, obj):
rv = getattr(self._local, 'stack', None) # 第一次肯定取不到
if rv is None:
self._local.stack = rv = [] # 在这里进行进行赋值,就会触发Local()类的__setattr__()方法
rv.append(obj) # 将AppendContext加入
return rv
------------------------------------------------------------------
class Local(object):
def __setattr__(self, name, value):
ident = self.__ident_func__() # 获取唯一标识
storage = self.__storage__ # 获取存放的字典
try:
storage[ident][name] = value # 将ctx放进去 {唯一标识:{"stack":[app_ctx]}}
except KeyError:
storage[ident] = {name: value}
2.5 _request_ctx_stack.push(self) self是RequestContext
class LocalStack(object):
def push(self, obj):
rv = getattr(self._local, 'stack', None) # 这里同上
if rv is None:
self._local.stack = rv = [] # 同上
rv.append(obj) # RequestContext 对象
return rv
---------------------------------------------------------------
class Local(object):
def __setattr__(self, name, value):
ident = self.__ident_func__() # 获取唯一标识
storage = self.__storage__ # 获取存放数据的字典
try:
storage[ident][name] = value # 进行赋值 {唯一标识:{"stack":[ctx]}}
except KeyError:
storage[ident] = {name: value}
2.6 session相关
self.session = self.app.open_session(self.request) # 调用app的open_session
# self.session 是在ctx中,而ctx又在Local中
--------------------------------------------------
def open_session(self, request):
return self.session_interface.open_session(self, request)
--------------------------------------------------
def open_session(self, app, request):
s = self.get_signing_serializer(app) # 查看 是否有secret_key
if s is None:
return None
val = request.cookies.get(app.session_cookie_name) # 先去请求的cookie中获取随机字符串 第一次肯定是获取不到的 app.session_cookie_name一个配置文件 获取key为session的值 "session" 是自己配置文件定义的
if not val: # 请求第一次来的时候取不到值
return self.session_class() #返回了一个 类似字典
max_age = total_seconds(app.permanent_session_lifetime)
try:
data = s.loads(val, max_age=max_age) #loads 作用是: 反序列化+解析乱码
return self.session_class(data) #返回了一个 类似字典对象,对象里面有data
except BadSignature:
return self.session_class()
第三步 response = self.full_dispatch_request() self是app
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions() # 3.1 before_first_request 请求
try:
request_started.send(self) # Flask的信号
rv = self.preprocess_request() # 3.2 before_request 请求
if rv is None:
rv = self.dispatch_request() # 3.3 执行视图函数
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv) # 3.4 对请求的最后处理
3.1 before_first_request 的原理
def try_trigger_before_first_request_functions(self):
if self._got_first_request: # 这个值初始时为false
return
with self._before_request_lock: # 在这里加了把锁
if self._got_first_request:
return
for func in self.before_first_request_funcs: # 所有被before_first_request请求都会加到before_first_request_funcs列表中
func()
self._got_first_request = True # 将这个值变为True就实现了第一次的请求执行
3.2 before_request
def preprocess_request(self):
funcs = self.before_request_funcs.get(None, ()) # 获得被before_request装饰器修饰的函数
if bp is not None and bp in self.before_request_funcs:
funcs = chain(funcs, self.before_request_funcs[bp]) # 在这里和蓝图进行了拼接
for func in funcs: # 进行遍历
rv = func()
if rv is not None:
return rv
3.4 最后的处理
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv)
try:
response = self.process_response(response) # 3.4.1 对带有after_request装饰器进行执行
request_finished.send(self, response=response) # Flask的信号
except Exception:
if not from_error_handler:
raise
self.logger.exception('Request finalizing failed with an '
'error while handling an error')
return response
3.4.1 的执行
def process_response(self, response):
ctx = _request_ctx_stack.top
bp = ctx.request.blueprint
funcs = ctx._after_request_functions
if bp is not None and bp in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[bp]))
if None in self.after_request_funcs:
funcs = chain(funcs, reversed(self.after_request_funcs[None]))
for handler in funcs:
response = handler(response)
if not self.session_interface.is_null_session(ctx.session): # 3.4.1.1
self.save_session(ctx.session, response) # 3.4.1.2
return response
3.4.1.1
def is_null_session(self, obj):
#判断ctx.session 是不是 self.null_session_class = NullSession 类或者它派生类的对象
return isinstance(obj, self.null_session_class)
3.4.1.2
def save_session(self, session, response):
return self.session_interface.save_session(self, session, response)
-------------------------------------------------------------------
class SecureCookieSessionInterface(SessionInterface):
def save_session(self, app, session, response):
if not session:
if session.modified: # 只有删除session使session为空才会执行
response.delete_cookie(app.session_cookie_name,
domain=domain, path=path)
return
# 先将特殊的session字典进行转换成字典,然后加密序列化
val = self.get_signing_serializer(app).dumps(dict(session))
#给响应设置cookie,此时发现是将session写到cookie中了
response.set_cookie(app.session_cookie_name, val,
expires=expires, httponly=httponly,
domain=domain, path=path, secure=secure)
第四步: 错误处理
def handle_exception(self, e):
exc_type, exc_value, tb = sys.exc_info()
got_request_exception.send(self, exception=e) # Flask的信号
handler = self._find_error_handler(InternalServerError())
if self.propagate_exceptions:
if exc_value is e:
reraise(exc_type, exc_value, tb)
else:
raise e
self.log_exception((exc_type, exc_value, tb))
if handler is None:
return InternalServerError()
return self.finalize_request(handler(e), from_error_handler=True) # 错误信息显示在页面上
第五步: 将请求删除
class RequestContext(object):
def pop(self, exc=_sentinel):
app_ctx = self._implicit_app_ctx_stack.pop()
try:
clear_request = False
if not self._implicit_app_ctx_stack:
self.preserved = False
self._preserved_exc = None
if exc is _sentinel:
exc = sys.exc_info()[1]
self.app.do_teardown_request(exc)
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
request_close = getattr(self.request, 'close', None)
if request_close is not None:
request_close()
clear_request = True
finally:
rv = _request_ctx_stack.pop() # 5.1 将其从栈中pop出,调用的LocalStack的pop方法
if app_ctx is not None:
app_ctx.pop(exc) # 5.2
5.1 执行 app.do_teardown_request方法
class Flask(_PackageBoundObject):
def do_teardown_request(self, exc=_sentinel):
# 信号 - 请求执行完毕后自动执行(无论成功与否)
request_tearing_down.send(self, exc=exc)
5.2
class AppContext(object):
def pop(self, exc=_sentinel):
try:
if self._refcnt <= 0:
#5.2.1
self.app.do_teardown_appcontext(exc)
# 信号 - 请求上下文pop时执行
appcontext_popped.send(self.app)
-----------------------------------------------------------
class Flask(_PackageBoundObject):
def do_teardown_appcontext(self, exc=_sentinel):
# 信号 - 请求上下文执行完毕后自动执行(无论成功与否)
appcontext_tearing_down.send(self, exc=exc)
至此,Flask的请求流程完毕!!!!撒花、撒花、撒花、*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。!
当我们执行视图函数时是如何调用的这些东西呢?
我们以request为例,说明一下流程,首先我们的视图函数这样写
@app.route("/index")
def index():
print(request)
request.method
return ""
当我们打印request对象时,会调用其__str__()方法
# request是 LocalProxy类的一个对象,所以会调用这个类的__str__()方法
# 偏函数作为参数传入到LocalProxy类中
request = LocalProxy(partial(_lookup_req_object, 'request'))
-------------------------------------------------------------------
class LocalProxy(object):
# local是偏函数
def __init__(self, local, name=None):
# 防止触发__setattr__(),所以这样赋值
object.__setattr__(self, '_LocalProxy__local', local) # self.__local = local
object.__setattr__(self, '__name__', name) # self.__name__ = name
if callable(local) and not hasattr(local, '__release_local__'):
object.__setattr__(self, '__wrapped__', local)
__str__ = lambda x: str(x._get_current_object()) # x是self也就是LocalProxy()
--------------------------------------------------------------------
def _get_current_object(self):
if not hasattr(self.__local, '__release_local__'):
return self.__local()
try:
return getattr(self.__local, self.__name__) # 在这里执行了偏函数
except AttributeError:
raise RuntimeError('no object bound to %s' % self.__name__)
--------------------------------------------------------------------
偏函数:
def _lookup_req_object(name): # name是request
top = _request_ctx_stack.top # 取栈顶元素
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name) # 从请求对象中获取request属性 相当于 return _request_ctx_stack.top.request 实际上是去Request对象去找
当调用request.method方法
class LocalProxy(object):
# local是偏函数, name是method
def __init__(self, local, name=None):
# 防止触发__setattr__(),所以这样赋值
object.__setattr__(self, '_LocalProxy__local', local) # self.__local = local
object.__setattr__(self, '__name__', name) # self.__name__ = name
if callable(local) and not hasattr(local, '__release_local__'):
object.__setattr__(self, '__wrapped__', local)
def __getattr__(self, name):
if name == '__members__':
return dir(self._get_current_object())
return getattr(self._get_current_object(), name) # 相当于 return _request_ctx_stack.top.request.method
--------------------------------------------------------------------
def _get_current_object(self):
if not hasattr(self.__local, '__release_local__'):
return self.__local()
try:
return getattr(self.__local, self.__name__) # 在这里执行了偏函数
except AttributeError:
raise RuntimeError('no object bound to %s' % self.__name__)
--------------------------------------------------------------------
偏函数:
def _lookup_req_object(name): # name是request
top = _request_ctx_stack.top # 取栈顶元素
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name) # 从请求对象中获取request属性 相当于 return _request_ctx_stack.top.request
※:当请求进来时先执行_request_ctx_stack.push() 中间用_request_ctx_stack.top 取值 然后执行 _request_ctx_stack.pop() 进行销毁。
session是如何工作的?
当请求到来时,会根据随机字符串到"数据库"中找,有就拿相关的值,没有就返回一个存放数据的空容器(字典)
在视图函数中,可以操作放在内存中的session对象。
响应时,把随机字符串写到用户的session中,将数据保存到"数据库"
当写到如下代码时会触发,Localproxy的__setitem__()方法
session["xxx"] = 123
-----------------------------------------------------------
session = LocalProxy(partial(_lookup_req_object, 'session'))
-----------------------------------------------------------
class LocalProxy(object):
def __setitem__(self, key, value):
# 1. obj = self._get_current_object() 先执行这个函数,返回一个session对象
# 2. 对session进行赋值 由于session是一个字典并不会触发__setitem__()方法
self._get_current_object()[key] = value
18、多应用app
from flask import Flask
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
# 创建两个应用
app01 = Flask("app01")
app02 = Flask("app02")
@app01.route("/index")
def index():
return "index"
@app02.route("/pop")
def pop():
return "pop"
# 当访问app01时:127.0.0.1/index
# 当访问app01时:127.0.0.1/xxx/pop
app = DispatcherMiddleware(app01,{"/xxx":app02}) # 请求分发
if __name__ == '__main__':
run_simple('127.0.0.1',5000,app) # app.run() 底层调用的是run_simple() 直接调用第三个参数+括号
源码:先到DispatcherMiddleware类中然后加括号运行__call__方法
def __init__(self, app, mounts=None):
self.app = app
self.mounts = mounts or {} # 将路由规则赋值给mounts如果没有就是空列表
__call__方法:
def __call__(self, environ, start_response):
script = environ.get('PATH_INFO', '') # 先获取url
path_info = ''
while '/' in script: # 当 / 在url中开始遍历 例如 : /sec/index
if script in self.mounts:
app = self.mounts[script] # 找到匹配的规则
break
script, last_item = script.rsplit('/', 1) # 从第一个 / 的位置进行分割
path_info = '/%s%s' % (last_item, path_info) # srcipt = /sec last_item = /index
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 # 在这里进行重新赋值用于路由匹配
return app(environ, start_response) # 调用app.__call__
还有一个测试的脚本
from flask import Flask,_app_ctx_stack
app1 = Flask("001")
app2 = Flask("002")
# 会触发___enter__、__exit__ 方法
with app1.app_context():
print( _app_ctx_stack._local.__storage__)
with app2.app_context():
print(_app_ctx_stack._local.__storage__)
很好的解释了flask的local中保存数据时,为什么用列表创建出来的栈,以及为什么用栈而不用对其进行赋值。
---- 如果写web程序,web运行环境:栈中永远保存一条数据(可有不用栈)
---- 写脚本获取app信息时,可能存在app上下文嵌套关系。
19、信号
Flask框架中的信号基于blinker,其主要就是让开发者可是在flask请求过程中定制一些用户行为。如果要使用信号,就要安装blinker。
Flask中信号的种类:
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') # ctx.push() 应用上下文push执行
appcontext_popped = _signals.signal('appcontext-popped') # ctx.autopop() 应用上下文pop执行
message_flashed = _signals.signal('message-flashed') # 闪现时执行
正确使用信号的姿势: signals+信号的种类+connect(函数名)
from flask import signals
def send_signal(*args): # 这里有一个参数
print(args)
print("我是信号")
signals.request_started.connect(send_signal)
信号有返回值也不会拦截。
执行的顺序:
1. before_first_request 装饰器
2. request_start 请求前信号
3. before_request 装饰器
4.before_render_template 渲染前信号
5.template_rendered 渲染后信号
6.after_request 装饰器
7.session.save()
8.request_finished 请求后信号
如果上述步骤出错则会触发 got_request_exception 信号
最后还要补充的:
这些对象都是localproxy对象:
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))
当请求终止时这些对象都会pop掉
如何体现多线程的?
各个线程都有自己的唯一id进行区分