文章目录
Flask
flask和django的区别
-
Django功能大而全,Flask只包含基本的配置;
-
Django有模板,表单,路由,认证,基本的数据库管理等等内建功能。
与之相反,Flask只是一个内核,默认依赖于两个外部库: Jinja2 模板引擎和 Werkzeug WSGI 工具集,其他很多功能都是以扩展的形式进行嵌入使用。
-
Flask 比 Django 更灵活 ,
-
flask非常适用于开发API接口
一、flask配置文件
-
导入类的配置文件(导入类的路径)
-
方法
-
首先创建一个PY文件,写一个类,类中写一些配置信息的静态字段
-
app.config.from_object("python类或类的路径")
def from_object(self, obj): if isinstance(obj, string_types): # 判断是否是字符串 obj = import_string(obj) # 利用import_module封装的函数拿到类 for key in dir(obj): # 遍历自己写的配置类 if key.isupper(): # 判断类的属性是不是大写 self[key] = getattr(obj, key) # 保存进配置信息
-
-
原理
-
利用
importlib
模块中的import_module
函数:o = importlib.import_module("aa.bb")
-
利用
getattr('var1')
获取类中或者模块中的属性 -
获取到类之后遍历类的属性,在利用
getattr()
获取所有大写的类的静态变量,写进config配置文件字典
-
-
二、路由系统
-
方式一
@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']) # 路由中的数据类型 DEFAULT_CONVERTERS = { 'default': UnicodeConverter, 'string': UnicodeConverter, 'any': AnyConverter, 'path': PathConverter, 'int': IntegerConverter, 'float': FloatConverter, 'uuid': UUIDConverter, }
-
方式二
def index(): pass app.add_url_rule('/', 'index', index) ''' add_url_rule(self,rule,endpoint=None,view_func=None, provide_automatic_options=None,**options) rule-----------------------------URL规则为字符串 endpoint-------------------------字符串,端点名字,不设置默认为函数名 view_func------------------------函数名 provide_automatic_options-------- 未设置provide_automatic_options,则进入默认的OPTIONS请求回应,否则请求endpoint匹配的函数执行,并返回内容 options -------------------------请求方式,options=['GET','POST'] '''
-
规则
属性 说明 示例 Truestrict_slashes False不严格,True严格,默认=None @app.route('/index',strict_slashes=False)
redirect_to 重定向到指定地址 @app.route('/index/<int:nid>',redirect_to='/home/<nid>'
@app.route('/index/<int:nid>', redirect_to=func)
subdomain 子域名访问(必须配置’SERVER_NAME’) app.config['SERVER_NAME'] = 'wupeiqi.com:5000'
@app.route("/", subdomain="admin")
子域名带正则 @app.route("/dynamic", subdomain="<username>")
def username_index(username)
自定义正则路由
三、蓝图
- 好处:
- 目录结构的划分
- 可以单独给一个蓝图加前缀
- 应用特殊装饰器-before_request
创建蓝图
-
创建一个和项目名相同的文件夹 (” crm“ ------文件夹)
-
在新文件夹(” crm“)内创建
__init__.py
文件,并在里面实例化from flask import Flask def create_app(): app = Flask(__name__) # 实例化flask return app
-
创建主程序文件
xxx.py
from crm import create_app # 导入自己写的实例化flask文件
app = create_app()
if __name__ == '__main__':
app.run()
-
在新文件夹(” crm“)中创建视图文件夹 views
-
在视图文件夹 views下可创建多个视图python文件
例:
account.py
from flask import Blueprint # 导入蓝图 ac = Blueprint('ac',__name__) # 创建蓝图对象 @ac.route('/login') def login(): return 'login' @ac.route('/login') def login(): return 'login'
-
在crm文件夹下的
__init__.py文件
创建视图文件与主程序的关系from flask import Flask from .views.account import ac # 导入视图文件 def create_app(): app = Flask(__name__) app.register_blurprint(ac) # 将蓝图注册到app return app
-
创建静态文件夹
static
和模板文件夹templates
crm // 工程文件夹 |-- crm // 蓝图文件夹 | |-- static // 蓝图-静态文件夹 | |-- templates // 蓝图-模板文件夹 | |-- views // 蓝图-视图文件夹 | |-- account.py // 蓝图-视图文件 | |-- __init__.py // 初始化 |-- manage.py // 主程序
自定义蓝图的static文件夹和trmplates文件夹
-
在
蓝图-视图文件
中创建蓝图对象时,给出静态文件夹名字ac = Blueprint('ac', __name__, template_folder='xxxx',static_url_path='xxx')
-
注意:
app在寻找模板文件和静态文件时,先从最外层的templates文件找,找不到才到自定义的
template_folder
里面找
为某一个蓝图内所有URL路由访问地址加前缀
-
在
__init__.py
文件中app.register_blurprint(ac, url_prefix='/xxxx')
before_request–访问URL先触发
-
直接在app下装饰函数(在主程序文件中)
这样任意一个URL访问进来都会触发这个被装饰的函数
@app.before_request def f1(): print('app.before_request')
-
在蓝图对象下装饰函数(在蓝图文件中)
例:有一个
ac
蓝图这样只有当前蓝图下的URL访问进来时才会触发这个函数
@ac.before_request def f1(): print('ac.before_request')
四、子域名
-
蓝图子域名:xxx = Blueprint(‘account’, name, subdomain=‘admin’)
-
前提需要给配置SERVER_NAME: app.config[‘SERVER_NAME’] = ‘abc.com:5000’
一般固定子域名
一般用于数量比较少的子域名,一个模块对应一个子域名。先看下面一个例子:
# modules.py
from flask import Blueprint # 蓝图
public = Blueprint('public', __name__)
@public.route('/')
def home():
return 'hello flask'
# app.py
app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
from modules import public
app.register_blueprint(public, subdomain='public')
现在可以通过public.example.com/
来访问public
模块了。
通配符子域
通配符子域,即通过一个模块来匹配很多个子域名。比如某些网站提供的个性化域名功能,就是这种形式。
# modules.py
from flask import Blueprint
public = Blueprint('public', __name__)
@member.route('/')
def home():
return g.subdomain
# app.py
app = Flask(__name__)
app.config['SERVER_NAME'] = 'example.com'
from modules import public
app.register_blueprint(public, subdomain='<subdomain>')
这里的subdomain
使用了动态参数<subdomain>
(路由中的URL变量也是这种方式)。我们可以用这个参数在请求回调函数之前利用的组合的url处理器来获取相关的用户。这样我们就可以通过*.example.com
的形式来访问member
模块。
*五、 threading.local–(和flask没有关系)
-
local为每个线程的数据开辟一个独立的空间,使得线程对自己空间中的数据进行操作(数据隔离)
import threading from threading import local import time obj = local() def task(i): obj.xxx = i time.sleep(1) print(obj.xxx,i) for i in range(10): t = threading.Thread(target = task,args=(i,)) t.start()
-
获取线程的唯一标记
import threading import time def task(i): print(threading.get_ident(),i) for i in range(10): t = threading.Thread(target = task,args=(i,)) t.start()
-
自定义类似local的函数—Local 为每个协程或者线程开辟独立空间
import threading try: import greenlet # 导入协程模块 get_ident = greenlet.getcurrent # 获得协程唯一标识函数 except: get_ident = threading.get_ident # 获得线程唯一标识函数 class Local(object): DIC = {} def __getattr__(self,item): ident = get_ident() if ident in self.DIC: return self.DIC[ident].get(item) else: return None def __setattr__(self,key,balue): ident = get_ident() if ident in self.DIC: self.DIC[ident][key] = value else: self.DIC[ident] = {key:value}
* 六、请求上下文管理(源码剖析)
session
flask中 session的流程详解
-
客户端的请求进来时,会调用app.wsgi_app():
-
此时,会生成一个ctx,其本质是一个RequestContext对象:
-
在RequestContext 对象中定义了session,且初值为None。
-
接着继续看wsgi_app函数中,ctx.push()函数,会返回一个session对象保存在ctx中(详解下面代码)
-
源码:
# ctx.push()中session的相关代码 if self.session is None: #session_interface = SecureCookieSessionInterface() session_interface = self.app.session_interface self.session = session_interface.open_session(self.app, self.request) # open函数在下面 if self.session is None: self.session = session_interface.make_null_session(self.app)
def open_session(self, app, request): # 获取session签名的算法 s = self.get_signing_serializer(app) # 如果为空 直接返回None if s is None: return None # session_cookie_name 是配置信息中的SESSION名字,获取request中的cookies val = request.cookies.get(app.session_cookie_name) # 如果val为空,即request.cookies为空 if not val: # session_class = SecureCookieSession # 看其继承关系,其实就是一个特殊的字典。到此我们知道了session就是一个特殊的字典 # 并保存在ctx中 return self.session_class() max_age = total_seconds(app.permanent_session_lifetime) try: data = s.loads(val, max_age=max_age) return self.session_class(data) except BadSignature: return self.session_class()
-
当请求第二次到来时,与第一次的不同就在open_session()那个val判断处,此时cookies不为空, 获取cookie的有效时长,如果cookie依然有效,通过与写入时同样的签名算法将cookie中的值解密出来并写入字典并返回中,若cookie已经失效,则仍然返回’空字典’。
request
flask中 request的流程详解(和session一样)
-
客户端的请求进来时,会调用
app.__call__
中app.wsgi_app()
: -
此时,会生成一个ctx,其本质是一个RequestContext对象(ctx中封装了session和request对象)
-
在RequestContext 对象中定义了
request = app.request_class(environ)
environ包含所有的请求数据 -
生成ctx后,ctx对象调用了push函数,push函数中有
_request_ctx_stack.push(self)
,将ctx对象存进_request_ctx_stack
中-
_request_ctx_stack
---->全局变量,是一个LocalStack()
类,下面有讲到这个类的原理,这里略过就可以 -
_request_ctx_stack
对象中有__storge__={id1:{stack:[ctx对象,]} }
(看完下面就明白了) -
这时候可以直接试验一下
from falsk.globals import _request_ctx_stack # 在试图函数中可以直接调用_request_ctx_stack.top
-
flask请求流程图
每个请求都流一遍,多线程协程中每个‘程’中都创建新的对象和属性包括(app, ctx, LocalStack,Local)
上下文原理
线程(协程)的值相互隔离(独立空间)。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
local_values = threading.local() # 为每个线程开辟空间,类似一个大字典,获取每个线程的唯一标识,每个标识作为一个ID ,然后{ID1:{}, ID2:{}, ID3:{}}
def func(num):
local_values.name = num
import time
time.sleep(1)
print(local_values.name, threading.current_thread().name)
for i in range(20):
th = threading.Thread(target=func, args=(i,), name='线程%s' % i)
th.start()
自定义线程(协程)的Local类
模拟栈的操作
-
创建一个类
-
创建一个字典
-
获取到的每个协程(线程)的唯一标记为字典内的键,创建一个新的自定当作独立空间
{id1:{}, id2:{}, id3:{}}
# 优先按照协程,次要线程
try:
from greenlet import greenlet as get_ident # 获取协程的唯一标记
except:
from threading import get_ident # 获取线程的唯一标记
class Local(object):
def __init__(self):
# 创建一个 storage = {},使用父类创建,如果不用父类,那么会调用自己定义的__setattr__函数,会报错
object.__setattr__(self,'storage',{})
def __setattr__(self, key, value):
ident = get_ident()
try:
self.storage[ident][key] = value
except KeyError:
self.storage[ident] = {key:value}
def __getattr__(self, item):
ident = get_ident()
try:
return self.storage[ident][item]
except KeyError:
print('None')
Flask上下文管理源码剖析
Local类
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 优先按照协程,次要线程
try:
from greenlet import greenlet as get_ident # 获取协程的唯一标记
except:
from threading import get_ident # 获取线程的唯一标记
from threading import local
# 清除协程(线程)中所有的变量
def release_local(local):
local.__release_local__()
# 定义一个存储协程(线程)空间的类
class Local(object):
__slots__ = ('__storage__', '__ident_func__') # 设置类只有这两个变量
def __init__(self):
object.__setattr__(self, '__storage__', {}) # self.__storage__ = {}
object.__setattr__(self, '__ident_func__', get_ident) # self.__ident_func__ = get_ident
# 清除协程(线程)中的变量
def __release_local__(self):
# 提取当前对象的 {id1:{}, id2{}} 的 idx 。如果没有则返回None
self.__storage__.pop(self.__ident_func__(), None)
# "." 触发
def __getattr__(self, name):
try:
# 返回当前协程(线程)唯一标识的字典的[name]
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
# obj.name=value触发
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)
if __name__ == '__main__':
obj = Local()
obj.stack = []
obj.stack.append('老王')
obj.stack.append('老李')
print(obj.stack) # ['老王','老李']
'''
操作起来太麻烦,看下面
'''
方便操作Local的类
# 定义方便操作Local的类
class LocalStack(object):
def __init__(self):
# 实例Local()
self._local = Local()
# 清除协程(线程)中的变量
def __release_local__(self):
self._local.__release_local__()
# 模拟栈操作,推入
def push(self, obj):
# 拿到Local对象中的stack属性,如果没有则返回None
rv = getattr(self._local, 'stack', None)
# 如果没找到,说明第一次进入,则创建一个列表用来模拟栈的进出
if rv is None:
self._local.stack = rv = []
# 将obj存进列表最后
rv.append(obj)
return rv
# 模拟栈操作,取出
def pop(self):
# 堆栈中删除最上面的项,也就是删除列表最后一项,并取出。如果堆栈已经为空,则为旧值或“None”。
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
if __name__ == '__main__':
obj = LocalStack()
obj.push('老王')
obj.push('老李')
print(obj.top)
print(obj.pop())
拥有了这个类,使操作Local类更快捷,更方便。
但是没有这个类,也可以操作Local类,只是比较复杂
session和requste在Flask中的原理
-
在上面flask中session中讲到RequestContext的原理,就是保存有session和request的一个类,
这里示例创建一个RequestContext类
class RequestContext(object): def __init__(self): self.session = 'xxxxxxxxxxx' self.request = 'ooooooooooo' ctx = RequestContext() xxx = LocalStack() xxx.push(ctx) # { 协程ID1:{stack:[ctx对象, ]} } obj = xxx.top # obj = ctx print(obj.request) # 'ooooooooooo' print(obj.session) # 'xxxxxxxxxxx'
-
定义一个自动取到上面的
obj
的函数,变量名和函数名参照Flask
源码def _lookup_req_object(name): top = _request_ctx_stack.top if top is None: raise RuntimeError(_request_ctx_err_msg) # 返回top对象中的name属性 return getattr(top, name)
-
偏函数
from functools import partial # 详情请百度 #_lookup_req_object('request') request = partial(_lookup_req_object,'request') #_lookup_req_object('session') session = partial(_lookup_req_object,'session') print(request()) # 返回request信息 print(session())
flask-session
pip3 install falsk-session
from flask_session import Session
老版本的:from flask.ext.session import Session
Session(app)
七、其他
执行父类的方法
class Foo(object):
def func(self):
print('Foo.func')
class Bar(object):
def func(self):
print('Bar.func')
class F1(Foo,Bar):
pass
f = F1()
f.func() # print('Foo.func')
print(F1.__mro__)
'''
(<class '__main__.F1'>,
<class '__main__.Foo'>,
<class '__main__.Bar'>,
<class 'object'>)
'''
面向对象中特殊的方法
def __getattr__(self,item)#---------------> 使用 (.)点 的时候触发,item为点后面的值
def __setattr__(self,key,value)#----------> obj.xx=123 时,key=xx,value=123
八、Flask数据库
暂无!等待更新