flask框架
flask
django是个大而全的框架,flask是一个轻量级的框架
django内部为我们提供了非常多的组件,orm/session/cookie/admin/form/modelform/路由/视图/模块/中间件/分页/auth/contenttype/缓存/信号/多数据库
flask框架本身没有太多的功能,路由视图模板(jinjia2)/中间件 ,第三方组件非常齐全。
注意事项:django的请求处理是封装传递。
flask的请求是利用上下文管理来实现的(放到一个地方,在去这个地方取)
最大区别处理机制不同: django:通过传参的方式 flask:通过上下文管理方式实现
flask快速使用
学习博客:https://www.cnblogs.com/wupeiqi/articles/7552008.html
安装
pip3 install flask
wgsi
django和flask内部没有实现socket,而是wsgi实现的。 wsgi是web服务的网关接口,是一个协议。实现他的协议有wsgiref、werkzurg、uwsgi(多并发,部署django项目用)
依赖wsgi Werkzeug
from werkzeug.serving import run_simple def func(environ,start_response): print('请求来了') pass if __name__ == '__main__': run_simple('127.0.0.1',5000,func)
from werkzeug.serving import run_simple class Flask(object): def __call__(self,environ,start_response) return 'xx' app=Flask() if __name__ == '__main__': run_simple('127.0.0.1',5000,app) #访问游览器,执行对象app(),会执行类中的call方法
from werkzeug.serving import run_simple class Flask(object): def __call__(self,environ,start_response) return 'xx' def run(self): run_simple('127.0.0.1',5000,app) #访问游览器,执行对象app(),会执行类中的call方法 app=Flask() if __name__ == '__main__': app.run()
app.run()的执行流程:https://www.h5w3.com/13717.html
快速使用flask
from flask import Flask app = Flask(__name__) # print(__name__) #app 文件名 @app.route('/') def hello_world(): return 'Hello World!' @app.route('/index') def index(): return 'index!' if __name__ == '__main__': app.run()
总结:
- flask框架是基于werkzeug的wsgi实现的,自己没有wgsi
- 用户请求一但到来,就会执行app的call方法
基本使用:
# -*- coding: utf-8 -*- from flask import Flask,request,redirect,render_template app=Flask(__name__) # print(__name__) #s1 data_dict = { 1:{'name':'xx','age':77}, 2:{'name':'zz','age':778}, 3:{'name':'zzx','age':999}, } #装饰器 @app.route('/login',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') #post请求取值 user = request.form.get('user') pwd = request.form.get('pwd') if user == 'zz' and pwd == '123': return redirect('/index') #url else: error = 'error' return render_template('login.html',error=error) @app.route('/index',endpoint='idx') #idx url别名 def index(): return render_template('index.html',data_dict=data_dict) @app.route('/edit') def edit(): #get请求取值 nid = request.args.get('nid') info = data_dict[int(nid)] return '修改' @app.route('/del/<int:nid>') #从url上取id def delete(nid): print(nid) del data_dict[nid] return '删除' if __name__ == '__main__': app.run()
总结:
1.flask 路由
@app.route('/login',methods=['GET','POST']) def login(): pass
2.路由的参数
@app.route('/login',methods=['GET','POST'],endpoint='login') def login(): pass 注意:endpoint:url别名,不能重名,默认为函数名
3.动态路由
@app.route('/edit') def edit(): #get请求取值 nid = request.args.get('nid') #前端模板url /xxx?nid=2 info = data_dict[int(nid)] return '修改' @app.route('/del/<int:nid>') #前端模板url /xxx/2 def delete(nid): print(nid) del data_dict[nid] return '删除'
@app.route('/user/<username>') @app.route('/post/<int:post_id>') @app.route('/post/<float:post_id>') @app.route('/post/<path:path>') @app.route('/login', methods=['GET', 'POST'])
4.后端获取提交的数据
get请求: nid = request.args.get('nid') post请求:pwd = request.form.get('pwd')
5.返回数据
return render_template('login.html') return redirect('/index') #是返回一个url路径 return redirect(url_for('idx')) #返回url别名 return jsonify({}) #相当于djanog 的JsonResponse return '修改'
6.模板处理
{{x}} {% for item in data_dict %} {{item.name}} {{ item.get('name') }} {{ item['name'] }} {% endfor %} #语法和python非常接近,这一点比django好
7.注释
ctrl + l {#get请求传参#}
8.session 保存用户会话信息
flask的session是保存在游览器的cookie。加密存储
from flask import Flask,session app=Flask(__name__) app.secret_key = 'xsadsagadsfg' #需要在app中设置secret_key session['xx']=zzz #就可以设置session 获取 session.get('xx')
9.装饰器实现用户认证
import functools def auth(func): @functools.wrap(func) #让参数名为调用的函数,而不是inner def inner(*args,**kwargs): username = session.get('xx') if not username: return redirect(url_for('login')) return func(*args,**kwargs) return inner @app.route('/index',endpoint='idx') @auth #装饰器上下往上执行,auth装饰器中必须有functools,他将 def index(): pass print(index.__name__) #结果为index
蓝图(blue print)
帮助我们可以将很多的业务拆分,创建多个py文件,把各个功能放在不同蓝图中,最后将蓝图注册到flask对象中。
构建目录结构
manage.py # -*- coding: utf-8 -*- from blue_print_flask import create_app app = create_app() if __name__ == '__main__': app.run()
__init__.py # -*- coding: utf-8 -*- from flask import Flask from .views.my import xmy from .views.wy import xwy def create_app(): app = Flask(__name__) app.secret_key = 'sdasfgasf' @app.route('/index') def index(): return 'index' #注册 和蓝图创建关系 app.register_blueprint(xmy,url_prefix='/web') #url_prefix 前缀 访问/web/f1 app.register_blueprint(xwy,url_prefix='/admin') return app
my.py # -*- coding: utf-8 -*- from flask import Blueprint #引入蓝图 xmy=Blueprint('my',__name__) @xmy.route('/f1') def f1(): return 'f1' @xmy.route('/f2') def f2(): return 'f2'
面试题:
django的app和flask的蓝图有什么区别?
没什么太大区别。django在settings中注册,flask在create_app()函数中注册app
http和https区别
htpp:端口80 https:端口443 http的数据是基于明文传输。 https的数据是基于密文传输。 对称加密: 客户端向服务器发送一条信息,首先客户端会采用已知的算法对信息进行加密,比如MD5或者Base64加密,接收端对加密的信息进行解密的时候需要用到密钥,中间会传递密钥,(加密和解密的密钥是同一个),密钥在传输中间是被加密的。 非对称加密:公钥,私钥。私钥在自己服务器。 “非对称加密”使用的时候有两把锁,一把叫做“私有密钥”,一把是“公开密钥”,使用非对象加密的加密方式的时候,服务器首先告诉客户端按照自己给定的公开密钥进行加密处理,客户端按照公开密钥加密以后,服务器接受到信息再通过自己的私有密钥进行解密,这样做的好处就是解密的钥匙根本就不会进行传输,因此也就避免了被挟持的风险。 证书秘钥加密: 在上面我们讲了非对称加密的缺点,其中第一个就是公钥很可能存在被挟持的情况,无法保证客户端收到的公开密钥就是服务器发行的公开密钥。此时就引出了公开密钥证书机制。数字证书认证机构是客户端与服务器都可信赖的第三方机构。
cookie和session
cookie:保存在游览器上
session:是基于cookie的一种机制,将用户会话信息保存在服务器端
token:令牌
jwt:json web token
开放封闭原则
对源码封闭 对配置开放
什么是接口?
-interface类型,java/c#语言中才有,用于约束实现了该接口的类中必须有的某些方法。 -api也可以称为一个接口,url访问,drf,restful风格
抽象方法/抽象类
具有约束的功能,让子类继承的功能。python中有abc模块实现。 但我们一般用raise NotmplementedError 来约束
flask链接数据库分为两种:
- sqlalchemy 一个orm框架
pip install flask-sqlalchemy
- sqlhelper 写原生sql
数据库连接池dbutils (SQLHelper)
参考 https://www.cnblogs.com/wupeiqi/articles/8184686.html
并发情况下 最好用数据库连接池 安装 pip3 install dbutils pip3 install pymysql
import time import pymysql import threading 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, # 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='123', database='pooldb', 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 tb1') result = cursor.fetchall() #关闭链接,连接就返回到连接池让后续线程继续使用 conn.close() func()
flask中的sqlhelper
# -*- coding: utf-8 -*- from DBUtils.PooledDB import PooledDB import pymysql class SqlHelper(object): def __init__(self): self.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, # 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='123', database='pooldb', charset='utf8') def open(self): conn = self.pool.connection() cursor = conn.cursor() return conn, cursor def close(self, cursor, conn): cursor.close() conn.close() def fetchall(self, sql, *args): '''获取所有数据''' conn, cursor = self.open() cursor.execute(sql, args) result = cursor.fetchall() self.close(cursor, conn) return result def fetchone(self, sql, *args): '''获取所有数据''' conn, cursor = self.open() cursor.execute(sql, args) result = cursor.fetchone() self.close(cursor, conn) return result db = SqlHelper() #在flask 的视图中 from sqlhelper import db #单例模式 @app.route('/index',endpoint='idx') #idx url别名 def index(): db.fetchall('select * from xx where name =%s,'zz') ‘’‘ 或者自己写 db.open() ....sql逻辑 db.cloese() ’‘’
test.py #参数的解释 def f1(sql,*args,**kwargs): print(sql,args,kwargs) a=f1('xxx','xx','xx',a=1,b=2) print(a)#xxx ('xx', 'xx') {'a': 1, 'b': 2} args返回元组,kwargs返回zi'di'a
知识点:上下文管理机制 (这里是面向对象的上下文管理,但和flask中的上下文管理无关)
# 面试题 class Foo(object): def do_something(self): print('内部执行') # 第三步 class Context: def __enter__(self): print('进入') # 第一步 return Foo() def __exit__(self, exc_type, exc_val, exc_tb): print('推出') # 第四步 with Context() as ctx: #这里的ctx 就是接受return的返回值 print('内部执行') # 第二步 ctx.do_something() #with Context() 会执行对象的__enter__方法,所以ctx就是返回的Foo()对象
跟据flask源码反推wsgi返回值
#werkzeug自己就能实现返回值 from werkzeug.serving import run_simple from werkzeug.wrappers import BaseResponse from flask.wrappers import BaseResponse def func(environ,start_response): print('请求来了') response=BaseResponse('你好') return response(environ,start_response) if __name__ == '__main__': run_simple('127.0.0.1',5000,func)
静态文件处理
Flask() 初始化参数 def __init__( self, import_name, #app名 static_url_path=None, #静态文件路径,默认为 /static static_folder="static", #静态文件名 static_host=None, host_matching=False, subdomain_matching=False, template_folder="templates", instance_path=None, instance_relative_config=False, root_path=None, )
html 推荐写法 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <img src="/static/qq.jpg" alt=""> #推荐 这样之后改static_url_path,就不用更改了。会自动找static文件夹 <img src="{{ url_for('static',filename='qq.jpg') }}" alt=""> </body> </html>
flask配置文件
- local settings 第一种
#新见一个config文件夹 里面有settings.py,localsettings.py #settings.py DB_IP = 'XX' S #导入localsettings try: from .localsettings import * expect ImportError: pass #localsettings.py 本地自己的配置,在上传git的时候不上传这个,写到.gitignore文件里 DB_IP = 'XX' #app.py from flask import Flask app=Flask(__name__) #加载配置文件 app.config.from_object('config.settings') #通过字符串导入改py文件 #取值 app.config['DB_IP']
第二种 基于类的一种
#app.py from flask import Flask app=Flask(__name__) #加载配置文件 app.config.from_object('config.settings.Probsettings') #settings.py class Probsettings(): DB_IP ='xx'
路由:
两种添加路由和视图对应关系的方法: def index(): return render_template('index.html') #添加关系 一般用这个就行 app.add_url_rule('/index','index',index,methods=["GET","POST"]) #url路径,url别名,视图名 or self.add_url_rule(rule='/index.html', endpoint="index", view_func=index, methods=["GET","POST"]) or app.add_url_rule(rule='/index.html', endpoint="index", view_func=index, methods=["GET","POST"]) app.view_functions['index'] = index #一般用这种 @app.route('/login') def index(): return render_template('index.html')
路由加载的源码流程
-将url和函数打包为rule对象 -将rule对象添加到map对象中 -将map对象加入app对象中 - app.url_map=map对象
支持这几种:
@app.route('/user/<username>') @app.route('/post/<int:post_id>') @app.route('/post/<float:post_id>') @app.route('/post/<path:path>') @app.route('/login', methods=['GET', 'POST'])
自定义路由:
from flask import Flask, views, url_for from werkzeug.routing import BaseConverter app = Flask(import_name=__name__) class RegexConverter(BaseConverter): """ 自定义URL匹配正则表达式 """ def __init__(self, map, regex): super(RegexConverter, self).__init__(map) self.regex = regex def to_python(self, value): """ 路由匹配时,匹配成功后传递给视图函数中参数的值 :param value: :return: """ return int(value) def to_url(self, value): """ 使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数 :param value: :return: """ val = super(RegexConverter, self).to_url(value) return val # 添加到flask中 app.url_map.converters['regex'] = RegexConverter @app.route('/index/<regex("\d+"):nid>') def index(nid): print(url_for('index', nid='888')) return 'Index' if __name__ == '__main__': app.run()
视图cbv:
CBV: 返回一个闭包函数view
from flask import views def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner class IndexView(views.View): methods = ['GET'] decorators = [auth, ] def dispatch_request(self): print('Index') return 'Index!' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint 或 class IndexView(views.MethodView): methods = ['GET'] decorators = [auth, ] #装饰器 def get(self): return 'Index.GET' def post(self): return 'Index.POST' app.add_url_rule('/index', view_func=IndexView.as_view(name='index')) # name=endpoint
模板:
和django类似,可以根据django中的语法学习
<!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> </head> <body> <h1>自定义函数</h1> {{ww()|safe}} #可以传函数名,前端加()执行函数 </body> </html>
from flask import Flask,render_template app = Flask(__name__) def wupeiqi(): return '<h1>Wupeiqi</h1>' @app.route('/login', methods=['GET', 'POST']) def login(): return render_template('login.html', ww=wupeiqi) app.run()
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% macro input(name, type='text', value='') %} <input type="{{ type }}" name="{{ name }}" value="{{ value }}"> {% endmacro %} {{ input('n1') }} {% include 'tp.html' %} <h1>asdf{{ v.k1}}</h1> </body> </html>
定义全局模板方法
加装饰器让函数在所有模板中都可使用(全局) @app.template_global() def func(arg): return 'xx' + arg #前端模板 {{ func("hhh") }} #返回xxhhh
@app.template.filter() def func(arg,name): return 'xx' + arg +name #前端模板 {{ "hhh"|func("sun") }} #返回xxhhhsun
注意:在蓝图中注册时,应用范围只在本蓝图中。(@xx.app_template_global())
特殊装饰器(中间件):
@app.before_request def f1(): # if request.path == '/login': #可做白名单或者是登录session验证 # return #想当return None,跳过中间件 print('f1') @app.after_request #这里必须返回reponse。和django中间件中process_response中的return response def f10(response): print('f10') return response @app.route('/index') def index(): print('index') #打印结果 f1 index f10
多个装饰器(中间件):
@app.before_request def f1(): print('f1') @app.before_request def f2(): print('f2') @app.after_request #这里必须返回reponse。和django中间件中process_response中的return response def f10(response): print('f10') return response def f11(response): print('f11') return response @app.route('/index') def index(): print('index') #打印结果 f1 f2 index f11 f10 #flask实现逻辑: 会把before_request的函数加到一个列表中for循环执行。 把after_request的函数加到列表中,进行revese反转在执行。
注意:在蓝图中定义,作用域只在本蓝图
小细节其他写法了解:
(装饰器本质,将下面紧挨着的函数当参数传入)
@app.before_request def f2(): print('f2') def f23(): print('f23') #f23=app.before_request(f23) app.before_request(f23) #这样写也可以
threading.local的作用 (flask中没有)
上面案例:如果不用threading.local,结果是都是3(最后一个线程完成的结果)。
为每个线程都会开辟一块空间,让你进行存取值。
栈和面向对象attr:
栈: list = [] list.pop() # 后进先出,可以理解装弹夹
# -*- coding:utf-8 -*- class Student: def __getattr__(self, item): return item + ' is not exits' def __setattr__(self, key, value): self.__dict__[key] = value def __getitem__(self, item): return self.__dict__[item] def __setitem__(self, key, value): self.__dict__[key] = value s = Student() print(s.name) # 调用__getattr__方法 输出'name is not exits' s.age = 1 # 调用__setattr__ 方法 print(s.age) # 输出 1 print(s['age']) # 调用 __getitem__方法 输出1 s['name'] = 'tom' # 调用 __setitem__ 方法
线程的唯一标识:
import threading from threading import get_ident def task(): ident =get_ident() #获取每一个线程的标识 print(ident) for i in range(20): t=threading.Thread(target=task) t.start()
自定义threading local :
每个线程都会维护一个地方去取存值。
class Local(object): def __init__(self): object.__setattr__(self, 'storage', {}) #self.storage={} 防止递归 def __setattr__(self, key, value): ident = threading.get_ident() if ident in self.storage: self.storage[ident][key] = value else: self.storage[ident] = {key:value} def __getattr__(self, item): ident = threading.get_ident() if ident not in self.storage: return #返回None return self.storage[ident].get(item) local=Local() def task(arg): local.x1 = arg #会调用setattr赋值 print(local.x1) #调用getattr for i in range(5): t=threading.Thread(target=task,args=(i,)) t.start() #结果 0 1 2 3 4
高级点的threading local
数据维护成一个栈 #列表 [ ]先进后出
import threading storage = { 111:{'x1':[1,]}, 222:{'x1':[2,]}, } class Local(object): def __init__(self): object.__setattr__(self, 'storage', {}) # self.storage={} 防止递归 def __setattr__(self, key, value): ident = threading.get_ident() if ident in self.storage: self.storage[ident][key].append(value) else: self.storage[ident] = {key:[value,]} def __getattr__(self, item): ident = threading.get_ident() if ident not in self.storage: return # 返回None return self.storage[ident][item][-1] #取最后一个 栈顶 local = Local() def task(arg): local.x1 = arg # 会调用setattr赋值 print(local.x1) # 调用getattr for i in range(5): t = threading.Thread(target=task, args=(i,)) t.start()
flask源码源于local的实现(local,localstack)
local类:
from flask import globals _request_ctx_stack = LocalStack() #从这里面找 class Local(object): __slots__ = ("__storage__", "__ident_func__") def __init__(self): object.__setattr__(self, "__storage__", {}) object.__setattr__(self, "__ident_func__", get_ident) def __iter__(self): return iter(self.__storage__.items()) def __call__(self, proxy): """Create a proxy for a name.""" return LocalProxy(self, proxy) def __release_local__(self): self.__storage__.pop(self.__ident_func__(), None) def __getattr__(self, name): try: return self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name) def __setattr__(self, name, value): ident = self.__ident_func__() storage = self.__storage__ try: storage[ident][name] = value except KeyError: storage[ident] = {name: value} def __delattr__(self, name): try: del self.__storage__[self.__ident_func__()][name] except KeyError: raise AttributeError(name)
localstack类:
class LocalStack(object): """This class works similar to a :class:`Local` but keeps a stack of objects instead. This is best explained with an example:: >>> ls = LocalStack() >>> ls.push(42) >>> ls.top 42 >>> ls.push(23) >>> ls.top 23 >>> ls.pop() 23 >>> ls.top 42 .. versionadded:: 0.6.1 """ def __init__(self): self._local = Local() def __release_local__(self): self._local.__release_local__() def _get__ident_func__(self): return self._local.__ident_func__ def _set__ident_func__(self, value): object.__setattr__(self._local, "__ident_func__", value) __ident_func__ = property(_get__ident_func__, _set__ident_func__) del _get__ident_func__, _set__ident_func__ def __call__(self): def _lookup(): rv = self.top if rv is None: raise RuntimeError("object unbound") return rv return LocalProxy(_lookup) def push(self, obj): """Pushes a new item to the stack""" rv = getattr(self._local, "stack", None) if rv is None: self._local.stack = rv = [] rv.append(obj) return rv def pop(self): """Removes the topmost item from the stack, will return the old value or `None` if the stack was already empty. """ stack = getattr(self._local, "stack", None) if stack is None: return None elif len(stack) == 1: release_local(self._local) return stack[-1] else: return stack.pop() @property def top(self): """The topmost item on the stack. If the stack is empty, `None` is returned. """ try: return self._local.stack[-1] except (AttributeError, IndexError): return None
总结
在flask中有一个Local类,他和threading.local的功能一样,为每个线程开辟空间进行存取值。
他们两个的内部实现机制,内部维护一个字典,以线程(协程id)为key,进行数据隔离 如:
__storage__={ 1211:{'k1':123}, } obj =Local() obj.k1 =123 #找到他自己的线程id
在flask中有一个LocalStack类,他内部会依赖local对象,local对象负责存储数据,localstack对象用于将local中的值维护成一个栈
__storage__={ 1211:{'stack':[]}, } obj = LocalStack() obj.push('k1') #往列表中添加值 obj.top #取栈顶的值 [-1] obj.pop() #pop值,当列表中只有一个值时,会将这个栈销毁del
在flask源码中只有两个localstack对象(单例模式实现)
from flask import globals _request_ctx_stack = LocalStack() #不管导入多少次,都只有这一个_request_ctx_stack对象 _app_ctx_stack = LocalStack()
一个请求过来,分别_request_ctx_stack.push(),_app_ctx_stack.push() __storage__={ 1111:{'stack':[RequestContext(request,sesion),]}, 1122:{'stack':[RequestContext(request,sesion),]}, } _request_ctx_stack = LocalStack() __storage__={ 1111:{'stack':[AppContext(app,g),]}, 1122:{'stack':[AppContext(app,g),]}, } _app_ctx_stack = LocalStack()
flask上下文管理
- 请求上下文管理 RequestContext
- 应用上下文管理(app上下文管理) AppContext
flask源码实现流程梗概:
SQLHelper ,通过上下文管理、threading.local 实现
# -*- coding: utf-8 -*- from DBUtils.PooledDB import PooledDB import pymysql import threading ''' storage={ 111:{'stack':[]} 111:{'stack':[]}, } ''' class SqlHelper(object): def __init__(self): self.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, # 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='123', database='pooldb', charset='utf8') self.local = threading.local() def open(self): conn = self.pool.connection() cursor = conn.cursor() return conn, cursor def close(self, cursor, conn): cursor.close() conn.close() def fetchall(self, sql, *args): '''获取所有数据''' conn, cursor = self.open() cursor.execute(sql, args) result = cursor.fetchall() self.close(cursor, conn) return result def fetchone(self, sql, *args): '''获取单个数据''' conn, cursor = self.open() cursor.execute(sql, args) result = cursor.fetchone() self.close(cursor, conn) return result def __enter__(self): conn, cursor = self.open() rv = getattr(self.local, 'stack', None) if not rv: self.local.stack = [(conn, cursor), ] else: rv.append((conn, cursor)) self.local.stack = rv return cursor def __exit__(self, exc_type, exc_val, exc_tb): rv = getattr(self.local, 'stack', None) if not rv: return conn, cursor = self.local.stack.pop() cursor.close() conn.close() db = SqlHelper() with db as cursor: #with db 执行__ernter__, cursor接受返回值 cursor.excute('select * from xx')
flask源码分析:
项目启动:
- 实例化Flask对象
app=Flask(__name__)
1.对app对象封装一些初始化的值 static_url_path=None, static_folder="static", template_folder="templates", 2.添加静态文件的路由static self.add_url_rule( self.static_url_path + "/<path:filename>", endpoint="static", host=static_host, view_func=self.send_static_file, ) 3.实例化了url_map对象,以后在map对象中放【/index、函数的对应关系】 class Flask(object): url_rule_class = Rule url_map_class = Map def __init__(self,...): static_url_path=None, static_folder="static", template_folder="templates", self.view_functions = {} self.url_map = self.url_map_class() #Rule() app=Flask() app.view_functions app.url_rule_class
- 加载配置文件
from flask import Flask app=Flask(__name__,static_url_path='/xx') app.config.from_object('xx.xx') #app.config=Config #Config对象.from_object('xx.xx') #app.config
1.读取配置文件中的所有键值对,并将键值对全都放到Config对象,Config继承dict,是一个字典 self.config = self.make_config(instance_relative_config) 2.把包含所有配置文件的Config对象,赋值给app.config
-
添加路由映射
1.将url=/index,和methods=[GET,POST],和endpoint='index',封装到Rule对象中 2.将Rule对象添加到self.url_map中 3.把endpoint和函数的对应关系放到app.view_functions -
截止目前 app对象中
app.config app.url_map app.view_functions -
运行flask
1.内部调用werkzeug的run_simple,内部创建socket,监听IP和端口,等待用户请求的到来 2.一但有请求到来,执行 app对象的__call__方法 class Flask(object): def __call__(self,envion,start_response): pass def run(self): run_simple(host, port, self, **options) if __name__ == '__main__': app.run()
用户请求到来:
-
创建ctx=RequestContext()对象,其内部封装了Request对象和session数据
-
创建app.ctx=AppContext()对象,内部封装了App和g
-
self.request_context(environ) -
然后ctx.push触发将ctx和app_ctx分别通过自己的LocalStack对象放入Local中,Local的本质是以线程id为key,已{‘stack’:[] } 为value的字典
-
ctx.push()
注意以后取request、session、app、g的时候都要去local中获取
ctx.request ctx.session -
-
执行所有的before_request函数 (before_first_request只在第一次执行)
-
self.try_trigger_before_first_request_functions() rv = self.preprocess_request()
-
-
执行视图函数
-
rv = self.dispatch_request()
-
-
执行所有的after_request (并将session加密放到cookie中,游览器存储cookie,下一次来带着cookie)
self.finalize_request(rv) -
销毁ctx的app.ctx (列表为1的时候pop)
-
ctx.auto_pop(error)
-
了解源码流程后,上下文封装的值的使用:
from flask import request,session,current_app,g #都是LocalProxy对象 session['x']=123 ==>本质是调用LocalProxy对象中的setitem方法 ctx.session['x']=123 request.method ==>#调用LocalProxy对象中的getattr方法 ctx.request.method current_app.config ==>app_ctx.config g.x1 ==>app_ctx.g.x1
# context locals _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")) #这里的session是LocalProxy对象 g = LocalProxy(partial(_lookup_app_object, "g")) #### def _lookup_req_object(name): top = _request_ctx_stack.top #ctx if top is None: raise RuntimeError(_request_ctx_err_msg) return getattr(top, name) #返回ctx.request
偏函数 functools.partial
#偏函数示例 def func(f1,f2): print(f1) print(f2) func1 = functools.partial(func,'x1',) #之后使用函数这个第一个参数就不用传了 func1('x2') #直接传第二个参数即可
LocalProxy 一个代理类
request = LocalProxy(partial(_lookup_req_object, "request")) #相当于LocalProxy(一个函数), 在你下次调用这个对象的时候,对这个函数进行处理(赋值或者取值session['xx']='xxx',执行对象的__setitem__,__getitem__)
class LocalProxy(object): __slots__ = ("__local", "__dict__", "__name__", "__wrapped__") def __init__(self, local, name=None): object.__setattr__(self, "_LocalProxy__local", local) # #self._LocalProxy__local=local # 强行取私有对象 _类+变量 #相当于self.__local=local #私有 双下划綫+变量 def _get_current_object(self): if not hasattr(self.__local, "__release_local__"): return self.__local() #相当于self._LocalProxy__local() #执行函数local,local即为传过来的函数名加括号执行。返回 ctx.session def __setitem__(self, key, value): self._get_current_object()[key] = value #赋值 ctx.session[key]=value
g的应用
在一次请求周期中,可以在g中设置一些值,在本次请求周期中都可以读取。
相当于是一次请求周期中的全局变量。 (在django中实在request对象中设置值,flask中单独有g来做这一件事)
from flask import Flask,g @app.before_request def f1(): print('f1') g.x1=123 @app.route('/') def hello_world(): print(g.x1) return 'Hello World!'
源码流程总结:
-
第一阶段:启动flask程序,加载特殊装饰器、路由、都封装到app=Flask()对象
-
第二阶段:请求到来
- 创建上下文对象:应用上下文 请求上下文
- 执行before、视图、after
- 销毁上下文对象
问题:
在flask中Local对象中为什么要通过线程id进行区分?
因为在flask中可以开启多进程的模式(多用户),当开启多线程模式进行处理用户请求时,需要将线程之间的数据进行隔离, 以防止数据混乱。
在flask的Local对象中为什么要维持成一个栈?
{ 111:{'stack':[ctx,]}, 112:{'stack':[ctx,]}, #另一个用户 } { 111:{'stack':[app_ctx,]}, 112:{'stack':[app_ctx,]}, } 在web运行时,栈永远只有一个对象,一个用户的请求时一个一个来的。
在写离线脚本的时候,才会用到栈中放入多个对象,多个上下文嵌套(创建多个app)。 #蓝图中__init__.py def create_app(): app = Flask(__name__) app.secret_key = 'sdasfgasf' @app.route('/index') def index(): return 'index' #注册 和蓝图创建关系 app.register_blueprint(xmy,url_prefix='/web') #url_prefix 前缀 访问/web/f1 app.register_blueprint(xwy,url_prefix='/admin') return app #manage.py from blue_print_flask import create_app from flask import current_app,g app1 = create_app() with app1.app_context(): print(current_app.config) #app1.config #Appcontent对象(app,g)-->local对象 app2=create_app() with app2.app_context(): print(current_app.config) #app2.config 注意:在flask中很少出现嵌套的脚本。
信号:
信号,是在flask框架中预留的钩子,让我们自定义一些操作。
pip install blinker
根据flask项目的请求流程来进行设置扩展点
-
中间件
from flask import Flask, flash, redirect, render_template, request app = Flask(__name__) app.secret_key = 'some_secret' @app.route('/') def index1(): return render_template('index.html') @app.route('/set') def index2(): v = request.args.get('p') flash(v) return 'ok' class MiddleWare: def __init__(self,wsgi_app): self.wsgi_app = wsgi_app def __call__(self, *args, **kwargs): #这里可以加扩展点 return self.wsgi_app(*args, **kwargs) if __name__ == "__main__": app.wsgi_app = MiddleWare(app.wsgi_app) app.run(port=9999) -
当app_ctx被push到local栈中,会触发appcontent_pushed信号,之前注册到这个信号中的方法,就会被执行。
class AppContext(object): def push(self): self._refcnt += 1 if hasattr(sys, "exc_clear"): sys.exc_clear() _app_ctx_stack.push(self) appcontext_pushed.send(self.app) #这里信号可扩展 from flask import signals @signals.appcontext_pushed.connect def f1(arg): print('信号f1被触发',arg) -
before_first_request
@app.before_first_request def f(): pass -
request_started信号
request_started.send(self) @signals.request_started.connect def f3(arg): print(arg) #app -
url_value_processor
@app.url_value_preprocessor #在before_request之前执行,没有返回值。 def f5(endpoint,args): print('f5') @app.before_request def f6(): pass -
before_request
@app.before_request def f6(): pass -
视图函数
-
render_template
#源码 def _render(template, context, app): """Renders the template and fires the signal""" before_render_template.send(app, template=template, context=context) rv = template.render(context) template_rendered.send(app, template=template, context=context) return rv #扩展点 from flask import Flask,render_template,signals @signals.before_render_template def f1(app, template, context): print('f1') @signals.template_rendered def f2(app, template, context): print('f2') -
after_request
@app.after_request -
request_finished
@signals.request_finished.connect def f6(app,response): pass -
got_request_exception
@signals.got_request_exception def f11(app, exception): pass -
teardown_request
@在finally的auto_pop中,总会执行的 def auto_pop(self, exc): ... self.pop(exc) ... self.app.do_teardown_request(exc) @app.teardown_request def f10(exc): pass -
信号 request_tearing_down
request_tearing_down.send(self, exc=exc) @signals.request_tearing_down def f11(app,exc): pass -
appcontext_popped
@signals.appcontext_popped.connect def f11(app): pass
总结:一共10个信号signals
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") appcontext_popped = _signals.signal("appcontext-popped") message_flashed = _signals.signal("message-flashed")
message(flash):
@signals.message_flashed
message是一个基于Session实现的用于保存数据的集合,其特点是:使用一次就删除。
rom flask import Flask, flash, redirect, render_template, request, get_flashed_messages app = Flask(__name__) app.secret_key = 'some_secret' @app.route('/') def index1(): messages = get_flashed_messages() print(messages) return "Index1" @app.route('/set') def index2(): v = request.args.get('p') flash(v) return 'ok' if __name__ == "__main__": app.run()
flask-script
flask的组件,用于运行flask程序。
pip install flask-script
其他:
安装 flask-migrate /flask-sqlalchemy 就支持pythoh manage.py migrate
蓝图:
- 分功能蓝图
- 分结构蓝图 (我们称之为大蓝图)
Flask插件:
- WTForms
- SQLAchemy
- 等... http://flask.pocoo.org/extensions/
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)