前情概要
1.flask的基本使用
- 配置
- 路由
- 视图
- 请求与响应相关
- 模板
2.flask基于装饰器实现的路由
- 基本操作
- functools
- 带参数的装饰器
- 源码剖析
3.flask-基于源码剖析session&特殊装饰器原理
一.历史回顾
1.装饰器原理
def wapper(func):
def inner(*args,**kwargs):
print("执行装饰器逻辑")
return func(*args,**kwargs)
return inner
"""
在程序执行从上到下加载,还未执行的时候 先执行如下两个步骤
1. 立即执行wapper函数,并将下面装饰的函数当做参数传递
2. 将wapper函数返回值获取,在index赋值
index = inner函数
"""
@wapper
def index():
print('函数内容')
# 实际执行的 inner函数,inner函数内部调用原函数
index()
2.functools
import functools
def wapper(func):
@functools.wraps(func)
def inner(*args,**kwargs):
return func(*args,**kwargs)
return inner
@wapper
def index():
print('index')
@wapper
def order():
print("order")
"""
默认不加functools会直接返回当前函数装饰器里inner的函数名字
"""
print(index.__name__)
print(order.__name__)
"""
返回值
index
order
"""
3.面向对象封装
"""
面向对象封装
"""
#将一些变量封装到一个类里进行统一调用
class Foo(object):
def __init__(self,age,name):
self.age = age
self.name = name
def get_info(self):
return self.name,self.age
def __call__(self, *args, **kwargs):
return self.name
class Bar(object):
def __init__(self,counter):
self.counter = counter
self.obj = Foo('18','guest') #这个也算是面向对象的组合,将用户封装到一个类里
b1 = Bar(1)
print(b1.obj.get_info())
print(b1.obj()) #对象加()执行对象所在类的__call__方法
4.加()会有几种表现形式-函数,类,方法,对象
def f1():
print('f1')
class F2(object):
pass
class F3(object):
def __init__(self):
pass
def ff3(self):
print('ff3')
class F4(object):
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print('f4')
def func(arg):
"""
由于arg在函数中加括号,所以他只有4中表现形式:
- 函数
- 类
- 方法
- 对象
:param arg:
:return:
"""
arg()
# 1. 函数,内部执行函数
func(f1)
# 2. 类,内部执行__init__方法
func(F2)
# 3. 方法,obj.ff3,执行方法
obj1 = F3()
func(obj1.ff3)
# F3.ff3(F3)类+方法的时候 方法就不是方法了而是函数 需要自行将self加进去
# 4. 对象
obj2 = F4()
func(obj2)
5.函数和方法的区别
from types import MethodType,FunctionType
class F3(object):
def __init__(self):
pass
def ff3(self):
print('ff3')
# v1 = isinstance(F3.ff3,MethodType)
# v2 = isinstance(F3.ff3,FunctionType)
# print(v1,v2) # False,True
obj = F3()
v1 = isinstance(obj.ff3,MethodType)
v2 = isinstance(obj.ff3,FunctionType)
print(v1,v2) # True False
二、框架的本质
1.基于socket实现
import socket
def main():
# 创建老师
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('localhost', 8000))
sock.listen(5) #队列长度
while True:
# 老师等待 用户请求的到来
connection, address = sock.accept()
# 获取发送的内容:x有没有女朋友?
# 获取发送的内容:xx 有没有女朋友?
# 获取发送的内容:xxx有没有女朋友?
# 获取发送的内容:xxxx有没有女朋友?
buf = connection.recv(1024)
# 根据请求URL的不同:
# 回答:没有
connection.send(b"HTTP/1.1 200 OK\r\n\r\n")
connection.send(b"No No No")
# 关闭连接
connection.close()
if __name__ == '__main__':
main()
2.flask依赖werkzeug,django依赖wsgiref
"""
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
@Request.application
def hello(request):
return Response('Hello World!')
if __name__ == '__main__':
# 当请求打来之后,自动执行:hello()
run_simple('localhost', 4000, hello)
"""
from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple
class Foo(object):
def __call__(self, *args, **kwargs):
return Response('Hello World!')
if __name__ == '__main__':
# 当请求打来之后,自动执行:hello()
obj = Foo()
run_simple('localhost', 4000, obj)
3.快速使用flask搭建web
from flask import Flask
# 1. 实例化Flask对象
app = Flask('xxxx')
"""
1. 执行 app.route('/index')并获取返回值 xx
2.
@xx
def index():
return 'Hello World'
3. 执行 index = xx(index)
本质:
{
'/index': index
}
"""
@app.route('/index')
def index():
return 'Hello World'
if __name__ == '__main__':
app.run()
三、Flask快速使用
import functools
from flask import Flask,render_template,request,redirect,session
app = Flask('xxxx',template_folder="templates")
app.secret_key = 'as923lrjks9d8fwlkxlduf'
def auth(func):
@functools.wraps(func) #为了添加路由的时候函数名为被装饰的函数名
def inner(*args,**kwargs):
user_info = session.get('user_info')
if not user_info:
return redirect('/login')
return func(*args,**kwargs)
return inner
"""
{
/order: inner函数, name: order
/index: inner函数, name: index
}
"""
@app.route('/order',methods=['GET']) #认证的装饰器要放路由下面 先加载app.route
@auth
def order():
user_info = session.get('user_info')
if not user_info:
return redirect('/login')
return render_template('index.html')
@app.route('/index',methods=['GET'])
@auth
def index():
return render_template('index.html')
@app.route('/login',methods=['GET','POST'])
def login():
if request.method == "GET":
return render_template('login.html')
else:
user = request.form.get('user')
pwd = request.form.get('pwd')
if user == 'alex' and pwd == '123':
session['user_info'] = user
return redirect('/index')
# return render_template('login.html',msg = "用户名或密码错误",x = 123)
return render_template('login.html',**{'msg':'用户名或密码错误'})
@app.route('/logout',methods=['GET'])
def logout():
del session['user_info']
return redirect('/login')
if __name__ == '__main__':
app.run()
四、导入配置文件
1.app.py
#app.py
from flask import Flask
# 配置:模板/静态文件
app = Flask('xxxx',template_folder="templates")
# 配置:secret_key
app.secret_key = 'as923lrjks9d8fwlkxlduf'
# 导入配置文件
app.config.from_object('settings.TestingConfig')
@app.route('/index')
def index():
return "index"
if __name__ == '__main__':
app.run()
2.settings.py
class BaseConfig(object):
DEBUG = False
SESSION_REFRESH_EACH_REQUEST = True
class ProConfig(BaseConfig):
pass
class DevConfig(BaseConfig):
DEBUG = True
class TestingConfig(BaseConfig):
DEBUG = True
flask配置文件详解:
flask中的配置文件是一个flask.config.Config对象(继承字典),默认配置为:
{
'DEBUG': get_debug_flag(default=False), 是否开启Debug模式
'TESTING': False, 是否开启测试模式
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'SESSION_REFRESH_EACH_REQUEST': True,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False,
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}
方式一:
app.config['DEBUG'] = True
PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...)
方式二:
app.config.from_pyfile("python文件名称")
如:
settings.py
DEBUG = True
app.config.from_pyfile("settings.py")
app.config.from_envvar("环境变量名称")
环境变量的值为python文件名称名称,内部调用from_pyfile方法
app.config.from_json("json文件名称")
JSON文件名称,必须是json格式,因为内部会执行json.loads
app.config.from_mapping({'DEBUG':True})
字典格式
app.config.from_object("python类或类的路径")
app.config.from_object('pro_flask.settings.TestingConfig')
settings.py
class Config(object):
DEBUG = False
TESTING = False
DATABASE_URI = 'sqlite://:memory:'
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
PS: 从sys.path中已经存在路径开始写
PS: settings.py文件默认路径要放在程序root_path目录,如果instance_relative_config为True,则就是instance_path目录
五、给字符串路径自动找到指定类并执行
importlib + 反射
import settings
import importlib
def send_notify():
for path in settings.NOTIFY_LIST:
# 'notify.email.Email',
# 'notify.msg.Msg',
module_path,cls_name = path.rsplit('.',maxsplit=1) #右排序 取一个
# m = importlib.import_module("notify.email") # import notify.email
m = importlib.import_module(module_path)
cls = getattr(m,cls_name)
obj = cls()
obj.send()
"""
settings.py
NOTIFY_LIST = [
'notify.email.Email',
'notify.wechat.Wechat',
'notify.msg.Msg',
"""
"""
run.py
from notify import send_notify
def run():
send_notify()
if __name__ == '__main__':
run()
"""
六、路由系统
1.cbv与fbv的使用方法:fbv可以种app.route的方法添加
#fbv可以用装饰器
@app.route('/index')
def index():
return "index"
def order():
return 'Order'
app.add_url_rule('/order', None, order)
class TestView(views.View):
methods = ['GET']
def dispatch_request(self):
return 'test!'
app.add_url_rule('/test', view_func=TestView.as_view(name='test')) # name=endpoint
# app.add_url_rule('/test', view_func=view函数) # name=endpoint
2.CBV加装饰器和methods方法
def auth(func):
def inner(*args, **kwargs):
print('before')
result = func(*args, **kwargs)
print('after')
return result
return inner
class X1View(views.MethodView):
methods = ['GET','POST']
decorators = [auth, ]
def get(self):
return 'x1.GET'
def post(self):
return 'x1.POST'
app.add_url_rule('/x1', view_func=X1View.as_view(name='x1')) #name=endpoint
3.执行@app.route('/index')
"""
生成类似dict的形式
{
'/index': index函数
}
1. decorator = app.route('/index')
2.
@decorator
def index():
return "index"
3. decorator(index)
"""
"""
Map() = [
Rule(rule=/index/ endpoint=None view_func=函数),
]
"""
4.路由系统源码剖析
route函数
def route(self, rule, **options):
'''
rule为url 例子rule='/index'
'''
def decorator(f):
endpoint = options.pop('endpoint', None)
'''
rule为/index,f为函数名
'''
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
"""
这段说明 如果endpoint为空,类似django的url里的name字段,就使用当前函数的__name__做为endpoint
"""
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
"""
将options里封装了反向生成url
"""
options['endpoint'] = endpoint
def _endpoint_from_view_func(view_func):
assert view_func is not None, 'expected view func if endpoint ' \
'is not provided.'
return view_func.__name__
def execute(app):
application_iter = app(environ, start_response) #对象加()执行__call__方法
try:
for data in application_iter:
write(data)
if not headers_sent:
write(b'')
finally:
if hasattr(application_iter, 'close'):
application_iter.close()
application_iter = None
try:
execute(self.server.app) #self.server.app 应该为Flask对象
可以看到 application_iter = app(environ, start_response)
就是调用代码获取结果的地方。
要调用 app
实例,那么它就需要定义了 __call__
方法,我们找到 flask.app:Flask
对应的内容:
def auth(func): def inner(*args, **kwargs): print('before') result = func(*args, **kwargs) print('after') return result return inner @app.route('/index.html',methods=['GET','POST'],endpoint='index') @auth def index(): return 'Index' 或 def index(): return "Index" 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 或 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 @app.route和app.add_url_rule参数: rule, URL规则 view_func, 视图函数名称 defaults=None, 默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数 endpoint=None, 名称,用于反向生成URL,即: url_for('名称') methods=None, 允许的请求方式,如:["GET","POST"] strict_slashes=None, 对URL最后的 / 符号是否严格要求, 如: @app.route('/index',strict_slashes=False), 访问 http://www.xx.com/index/ 或 http://www.xx.com/index均可 @app.route('/index',strict_slashes=True) 仅访问 http://www.xx.com/index redirect_to=None, 重定向到指定地址 如: @app.route('/index/<int:nid>', redirect_to='/home/<nid>') 或 def func(adapter, nid): return "/home/888" @app.route('/index/<int:nid>', redirect_to=func) subdomain=None, 子域名访问 from flask import Flask, views, url_for app = Flask(import_name=__name__) app.config['SERVER_NAME'] = 'wupeiqi.com:5000' @app.route("/", subdomain="admin") def static_index(): """Flask supports static subdomains This is available at static.your-domain.tld""" return "static.your-domain.tld" @app.route("/dynamic", subdomain="<username>") def username_index(username): """Dynamic subdomains are also supported Try going to user1.your-domain.tld/dynamic""" return username + ".your-domain.tld" if __name__ == '__main__': app.run()
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() b. 自定制正则路由匹配
@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'])
七、Flask特殊装饰器
from flask import Flask,render_template,request,redirect,session app = Flask('xxxx',template_folder="templates") app.secret_key = 'as923lrjks9d8fwlkxlduf' @app.before_request #类似于django的middleware def bf(): if request.path == '/login': return None user_info = session.get('user_info') if not user_info: return redirect('/login') @app.route('/order',methods=['GET']) def order(): return "order" @app.route('/index',methods=['GET']) def index(): return "index" @app.route('/logout',methods=['GET']) def logout(): del session['user_info'] return redirect('/login') @app.route('/login',methods=['GET','POST']) def login(): if request.method == "GET": return render_template('login.html') else: user = request.form.get('user') pwd = request.form.get('pwd') if user == 'alex' and pwd == '123': session['user_info'] = user return redirect('/index') # return render_template('login.html',msg = "用户名或密码错误",x = 123) return render_template('login.html',**{'msg':'用户名或密码错误'}) if __name__ == '__main__': app.run()
请求之前:@app.before_request
def before_request(self, f):
#self.before_request_funcs = {}
#结果返回 {None: [bf1,bf2]}
# setdefault(None, [])会返回{None:[]}
self.before_request_funcs.setdefault(None, []).append(f) return f
请求之后:
def after_request(self, f):
#{None:[af1,af2]}
self.after_request_funcs.setdefault(None, []).append(f)
return f
八、模版使用
import functools from flask import Flask,render_template,request,redirect,session,Markup app = Flask('xxxx',template_folder="templates") app.secret_key = 'as923lrjks9d8fwlkxlduf' @app.template_global() def sb(a1, a2): return a1 + a2 @app.template_filter() """ 类似django的filter """ def db(a1, a2, a3): return a1 + a2 + a3 def fffff(value): return Markup("<input type='text' value='%s' />" %(value,)) #与django的make_safe一样 @app.route('/index',methods=['GET']) def index(): context = { 'k1': 'v1', 'k2': [11,22,33], 'k3':{ 'name':'oldboy', 'age': 56 }, 'k4':fffff } return render_template('index.html',**context) @app.route('/order',methods=['GET']) def order(): return render_template('order.html') if __name__ == '__main__': app.run()
{% extends "layout.html" %} #继承 {% block content %} <h1>欢迎进入系统</h1> {% include 'xxx.html'%} #类似django的include_tag {% include 'xxx.html'%} {% include 'xxx.html'%} <p>{{k1}}</p> <p>{{k2.0}} {{k2[0]}}</p> <ul> {% for item in k2 %} <li>{{item}}</li> {% endfor %} </ul> <p>{{k3.name}} {{k3['name']}} {{k3.get('name')}} </p> #可以用get,这样可以设置取不到为空不会出现异常 <ul> {% for item in k3.keys() %} <li>{{item}}</li> {% endfor %} </ul> <ul> {% for item in k3.values() %} <li>{{item}}</li> {% endfor %} </ul> <ul> {% for k,v in k3.items() %} {% if v == 'oldboy'%} <li>老男人:{{k}} {{v}}</li> {% else %} <li>{{k}} {{v}}</li> {% endif %} {% endfor %} </ul> <h1>函数: {{k4('123')}}</h1> #可以执行函数返回 <h1>全局函数: {{sb(1,2)}} {{ 1|db(2,3)}}</h1> #全局生效,类似于django的filter和simple_tag {% endblock %}
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <div style="height: 48px;"> 头部内容 </div> <div> {% block content %} {% endblock %} </div> <div style="height: 48px;"> 底部内容 </div> </body> </html>
{% extends "layout.html" %} {% block content %} <h1>订单列表</h1> {% endblock %}
九、Flask-session源码剖析
1.特殊的字典,当前类继承dict就具有dict的特性
class MyDict(dict):
def on_update(self):
pass
v2 = MyDict()
v2['k1'] = 'v1'
print(v2,type(v2))
v3 = dict(v2)
print(v3,type(v3))
2.flask请求进来先执行Flask类的__call__方法 将environ和start_response传入(请求相关的信息)
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)
3. ctx = self.request_context(environ)将请求相关的数据传入Flask类的request_context方法,request_context方法实例化RequestContext,执行__init__
def request_context(self, environ):
#实例化RequestContext 执行__init__
return RequestContext(self, environ)
4.RequestContext的构造方法中,再次实例化了request = app.request_class(environ) 一个request类将请求相关的数据封装到request类中,执行request的__init__方法
def __init__(self, app, environ, request=None):
self.app = app
if request is None:
request = app.request_class(environ) #封装
self.request = request
self.url_adapter = app.create_url_adapter(self.request)
self.flashes = None
self.session = None
# Request contexts can be pushed multiple times and interleaved with
# other request contexts. Now only if the last level is popped we
# get rid of them. Additionally if an application context is missing
# one is created implicitly so for each level we add this information
self._implicit_app_ctx_stack = []
# indicator if the context was preserved. Next time another context
# is pushed the preserved context is popped.
self.preserved = False
# remembers the exception for pop if there is one in case the context
# preservation kicks in.
self._preserved_exc = None
5.通过ctx.push()=Request_Context.push()执行session的操作
def push(self):
top = _request_ctx_stack.top
if top is not None and top.preserved:
top.pop(top._preserved_exc)
app_ctx = _app_ctx_stack.top
if app_ctx is None or app_ctx.app != self.app:
app_ctx = self.app.app_context()
app_ctx.push()
self._implicit_app_ctx_stack.append(app_ctx)
else:
self._implicit_app_ctx_stack.append(None)
if hasattr(sys, 'exc_clear'):
sys.exc_clear()
_request_ctx_stack.push(self)
#此处操作session
self.session = self.app.open_session(self.request)
if self.session is None:
self.session = self.app.make_null_session()
6.open_session执行Flask类的 self.session_interface.open_session(self, request)
Flask的 session_interface = SecureCookieSessionInterface()
相当于执行了 SecureCookieSessionInterface类里的 open_session 方法
def open_session(self, app, request):
s = self.get_signing_serializer(app)
if s is None:
return None
val = request.cookies.get(app.session_cookie_name)
if not val:
return self.session_class()
“”“
self.session_class() = SecureCookieSession() SecureCookieSession继承了dict
”“”
max_age = total_seconds(app.permanent_session_lifetime)
try:
data = s.loads(val, max_age=max_age) #通过sercet_key进行编码
return self.session_class(data)
except BadSignature:
return self.session_class()