flask的request和session设置方式比较新颖,如果没有这种方式,那么就只能通过参数的传递。
flask是如何做的呢?
1:本地线程,保证即使是多个线程,自己的值也是互相隔离
1 import threading
2
3 local_values = threading.local()
4
5
6 def func(num):
7 local_values.name = num
8 import time
9 time.sleep(1)
10 print(local_values.name, threading.current_thread().name)
11
12
13 for i in range(20):
14 th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
15 th.start()
2:自定义threading.local
1 """
2 {
3 1368:{}
4 }
5
6
7
8 """
9 import threading
10 try:
11 from greenlet import getcurrent as get_ident # 协程
12 except ImportError:
13 try:
14 from thread import get_ident
15 except ImportError:
16 from _thread import get_ident # 线程
17
18
19 class Local(object):
20 def __init__(self):
21 self.storage = {}
22 self.get_ident = get_ident
23
24 def set(self,k,v):
25 ident = self.get_ident()
26 origin = self.storage.get(ident)
27 if not origin:
28 origin = {k:v}
29 else:
30 origin[k] = v
31 self.storage[ident] = origin
32
33 def get(self,k):
34 ident = self.get_ident()
35 origin = self.storage.get(ident)
36 if not origin:
37 return None
38 return origin.get(k,None)
39
40 local_values = Local()
41
42
43 def task(num):
44 local_values.set('name',num)
45 import time
46 time.sleep(1)
47 print(local_values.get('name'), threading.current_thread().name)
48
49
50 for i in range(20):
51 th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
52 th.start()
升级版
1 import threading
2 try:
3 from greenlet import getcurrent as get_ident # 协程
4 except ImportError:
5 try:
6 from thread import get_ident
7 except ImportError:
8 from _thread import get_ident # 线程
9
10
11 class Local(object):
12
13 def __init__(self):
14 object.__setattr__(self, '__storage__', {})
15 object.__setattr__(self, '__ident_func__', get_ident)
16
17
18 def __getattr__(self, name):
19 try:
20 return self.__storage__[self.__ident_func__()][name]
21 except KeyError:
22 raise AttributeError(name)
23
24 def __setattr__(self, name, value):
25 ident = self.__ident_func__()
26 storage = self.__storage__
27 try:
28 storage[ident][name] = value
29 except KeyError:
30 storage[ident] = {name: value}
31
32 def __delattr__(self, name):
33 try:
34 del self.__storage__[self.__ident_func__()][name]
35 except KeyError:
36 raise AttributeError(name)
37
38
39 local_values = Local()
40
41
42 def task(num):
43 local_values.name = num
44 import time
45 time.sleep(1)
46 print(local_values.name, threading.current_thread().name)
47
48
49 for i in range(20):
50 th = threading.Thread(target=task, args=(i,),name='线程%s' % i)
51 th.start()
说明解释:
1 - threading.local对象,用于为每个线程开辟一块空间来保存它独有的值。
2
3 - 源码(request)
4 - 情况一:单进程单线程,基于全局变量做。
5 - 情况二:单进程多线程,threading.local对象。
6 - 情况二:单进程单线程(多个协程),threading.local对象做不到。
7
8 - 决定:
9 - 以后不支持协程:threading.local对象。
10 - 支持:自定义类似threading.local对象(支持协程)
11 - 自定义类似threading.local对象
12 PS:
13 a.
14 object.__setattr__(self, 'storage', {})
15 self.storage = {}
16 b.
17 对象.xx
18 def __setattr__(self, key, value):
19 print(key,value)
3:请求上下文原理
1 from functools import partial
2 from flask.globals import LocalStack, LocalProxy
3
4 ls = LocalStack()
5
6
7 class RequestContext(object):
8 def __init__(self, environ):
9 self.request = environ
10
11
12 def _lookup_req_object(name):
13 top = ls.top
14 if top is None:
15 raise RuntimeError(ls)
16 return getattr(top, name)
17
18
19 session = LocalProxy(partial(_lookup_req_object, 'request'))
20
21 ls.push(RequestContext('c1')) # 当请求进来时,放入
22 print(session) # 视图函数使用
23 print(session) # 视图函数使用
24 ls.pop() # 请求结束pop
25
26
27 ls.push(RequestContext('c2'))
28 print(session)
29
30 ls.push(RequestContext('c3'))
31 print(session)
4:请求上下文源码
1 from greenlet import getcurrent as get_ident
2
3
4 def release_local(local):
5 local.__release_local__()
6
7
8 class Local(object):
9 __slots__ = ('__storage__', '__ident_func__')
10
11 def __init__(self):
12 # self.__storage__ = {}
13 # self.__ident_func__ = get_ident
14 object.__setattr__(self, '__storage__', {})
15 object.__setattr__(self, '__ident_func__', get_ident)
16
17 def __release_local__(self):
18 self.__storage__.pop(self.__ident_func__(), None)
19
20 def __getattr__(self, name):
21 try:
22 return self.__storage__[self.__ident_func__()][name]
23 except KeyError:
24 raise AttributeError(name)
25
26 def __setattr__(self, name, value):
27 ident = self.__ident_func__()
28 storage = self.__storage__
29 try:
30 storage[ident][name] = value
31 except KeyError:
32 storage[ident] = {name: value}
33
34 def __delattr__(self, name):
35 try:
36 del self.__storage__[self.__ident_func__()][name]
37 except KeyError:
38 raise AttributeError(name)
39
40
41 class LocalStack(object):
42 def __init__(self):
43 self._local = Local()
44
45 def __release_local__(self):
46 self._local.__release_local__()
47
48 def push(self, obj):
49 """Pushes a new item to the stack"""
50 rv = getattr(self._local, 'stack', None)
51 if rv is None:
52 self._local.stack = rv = []
53 rv.append(obj)
54 return rv
55
56 def pop(self):
57 """Removes the topmost item from the stack, will return the
58 old value or `None` if the stack was already empty.
59 """
60 stack = getattr(self._local, 'stack', None)
61 if stack is None:
62 return None
63 elif len(stack) == 1:
64 release_local(self._local)
65 return stack[-1]
66 else:
67 return stack.pop()
68
69 @property
70 def top(self):
71 """The topmost item on the stack. If the stack is empty,
72 `None` is returned.
73 """
74 try:
75 return self._local.stack[-1]
76 except (AttributeError, IndexError):
77 return None
78
79
80 stc = LocalStack()
81
82 stc.push(123)
83 v = stc.pop()
84
85 print(v)
5:个人源码剖析
a.偏函数
1 import functools
2
3 def func(a1):
4 print(a1)
5
6
7 new_func = functools.partial(func,666)
8
9 new_func()
10
11 说明:将666这个参数当做func函数的第一个参数
b.第一阶段
1 1.当携带这用户的请求过来之后,首先会执行app.run,会执行 run_simple(host, port, self, **options),因为run方法是由app调用的,app又是Flask的对象,因此当执行了app.run()之后就会调用Flask类中的__call__方法
2 2.进入app.__call__拿到return self.wsgi_app(environ, start_response)
3 3.进入app.wsgi_app之后就会执行:ctx = self.request_context(environ)
4 app.request_context(environ)会返回 return RequestContext(self, environ);然后执行__init__方法得到request = app.request_class(environ);最后将请求的信息放入request中。经过上面的操作,得出ctx就是携带有请求信息的request_context对象,ctx里面具有ctx.app,ctx.request,ctx.session
5 4.然后执行ctx.push()
6 进入push()里面找到 _request_ctx_stack.push(self),这里面的self指的是ctx对象,_request_ctx_stack是全局变量,_request_ctx_stack = LocalStack(),_request_ctx_stack 是LocalStack这个类的对象;通过def push(self, obj)这个方法将ctx这个对象存入threadinglocal中,给ctx这个对象分配专属与他的一块内存空间。
c.第二阶段
1 如果需要打印print(request),源码是如何执行的呢?
2 进入request
3 1.request = LocalProxy(partial(_lookup_req_object, 'request'))
4 这个里面:偏函数=partial(_lookup_req_object, 'request')
5 首先执行LocalProxy这个类实例化执行__init__方法:
6 def __init__(self, local, name=None):
7 object.__setattr__(self, '_LocalProxy__local', local)
8 在这里:_LocalProxy__local其实就是强制获取私有字段等价于
9 self._local = local
10 2.再执行__str__方法:
11 __str__ = lambda x: str(x._get_current_object())
12 3.def _get_current_object(self):
13 if not hasattr(self.__local, '__release_local__'):
14 return self.__local()
15 try:
16 return getattr(self.__local, self.__name__)
17 返回return self.__local(),执行偏函数
18 def _lookup_req_object(name):
19 top = _request_ctx_stack.top
20 if top is None:
21 raise RuntimeError(_request_ctx_err_msg)
22 return getattr(top, name)
23 4.从top = _request_ctx_stack.top中去拿开始存入的ctx对象,然后再从ctx对象中去拿request: return self._local.stack[-1]
24 5.通过getattr(top, name)==getattr(ctx对象,request)
25
26 如果是打印print(request.method)
27 其实就是多了一步,再执行__str__方法之前执行def __getattr__(self, name)方法
28
29
30 代码示例展示:
31 from flask import Flask,request,session,g,current_app
32
33 app = Flask(__name__)
34
35 @app.route('/',methods=['GET',"POST"])
36 def index():
37 # request是 LocalProxy 的对象
38 print(request) # LocalProxy.__str__ --> str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.request
39 request.method # LocalProxy.__getattr__ -->
40 # str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.request
41 # getattr(self._get_current_object(), name) --> ctx.request.method
42
43 request.path # ctx.request.path
44
45 print(session) # LocalProxy.__str__ --> str(LocalProxy._get_current_object) --> 调用偏函数 --> ctx.session
46
47
48 print(g) # 执行g对象的__str__
49 return "index"
50
51
52 if __name__ == '__main__':
53 app.__call__
54 app.wsgi_app
55 app.wsgi_app
56 app.request_class
57 app.run()
d.第三阶段
1 # 寻找视图函数并执行,获取返回值
2 response = self.full_dispatch_request()
3
4 1.先执行before_first_request
5 self.try_trigger_before_first_request_functions()
6
7 2.触发request_started信号,request_started
8 request_started.send(self)
9 补充:信号:
10 from flask import Flask, signals, render_template
11
12 app = Flask(__name__)
13
14
15 # 往信号中注册函数
16 def func(*args, **kwargs):
17 print('触发型号', args, kwargs)
18
19
20 signals.request_started.connect(func)
21
22
23 # 触发信号: signals.request_started.send()
24
25 @app.before_first_request
26 def before_first1(*args, **kwargs):
27 pass
28
29
30 @app.before_first_request
31 def before_first2(*args, **kwargs):
32 pass
33
34
35 @app.before_request
36 def before_first3(*args, **kwargs):
37 pass
38
39
40 @app.route('/', methods=['GET', "POST"])
41 def index():
42 print('视图')
43 return render_template('index.html')
44
45
46 if __name__ == '__main__':
47 app.wsgi_app
48 app.run()
49
50 3.再执行def preprocess_request(self):中的before_request
51
52 4.# 执行视图函数dispatch_request-->def render_template(template_name_or_list, **context):-->before_render_template.send(app, template=template, context=context)-->template_rendered.send(app, template=template, context=context)
53 rv = self.dispatch_request()
54
55 5. 执行self.finalize_request(rv)--> response = self.process_response(response)-->self.session_interface.save_session(self, ctx.session, response)
56
57 6.执行after_request
58
59 7.执行信号request_finished.send(self, response=response)
e.第四阶段
1 ctx.auto_pop,将请求信息从threadinglocal中清除
2 1.ctx.auto_pop -->self.pop(exc)-->_request_ctx_stack.pop()
3
4 2._request_ctx_stack = LocalStack()-->return stack.pop()
6.应用上下文原理(跟请求上下文的原理几乎一致)
1 通过app_ctx.push()将app_ctx对象存储到threadinglocal中,分配一块独有的内存空间,app_ctx对象中具有:app_ctx.app与app_ctx.g
2
3 1.app_ctx.g是存储变量用的
4 示例代码:
5 from flask import Flask,request,g
6
7 app = Flask(__name__)
8
9 @app.before_request
10 def before():
11 g.permission_code_list = ['list','add']
12
13
14 @app.route('/',methods=['GET',"POST"])
15 def index():
16 print(g.permission_code_list)
17 return "index"
18
19
20 if __name__ == '__main__':
21 app.run()
补充:
a:离线脚本示例
1 """
2 需求:不用数据库连接池,显示数据库连接
3 """
4 class SQLHelper(object):
5
6 def open(self):
7 pass
8
9 def fetch(self,sql):
10 pass
11
12 def close(self):
13 pass
14
15 def __enter__(self):
16 self.open()
17 return self
18
19 def __exit__(self, exc_type, exc_val, exc_tb):
20 self.close()
21
22
23 # obj = SQLHelper()
24 # obj.open()
25 # obj.fetch('select ....')
26 # obj.close()
27
28
29 with SQLHelper() as obj: # 自动调用类中的__enter__方法, obj就是__enter__返回值
30 obj.fetch('xxxx')
31 # 当执行完毕后,自动调用类 __exit__ 方法
1 from flask import Flask,current_app,globals,_app_ctx_stack
2
3 app1 = Flask('app01')
4 app1.debug = False # 用户/密码/邮箱
5 # app_ctx = AppContext(self):
6 # app_ctx.app
7 # app_ctx.g
8
9 app2 = Flask('app02')
10 app2.debug = True # 用户/密码/邮箱
11 # app_ctx = AppContext(self):
12 # app_ctx.app
13 # app_ctx.g
14
15
16
17 with app1.app_context():# __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
18 # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438>]}}
19 print(_app_ctx_stack._local.__storage__)
20 print(current_app.config['DEBUG'])
21
22 with app2.app_context():
23 # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
24 print(_app_ctx_stack._local.__storage__)
25 print(current_app.config['DEBUG'])
26
27 print(current_app.config['DEBUG'])
7.总结
1 1. 面向对象私有
2 class Foo(object):
3
4 def __init__(self):
5 self.name = 'alex'
6 self.__age = 18
7
8 def get_age(self):
9 return self.__age
10
11 obj = Foo()
12 # print(obj.name)
13 # print(obj.get_age())
14 # 强制获取私有字段
15 print(obj._Foo__age)
16
17 2. 谈谈Flask上下文管理
18 - 与django相比是两种不同的实现方式。
19 - django/tornado是通过传参数形式
20 - flask是通过上下文管理
21 两种都可以实现,只不过试下方式不一样。
22 - 上下文管理:
23 - threading.local/Local类,其中创建了一个字典{greelet做唯一标识:存数据} 保证数据隔离
24 - 请求进来:
25 - 请求相关所有数据封装到了RequestContext中。
26 - 再讲RequestContext对象添加到Local中(通过LocalStack将对象添加到Local对象中)
27 - 使用,调用request
28 - 调用此类方法 request.method、print(request)、request+xxx 会执行LocalProxy中对应的方法
29 - 函数
30 - 通过LocalStack去Local中获取值。
31 - 请求终止
32 - 通过LocalStack的pop方法 Local中将值异常。
33
34 3.上下文
35 a. 请求上下文
36 - request
37 - session
38 b. 应用上下文
39
40
41 请求流程:
42 _request_ctx_stack.local = {
43
44 }
45
46 _app_ctx_stack.local = {
47
48 }
49
50
51 3.1. 请求到来 ,有人来访问
52 # 将请求相关的数据environ封装到了RequestContext对象中
53 # 再讲对象封装到local中(每个线程/每个协程独立空间存储)
54 # ctx.app # 当前APP的名称
55 # ctx.request # Request对象(封装请求相关东西)
56 # ctx.session # 空
57 _request_ctx_stack.local = {
58 唯一标识:{
59 "stack":[ctx, ]
60 },
61 唯一标识:{
62 "stack":[ctx, ]
63 },
64 }
65
66
67 # app_ctx = AppContext对象
68 # app_ctx.app
69 # app_ctx.g
70
71 _app_ctx_stack.local = {
72 唯一标识:{
73 "stack":[app_ctx, ]
74 },
75 唯一标识:{
76 "stack":[app_ctx, ]
77 },
78 }
79
80 3.2. 使用
81 from flask import request,session,g,current_app
82
83 print(request,session,g,current_app)
84
85 都会执行相应LocalProxy对象的 __str__
86
87 current_app = LocalProxy(_find_app)
88 request = LocalProxy(partial(_lookup_req_object, 'request'))
89 session = LocalProxy(partial(_lookup_req_object, 'session'))
90
91 current_app = LocalProxy(_find_app)
92 g = LocalProxy(partial(_lookup_app_object, 'g'))
93
94 3.3. 终止,全部pop
95
96 问题1:多线程是如何体现?
97 问题2:flask的local中保存数据时,使用列表创建出来的栈。为什么用栈?
98 - 如果写web程序,web运行环境;栈中永远保存1条数据(可以不用栈)。
99 - 写脚本获取app信息时,可能存在app上下文嵌套关系。
100 from flask import Flask,current_app,globals,_app_ctx_stack
101
102 app1 = Flask('app01')
103 app1.debug = False # 用户/密码/邮箱
104 # app_ctx = AppContext(self):
105 # app_ctx.app
106 # app_ctx.g
107
108 app2 = Flask('app02')
109 app2.debug = True # 用户/密码/邮箱
110 # app_ctx = AppContext(self):
111 # app_ctx.app
112 # app_ctx.g
113
114
115
116 with app1.app_context():# __enter__方法 -> push -> app_ctx添加到_app_ctx_stack.local
117 # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438>]}}
118 print(_app_ctx_stack._local.__storage__)
119 print(current_app.config['DEBUG'])
120
121 with app2.app_context():
122 # {<greenlet.greenlet object at 0x00000000036E2340>: {'stack': [<flask.ctx.AppContext object at 0x00000000037CA438> ]}}
123 print(_app_ctx_stack._local.__storage__)
124 print(current_app.config['DEBUG'])
125
126 print(current_app.config['DEBUG'])
127
128 3.3. 多app应用
129
130 from werkzeug.wsgi import DispatcherMiddleware
131 from werkzeug.serving import run_simple
132 from flask import Flask, current_app
133
134 app1 = Flask('app01')
135
136 app2 = Flask('app02')
137
138
139
140 @app1.route('/index')
141 def index():
142 return "app01"
143
144
145 @app2.route('/index2')
146 def index2():
147 return "app2"
148
149 # http://www.oldboyedu.com/index
150 # http://www.oldboyedu.com/sec/index2
151 dm = DispatcherMiddleware(app1, {
152 '/sec': app2,
153 })
154
155 if __name__ == "__main__":
156 run_simple('localhost', 5000, dm)
157
158 4.信号
159 a. before_first_request
160 b. 触发 request_started 信号
161 c. before_request
162 d. 模板渲染
163 渲染前的信号 before_render_template.send(app, template=template, context=context)
164 rv = template.render(context) # 模板渲染
165 渲染后的信号 template_rendered.send(app, template=template, context=context)
166 e. after_request
167 f. session.save_session()
168 g. 触发 request_finished信号
169
170 如果上述过程出错:
171 触发错误处理信号 got_request_exception.send(self, exception=e)
172
173 h. 触发信号 request_tearing_down
174
175 5.面向对象
176 - 封装
177 class Foo:
178 def __init__(self):
179 self.age = 123
180 self.nmame = 'ssdf'
181
182 class Bar:
183 def __init__(self):
184 self.xx = 111
185
186
187
188 class Base:
189 def __init__(self):
190 self.f = Foo()
191 self.x = Bar()
192 - 某个值+括号
193 - 函数/方法
194 - 类
195 - 对象
196
197 - 特殊的双下划线方法:
198 __new__
199 __call__
200 __str__
201 __setattr__
202 __setitem__
203 __enter__
204 __exit__
205 __add__
206
207 PS: Flask的LocalProxy中全部使用。
208
209 - 强制调用私有字段
210 - 派生类中无法调用基类私有字段