1.请求上下文分析预备知识
1.1 导出项目依赖
我们之前使用导出项目依赖的命令是:
pip freeze > requirements.txt # 导出项目依赖
pip install -r requirements.txt # 安装项目依赖
这种方式更适合在虚拟环境的导出和导入,因为它会导出当前解释器所有的依赖。但是如果在本机的解释器环境,我们安装了很多依赖但是我们实际项目需要使用的依赖只是其中个别,那么我们需要按照以下命令更精确的导出依赖:
1.安装:
pip3 install pipreqs
2.生成项目依赖
pipreqs ./
# windows电脑有可能会报错,如果报错执行:pipreqs ./ --encoding=utf8
1.2 函数和方法
函数和方法的区分:
函数:用类或者对象调用不能将自身传入,必须按照函数形参数量传值
方法:绑定给对象或者类的方法,由谁来调用可以将自身当做参数传入。面向对象中类中不加任何装饰器的是调用给对象的方法,加classmethod对的是方法,加staticmethod的是一个普通函数
类的绑定方法,对象也可以调用,会自动把产生对象的类当做参数传入:
class Student:
@classmethod
def stu(self):
return self.__name__
stu1 = Student()
print(stu1.stu()) # Student
对象绑定的方法,类也可以调用,但是需要自己手动传参,就变成了普通的函数而不是方法,有几个值就要传几个值:
class Student:
def stu(self):
return self
print(Student.stu(1)) # 1
python中可以用isinstance判断对象是否是这个类产生的:
class Foo(object):
def fetch(self):
pass
@classmethod
def test(cls):
pass
@staticmethod
def test1():
pass
def add():
pass
a = Foo()
print(isinstance(a,Foo)) # True
print(isinstance('a',Foo)) # False
'''也可以用来判断数据类型,数据类型也是一个类,产生的数据都是该类的对象'''
print(isinstance(1,int)) # True
issubclass:判断子类是否继承自该父类:
class Student:
pass
class Stu1(Student):
pass
print(issubclass(Stu1,Student)) # True
了解了issubclass和isinstance,下面就可以来判断函数混合方法:
在python中有两个关键字,是判断他们是方法还是函数:MethodType:方法,FunctionType:函数
# 导入
from types import MethodType, FunctionType
class Foo(object):
def fetch(self):
pass
@classmethod
def test(cls):
pass
@staticmethod
def test1():
pass
def add():
pass
print(isinstance(Foo.fetch, MethodType)) # False 类调用对象的方法,就是一个普通函数
print(isinstance(Foo.fetch, FunctionType)) # True
print(isinstance(obj.fetch,MethodType)) # True 绑定给对象的方法,对象来调用,是一个方法
print(isinstance(obj.fetch,FunctionType)) # False
print(isinstance(add,FunctionType)) # True
print(isinstance(Foo.test,MethodType)) # True 类调用绑定给自身的方法,是一个方法
print(isinstance(Foo.test,FunctionType)) #False
print(isinstance(obj.test,MethodType)) # True 对象调用绑定给类的方法,将父类当做参数传入,是一个方法
print(isinstance(obj.test,FunctionType)) # False
print(isinstance(obj.test1,FunctionType)) # True 静态方法是一个普通函数,不管是对象调用还是类调用
print(isinstance(Foo.test1,FunctionType)) # True
2.threading.local对象
并发编程时,多个线程操作同一个变量,会出现并发安全的问题,需要加锁。而是用local对象多线程并发操作时,不需要加锁,不会出现数据错乱。
本质原理:多线程修改同一个数据,复制多份变量,为每个线程开辟一块空间进行数据存储,每个线程操作自己的那部分数据。
在全局定义一个a,开启10个线程依次修改全局的a,因为一个进程下只有一个线程在执行,所以会依次修改a,从0到9,这也验证了gil锁的存在:
from threading import Thread, get_ident
a = 0
def task(args):
global a
a = args
print('线程id为:%s' % get_ident(), a)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
当我们在task内部加上time.sleep之后,得到的结果全部是9。这是因为当第一次修改之后程序进入阻塞态,cpu去执行下一次循环修改a,直到最后一次修改a为9。当所有程序到就绪态打印的a都是全局的a:
from threading import Thread, get_ident
import time
a = 0
def task(args):
global a
a = args
time.sleep(2)
print('线程id为:%s' % get_ident(), a)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
'''打印的结果全部为9'''
如果我们想解决数据错乱,达到刚才的效果(a分别打印0到9),有三种方案:
方法1:用join()控制每一个线程执行完毕之后,再释放cpu:
from threading import Thread, get_ident
import time
a = 0
def task(args):
global a
a = args
time.sleep(2)
print('线程id为:%s' % get_ident(), a)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
t.join()
方式二:用threading模块中自带的互斥锁,执行完毕之后再释放锁(用互斥锁解决并发安全的问题):
from threading import Thread, get_ident,Lock
import time
lock = Lock()
a = 0
def task(args):
lock.acquire()
global a
a = args
time.sleep(2)
print('线程id为:%s' % get_ident(), a)
lock.release()
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
只需要在数据可能发生错乱的地方加锁串行,其他地方可以并行
方式三:使用local对象,避免并发安全的问题:
虽然修改的都是全局对象对象a的args属性,但是每个对象修改的都是自己那个部分,并不会发生数据错乱
from threading import Thread, get_ident,local
import time
# 生成一个local对象
a = local()
def task(args):
# 给local对象添加属性
a.args = args
time.sleep(2)
# 只能打印a.args,如果直接打印a的话结果是对象a的内存地址
print('线程id为:%s' % get_ident(), a.args)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
这和flask中的request类似,虽然我们每次请求用的都是全局的request,但是每次请求的request对象的属性都只是本次请求线程的数据,并不会和其他请求线程的数据发生混淆和错乱。这是因为flask中的request就是用了local,但是它只能区分线程,不能区分协程。
协程:单线程下的并发。
python中的多线程实际上是由一个线程在程序之间来回切换产生的。而协程是程序层面的切换,程序层面切换耗费资源较少。也就是协程还是由同一个线程来回切换产生的并发效果,cpu(在线程上)遇到io操作就去执行下一个程序,实现单线程下的并发效果。既然是同一个线程,使用loacl必然会导致数据错乱。
local本质使用线程来区分数据,也就是每个线程上都有自己独立的数据不受其他线程的影响:
{'线程id号':{args:1},{'线程id号':{args,2}}...}
但是如果是使用协程,local本质依然可以按照协程来区分数据,本质如下:
{'协程id号':{args:1},{'协程id号':{args,2}}...}
3.兼容线程和协程的local对象
正是因为gil锁的存在,在一个进程下开多个线程,也只能有一个线程在执行。如果我们想要利用多核优势,就需要开多个进程(比如是4核,那么就需要开4个进程)。这也就是计算密集型程序要用多进程的原因,因为这样才能充分利用多核优势。于是在线程下开携程,一个进程下只能有一个线程在运行,所以我们要利用协程来充分榨干cpu。
接下来用local对象封装一个兼容线程和协程的local对象:
try:
'''如果能导入说明使用协程访问'''
from greenlet import getcurrent as get_ident
except Exception as e:
'''否则是用线程访问的'''
from threading import get_ident
from threading import Thread
import time
class Local(object):
def __init__(self):
'''用类object点方法而不是用对象,是因为对象点那么self就是Local的对象。对象.storage=值会触发
__setattr__方法,所以如果是对象点结果就是执行__setattr__方法的前四行,产生递归深度的问题'''
object.__setattr__(self, 'storage', {})
def __setattr__(self, k, v):
ident = get_ident()
# 这里的get_ident()方法如果是线程访问的产生的结果就是线程,否则就是协程
if ident in self.storage:
'''storage:{线程id:{k:v},协程id:{k"v}...}'''
self.storage[ident][k] = v
else:
self.storage = {k: v}
def __getattr__(self, k):
ident = get_ident()
return self.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
obj.xxx = arg
print(obj.val)
for i in range(10):
t = Thread(target=task,args=(i,))
t.start()
4.偏函数
当我们只知道函数参数中的部分时,我们可以分批次给函数传参:
关键字传参:
from functools import partial
def index(a, b, c):
return a * b * c
# print(index(2,3,4)) # 24
index = partial(index,2,3)
print(index(4)) # 24
也可以利用位置传参:
from functools import partial
def index(a, b, c):
return a * b * c
# print(index(2,3,4)) # 24
index = partial(index,b=2,c=3)
print(index(a=4))
5.flask执行流程(以1.1.4版本为例)
注:1.1.4版本的Flask要搭配2.0.1版本的MarkupSafe
1.我们手写一个简单的flask框架:
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return 'index'
if __name__ == '__main__':
app.run()
2.当请求来的时候,执行了run()方法,实际是在执行run()方法中的run_simple()方法:
run方法:
def run():
try:
run_simple(host, port, self, **options)
finally:
self._got_first_request = False
3.run_simple有一种特殊的机制:
run_simple('localhost',4000,hello)执行的结果是hello()。而
run_simple(host, port, self, **options)中的self就是Flask的对象app,run_simple(host, port, self, **options)的结果也就是app()。app()会触发Flask中的__call__。
flask/call.py:
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
本质是在执行wsgi_app(environ, start_response)
4.wsgi_app方法其实就是整个flask请求进来到处理请求到返回结果的整个生命周期的内容都在这个函数中处理:
4.1它包装了request
4.2执行了请求扩展
4.3根据路由匹配视图函数
4.4执行视图函数
4.5视图函数返回结果
def wsgi_app(self, environ, start_response):
'''environ是当次http请求的字典对象'''
ctx=self.request_context(environ)
error = 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)
5.上述代码中self是Flask对象,也就是app,self点request_context方法,下面看request_context方法:
def request_context(self, environ):
return RequestContext(self, environ)
request_context执行的结果是RequestContext(请求上下文)对象
6.RequestContext源码:
class RequestContext(object):
def __init__(self, app, environ, request=None, session=None):
self.app = app
'''刚开始request是None'''
if request is None:
request = app.request_class(environ)
"""
点进request_class源码:
request_class = Request
所以request也就是Request的一个对象,把当次请求字典传入,request就拥有了当次请求的属性
"""
self.request = request
'''将request赋值给当次请求的Flask对象self'''
self.url_adapter = None
try:
self.url_adapter = app.create_url_adapter(self.request)
except HTTPException as e:
self.request.routing_exception = e
self.flashes = None
self.session = session
7.再回到wsgi_app方法:
'''ctx是类RequestContext的对象,当次请求的信息都在ctx中'''
ctx = self.request_context(environ)
error = None
try:
try:
ctx.push()
response = self.full_dispatch_request()
ctx.push():现在去类RequestContext对象查找方法push()
8.RequestContext下push
def push()
_request_ctx_stack.push(self)
'''此时self是RequestContext的对象ctx'''
if self.session is None:
session_interface = self.app.session_interface
self.session = session_interface.open_session(self.app, self.request)
'''如果session是空的,那么执行方法open_session'''
9.点进_request_ctx_stack方法:
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
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"))
所以_request_ctx_stack.push(self)就相当于LocalStack().push(self),此时的self是ctx对象
10.LocalStack中push方法:
def push(self, obj):
"""Pushes a new item to the stack"""
rv = getattr(self._local, "stack", None)
'''
self是类LocalStack的对象,去__init__中找_local方法,发现_local是Local的一个对象,这个Local()对象可以区分线程或者协程(类似于我们刚才自己定义的)
def __init__(self):
self._local = Local()
'''
if rv is None:
self._local.stack = rv = []
rv.append(obj)
'''此时的rv是:
{线程id号1:{stack:[]},线程id号2:{stack:[]}...}
'''
return rv
11.当请求走到视图函数中,此时的request并不是真的request。点进request源码:
request = LocalProxy(partial(_lookup_req_object, "request"))
发现request其实是类LocalProxy的对象。当我们执行print(request)是会触发类LocalProxy的__str__方法。request.method会触发类LocalProxy的__getattr__方法。现在去类LocalProxy中找__getattr__方法。
12.LocalProxy下__getattr__方法:
def __getattr__(self, name):
...
return getattr(self._get_current_object(), name)
LocalProxy下_get_current_object()方法
def _get_current_object(self):
if not hasattr(self.__local, "__release_local__"):
'''__local()是Local对象,有值'''
'''__local()是Local对象,有值'''
#self.__local() 在init的时候,实例化的,在init中:object.__setattr__(self, "_LocalProxy__local", local)
# 用了隐藏属性
#self.__local 实例化该类的时候传入的local(偏函数的内存地址:partial(_lookup_req_object, "request"))
#加括号返回,就会执行偏函数,也就是执行_lookup_req_object,不需要传参数了
#这个地方的返回值就是request对象(当此请求的request,没有乱)
return self.__local()
try:
return getattr(self.__local, self.__name__)
except AttributeError:
raise RuntimeError("no object bound to %s" % self.__name__)
综上所述:每次请求的request都是自己的,session、g对象同理。
标签:__,请求,flask,self,request,线程,上下文,local,def From: https://www.cnblogs.com/ERROR404Notfound/p/17293085.html