文章目录
1 预备知识
flask源码很pythonic,使用了很多python的内置方法和面向对象的思想,为了很好的理解flask源码,需要具备一些基础知识
1.1 setattr、getattr、delattr
class Foo:
storage = {'A':'a','B':'b'}
def __getattr__(self,item):
print("__getattr__被执行了")
return Foo.storage[item]
def __setattr__(self, key, value):
print("__setattr__被执行了")
Foo.storage[key] = value
def __delattr__(self, item):
print("__delattr__被执行了")
del Foo.storage[item]
foo = Foo()
print(foo.A)
foo.C = 'c'
print(Foo.storage)
del foo.C
print(Foo.storage)
"""
OUT:
__getattr__被执行了
a
__setattr__被执行了
{'A': 'a', 'B': 'b', 'C': 'c'}
__delattr__被执行了
{'A': 'a', 'B': 'b'}
从代码的输出结果看:
(1) obj.attr 执行obj.__class__的__getattr__方法
(2) obj.attr=val 执行obj.__class__的__setattr__方法
(3) del obj.attr 执行obj.__class__的__delattr__方法
"""
1.2 python新式类的超能力
最简单的python类
class Foo_(object):
def __init__(self,storage):
self.storage = storage
foo = Foo_('hello')
print(foo.storage)
print(foo.__dict__)
foo.A = 'a'
print(foo.A)
print(foo.__dict__)
"""
OUT:
hello
{'storage': 'hello'}
a
{'storage': 'hello', 'A': 'a'}
看到这个输出可能有一个疑问:为什么没有A属性,foo还是可以使用点号设置值和取值?留着这个疑问看下面的代码
"""
”新式类“默认继承object基类
class Foo_(object):
def __init__(self,val):
object.__setattr__(self,'storage',val)
foo = Foo_('测试新式类的隐藏功能')
print(foo.storage)
"""
OUT:
测试新式类的隐藏功能
"""
再看第二个实例
class Hi:
pass
h = Hi()
class Foo__:
def __init__(self,val):
object.__setattr__(h,'storage',val)
foo = Foo__('test_Hi')
print(h.storage)
"""
OUT:
test_Hi
"""
通过这两个例子对比,可以得出以下结论:
- 任何继承object的类,都有一些隐藏的“超能力”
- 当执行obj.attr的时候,会现在obj的属性中查找,然后在在obj的class有没有实现getattr方法,最后会执行object的getattr
- 当执行obj.attr=val的时候,与上面是一样的
1.3 实例初始化中的递归问题
class Foo:
def __init__(self,storage={'A':'a'}):
self.storage = storage
def __getattr__(self,item):
print("__getattr__被执行了")
return self.storage[item]
def __setattr__(self, key, value):
print("__setattr__被执行了")
self.storage[key] = value
def __delattr__(self, item):
print("__delattr__被执行了")
del self.storage[item]
foo = Foo()
"""
OUT:
__getattr__被执行了
__getattr__被执行了
__getattr__被执行了
__getattr__被执行了
__getattr__被执行了
。
。
。
N-1
N
RecursionError: maximum recursion depth exceeded while calling a Python object
"""
为什么我初始化一个实例就会无限递归呢?
我们来分析一下这句代码内部做了什么:
无限递归就出现在上图中,对于类需要重写getattr、setattr等方法,还需要初始化实例属性,可以使用下面这种结构:
class Foo:
def __init__(self,storage={'A':'a'}):
object.__setattr__(self,'storage',storage)
def __getattr__(self,item):
print("__getattr__被执行了")
return self.storage[item]
def __setattr__(self, key, value):
print("__setattr__被执行了")
self.storage[key] = value
def __delattr__(self, item):
print("__delattr__被执行了")
del self.storage[item]
foo = Foo()
print(foo.A)
foo.B='b'
print(foo.storage)
del foo.B
print(foo.storage)
"""
OUT:
__getattr__被执行了
a
__setattr__被执行了
{'A': 'a', 'B': 'b'}
__delattr__被执行了
{'A': 'a'}
"""
1.4 偏函数
from functools import partial
def request(name):
print(name)
method = partial(request, 'method')
args = partial(request,'args')
print(method)
method()
print(args)
args()
print([callable(i) for i in [method,args]])
"""
OUT:
functools.partial(<function request at 0x0000025171926160>, 'method')
method
functools.partial(<function request at 0x0000025171926160>, 'args')
args
[True, True]
"""
partial返回一个指定参数的函数
1.5 代理设计模式
使用一个代理类来代理一个类,所有的操作都由代理类完成
class Foo:
def __init__(self,a):
self.a =a
self.b =b
def get_a(self):
return self.a
def set_a(self,val):
self.a = val
class Foo_Proxy:
def __init__(self,obj):
self._foo = obj
def get_a(self):
return self._foo.get_a()
def set_a(self,val):
self._foo.set_a(val)
proxy = Foo_Proxy(Foo(1))
print(proxy.get_a())
proxy.set_a(2)
print(proxy.get_a())
"""
OUT:
1
2
通过代理对象对基本对象进行操作
"""
1.6 本地线程
flask的上下文管理机制使用的是本地线程技术,上下文是flask的核心,flask与其他框架相比,没有把请求与响应做为视图函数的形参进行传递,而是以一种全局变量的方式随用随取,一定程度上造就了flask的轻
先看一个简单的多线程例子
from threading import Thread
from time import sleep
i = 0
def task(val):
global i
sleep(2)
i = val
thread_list = []
for index,i in enumerate(range(1,11)):
thread_list.append(Thread(target=task,args=(index,)))
for index,thread in enumerate(thread_list):
thread.start()
print(index,i)
for thread in thread_list:
thread.join()
"""
OUT:
0 10
1 10
2 10
3 10
4 10
5 10
6 10
7 10
8 10
9 10
"""
线程破环了全局变量,每个线程都在争着修改全局变量,可以通过加锁来控制一个时刻只有一个线程拿到这个变量,但是效率会很低
解决这个问题有两种方式:
使用threading.local设置本地线程
from threading import local, Thread, get_ident
from time import sleep
var = local()
def task(data):
sleep(data)
var.val = data
print(f'{get_ident()}:{var.val}')
thread_list = []
for i in range(1,11):
thread_list.append(Thread(target=task,args=(i,)))
for index,thread in enumerate(thread_list):
thread.start()
for thread in thread_list:
thread.join()
"""
OUT:
8412:1
3860:2
17392:3
12568:4
6592:5
16392:6
13336:7
8460:8
10448:9
4016:10
"""
使用全局字段保存每个线程的值
"""
{
thread_id:
{
'key':val
},
thread_id:
{
'key':val
},
}
层叠字典的方式,第一层key是线程id,线程id key对应的又是一个字典,字典存储key和value,这就是flask用于存储请求数据的数据结构
"""
2 开始剖析flask
2.1 创建一个最小的flask应用
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return "Hello world"
if __name__ == '__main__':
app.run()
(1)
app.run() # 调用了Flask的run方法
from werkzeug.serving import run_simple
try:
run_simple(t.cast(str, host), port, self, **options)
# 不看其他代码,执行了werzeug里面的run_simple
(2)
# run_simple调用了make_server函数的serve_forerver方法
srv = make_server(
hostname,
port,
application,
threaded,
processes,
request_handler,
passthrough_errors,
ssl_context,
fd=fd,
)
if fd is None:
log_startup(srv.socket)
srv.serve_forever()
(3)请求进来执行app() 也就是Flask的call方法
def __call__(self, environ: dict, start_response: t.Callable) -> t.Any
return self.wsgi_app(environ, start_response)
(4)wagi_app接受了请求参数组成的字段和可调用的响应
def wsgi_app(self, environ: dict, start_response: t.Callable) -> t.Any:
ctx = self.request_context(environ)
error: t.Optional[BaseException] = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
"""
=======================Step1===============================
分析ctx = self.request_context(environ)做了啥
"""
def request_context(self, environ: dict) -> RequestContext:
# self就是FLask()对象
return RequestContext(self, environ)
class RequestContext:
def __init__(
self,
app: "Flask",
environ: dict,
request: t.Optional["Request"] = None,
session: t.Optional["SessionMixin"] = None,
) -> None:
"""
ctx是一个RequestContext类的对象
"""
"""
=======================Step2===============================
分析ctx.push()做了啥,
因为ctx是RequestContext类的对象,所以ctx.push()执行的是RequestContext()类的push方法
"""
def push(self) -> None:
# 删掉暂时用不到的代码只留下下面这一句
_request_ctx_stack.push(self)
# 通过step3的分析,执行LocalStack类的push方法,请求内容作为参数
"""
=======================Step3===============================
_request_ctx_stack是个啥
"""
_request_ctx_stack = LocalStack()
# _request_ctx_stack 是LocalStack类的一个对象
class LocalStack:
def __init__(self) -> None:
# 使用组合的方式将_local实例化为Local类的一个实例
self._local = Local()
def push(self, obj: t.Any) -> t.List[t.Any]: # obj为RequestContext类的实例
"""Pushes a new item to the stack"""
# 这句话会执行Local类的__getattr__方法,空列表做为默认值,复制一个新的到这个列表的引用
rv = getattr(self._local, "stack", []).copy()
# 将RequestContext实例append到列表中
rv.append(obj)
# 这句话会执行Local类的__setattr__方法
self._local.stack = rv
return rv # type: ignore 返回push到stack中的值
class Local:
__slots__ = ("_storage",)
def __init__(self) -> None:
#根据前面的知识我猜到,这个类肯定实现了__getattr__、__setattr__方法
object.__setattr__(self, "_storage", ContextVar("local_storage"))
def __getattr__(self, name: str) -> t.Any:
# 这句话会执行ContextVar类的get方法
values = self._storage.get({})
try:
return values[name] # 返回values字典中‘stack’健的值
except KeyError:
raise AttributeError(name) # 如果没有这个健返回一个异常
def __setattr__(self, name: str, value: t.Any) -> None:
# self._local.stack = rv name:stack value:rv
values = self._storage.get({}).copy() # 获取线程ID健的值没有返回一个空字典并复制一个引用为values
values[name] = value # values['stack'] = rv
# 这句话会执行ContextVar类的set方法
self._storage.set(values) # values作为一个嵌套字典存入到一个字典中,键值为当前线程ID
"""
=======================Step4===============================
self._storage是 ContextVar类的一个实例
"""
self._storage = ContextVar("local_storage")#"local_storage"没有实际意义,只是一个名字
class ContextVar: # type: ignore
def __init__(self, _name: str) -> None:
self.storage: t.Dict[int, t.Dict[str, t.Any]] = {}
def get(self, default: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
#self.storage初始化是一个字典,通过线程ID健的值没有返回空字典
return self.storage.get(_get_ident(), default)
def set(self, value: t.Dict[str, t.Any]) -> None:
self.storage[_get_ident()] = value
"""
=======================Step5(小结)===============================
上部分的代码主要的目的就是将请求信息,push到类似栈结构中
存储模式是:
{
线程ID1:{'stack':[RequestContext()]},
线程ID2:{'stack':[RequestContext()]},
。
。
。
线程IDn:{'stack':[RequestContext()]},
}
"""
2.2 使用请求参数
(1) from flask import request
from flask import Flask,request
app = Flask(__name__)
@app.route('/')
def index():
if request.method == 'GET':
return "Hello world"
if __name__ == '__main__':
app.run()
(2) 请求参数被存储后的使用流程
"""
=======================Step1===============================
先找到request
"""
# request是LocalProxy()类的对象,初始化传入一个函数
request: "Request" = LocalProxy(partial(_lookup_req_object, "request"))
"""
=======================Step2===============================
_lookup_req_object函数是啥?
看下面的函数先进行总结在分析,这个函数的目的是获取到RequestContext对象的request实例属性
"""
def _lookup_req_object(name):
top = _request_ctx_stack.top
"""
========Step2-1=====
top = _request_ctx_stack.top执行了啥?
@property
def top(self) -> t.Any:
try:
#"self._local.stack = {} 或者是 {'stack':[RequestContext()]}"
return self._local.stack[-1]
except (AttributeError, IndexError):
return None
"""
if top is None:
raise RuntimeError(_request_ctx_err_msg)
return getattr(top, name)
"""
=======================Step3===============================
进入LocalProxy类中
"""
class LocalProxy:
__slots__ = ("__local", "__name", "__wrapped__")
def __init__(
self,
local: t.Union["Local", t.Callable[[], t.Any]],
name: t.Optional[str] = None,
) -> None:
object.__setattr__(self, "_LocalProxy__local", local)
object.__setattr__(self, "_LocalProxy__name", name)
if callable(local) and not hasattr(local, "__release_local__"):
object.__setattr__(self, "__wrapped__", local)
"""
==========Step3.1===========
request.method执行的是LocalProxy的getattr方法
"""
def _get_current_object(self) -> t.Any:
if not hasattr(self.__local, "__release_local__"): # type: ignore
return self.__local() # type: ignore
try:
return getattr(self.__local, self.__name) # type: ignore
except AttributeError:
raise RuntimeError(f"no object bound to {self.__name}") # type: ignore
2.3 LocalProxy的重要性
localproxy可以代理所有的类,代理的作用是对客户端代码与底层代码之间解耦,让代理处理所有的请求而不是直接使用真实的对象
例子1:
from werkzeug.local import LocalStack
user_stack = LocalStack()
# 在堆栈中入栈两个键值对,键都命名为name;
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})
def get_user():
# 出栈这两个键值对(获取user对象)
return user_stack.pop()
# 直接调用函数获取user对象
user = get_user()
# 打印name的value值
print(user['name'])
print(user['name'])
"""
OUT
John
John
"""
例子2
from werkzeug.local import LocalStack, LocalProxy
user_stack = LocalStack()
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})
def get_user():
# do something to get User object and return it
return user_stack.pop()
# 通过LocalProxy代理user对象
user = LocalProxy(get_user)
print(user['name'])
print(user['name'])
"""
OUT:
John
Bob
"""
区别:直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用Proxy,每次调用操作符(这里[]操作符用于获取属性),都会重新获取user,从而实现了动态更新user的效果。见下图: