Flask基础与进阶 一 概述 二 与其他web框架比较 三 安装和简单使用 四 Flask提供的功能

Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。

“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。

默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。

二 与其他web框架比较

1.Django 主要特点是大而全,集成了很多组件,例如: Models Admin Form 等等, 不管你用得到用不到,反正它全都有,属于全能型框架

2.Tornado 主要特点是原生异步非阻塞,在IO密集型应用和多任务处理上占据绝对性的优势,属于专注型框架

3.Flask 主要特点小而轻,原生组件几乎为0, 三方提供的组件请参考Django 非常全面,属于短小精悍型框架

Django 通常用于大型Web应用由于内置组件足够强大所以使用Django开发可以一气呵成

Tornado 通常用于API后端应用,游戏服务后台,其内部实现的异步非阻塞真是稳得一批

Flask 通常应用于小型应用和快速构建应用,其强大的三方库,足以支撑一个大型的Web应用

Django 优点是大而全,缺点也就暴露出来了,这么多的资源一次性全部加载,肯定会造成一部分的资源浪费

Tornado 优点是异步,缺点是干净,连个Session都不支持

Flask 优点是精悍简单

三 安装和简单使用

安装

pip install flask

简单使用

from flask import Flask
app = Flask(__name__)
 
@app.route('/')
def hello_world():
    return 'Hello World!'
 
if __name__ == '__main__':
    app.run()

四 Flask提供的功能

1 配置文件

方式1:

app.config['DEBUG'] = True

方式2: (推荐)

//setting.py
class BaseConfig(object):
    DEBUG = True
    
//app.py
app.config.from_object('settings.BaseConfig')    

查看配置文件: 

print(app.config)

技术点:

- rsplit
- importlib
- getattr

2 路由系统

路由设置的两种方式

方式一

通过装饰器设置路由

@app.route('/xxx')
def index():
    return "index"

方式二

通过函数设置路由

def index():
    return "index"
app.add_url_rule("/xxx",None,index)

主要的参数

url                # url路径
methods            # 允许访问的请求方式 , 默认get
endpoint        # 用于反向解析生成url的        !!!!注意不要让endpoint重名
redirect_to     # url地址重定向
strict_slashes        # url地址结尾符"/"的控制 False  无论结尾 "/" 是否存在均可以访问 , True : 结尾必须不能是 "/"
defaults             # 视图函数的参数默认值{"nid":1}
subdomain             # 子域名前缀 subdomian="test" 这样写可以得到 test.myserver_name 前提是app.config["SERVER_NAME"] = "myserver_name"

rule                     #  URL规则
view_func                #  视图函数名称
参数

反向解析

@app.index('/index', methods=['GET', 'POST'], endpoint='n1')
def index():
    # 使用反向解析
    from flask import url_for
    
    print(url_for('n1'))

通过url传参(动态路由)

@app.route('/detail/<int:nid>')
def detail(nid):
    pass
    
    
    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'])

DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

# 自定义正则
传参类型

反向解析时

@app.detail('/detail/<int:nid>')
def detail(nid):
  # 使用反向解析
  from flask import url_for

  print(url_for('detail', nid=nid))

3 CBV

本质也是利用了FBV

技术点: 反射

from flask import Flask,views
class UserView(views.MethodView):
    methods = ['GET']
    decorators = [wrapper,]

    def get(self,*args,**kwargs):
        return 'GET'

    def post(self,*args,**kwargs):
        return 'POST'
        
app.add_url_rule('/user',None,UserView.as_view('uuuu'))        # 添加路由
CBV示例

4 请求相关

from flask import request

request.method            # 请求的方法
request.args            # url中传递的参数
request.form            # Form表单中传递过来的值
request.values            # 获取所有的数据
request.cookies            # 请求cookie
request.headers            # 请求头
request.path            # 当前url的路经            如  /index
request.full_path
request.script_root         # 当前url路径的上一级路径
request.url                # 当前url的全部路径
request.base_url
request.url_root        # 当前url的路径的上一级全部路径
request.host_url
request.host
request.files            # 上传的文件,
obj = request.files['the_file_name']
obj.save('/var/www/uploads/' + secure_filename(f.filename))
request.xx

5 响应相关

5.1 响应体

from flask import render_template, redirect, jsonify, Flask

app = Flask(__name__)

@app.route('/index')
def index():
    # return "字符串"                                # 1 返回字符串
    # return render_template('html模板路径',**{})    # 2 返回html
    # return redirect('/index.html')                # 3    重定向
    # return jsonify(dic)                            # 4    返回json格式数据

5.2 设置响应头

obj = make_response('hello world')
obj.headers['xxx'] = '123'
return obj

5.3 设置cookie

obj.set_cookie('k', 'v')

6 模板渲染

django不用加括号执行函数
flask自己加括号执行函数, 语法更接近python

元祖/列表

list.0
list[0]

渲染html

txt = Markup('<input .....>')    # 后端
{{txt|sage}}                # 前端

函数

{{func(6)}}

全局函数

不需要通过上下文传到template

@app.template_global()
def sb(a1, a2):
    # {{sb(1,9)}}
    return a1 + a2

@app.template_filter()
def db(a1, a2, a3):
    # {{ 1|db(2,3) }}    # 可以在if后面当条件
    return a1 + a2 + a3

模板继承

与django一样

{% macro ccccc(name, type='text', value='') %}
    <h1>宏</h1>
    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
    <input type="submit" value="提交">
{% endmacro %}

{{ ccccc('n1') }}    # 调用
{{ ccccc('n2') }}

7 session

默认超时时间为30天    (django默认2周)

当请求刚到来:flask读取cookie中session对应的值:eyJrMiI6NDU2LCJ1c2VyIjoib2xkYm95,将该值解密并反序列化成字典,放入内存以便视图函数使用。

流程

1 当请求刚到来:flask读取cookie中session对应的值:eyJrMiI6NDU2LCJ1c2VyIjoib2xkYm95,将该值解密并反序列化成字典,放入内存以便视图函数使用。
2 视图函数:
    @app.route('/ses')
    def ses():
        session['k1'] = 123
        session['k2'] = 456
        del session['k1']

        return "Session"
        
3 当请求结束时(返回响应前一刻),flask会读取内存中字典的值,进行序列化+加密,写入到用户cookie中。并且会删掉服务端的session

实现原理

__call__

    return self.wsgi_app(environ, start_response)

8 闪现

相当于一次性的session

在session中存储一个数据,读取时通过pop将数据移除。

from flask import Flask,flash,get_flashed_messages
@app.route('/page1')
def page1():
    # flsah('hello world')
    flash('临时数据存储','error')
    flash('sdfsdf234234','error')
    flash('adasdfasdf','info')

    return "Session"

@app.route('/page2')
def page2():
    # print(get_flashed_messages())
    print(get_flashed_messages(category_filter=['error']))
flash

9 中间件

- call方法什么时候触发?
  - 用户发起请求时,才执行。
- 示例:

在执行call方法之前,做一个操作,call方法执行之后做一个操作。

class Middleware(object):
    def __init__(self,old):
        self.old = old

    def __call__(self, *args, **kwargs):
        print('')
        ret = self.old(*args, **kwargs)
        print('')
        return ret


if __name__ == '__main__':
    app.wsgi_app = Middleware(app.wsgi_app)
    app.run()
middleware

10 特殊装饰器

1. before_request     # 先定义先执行
2. after_request      # 先定义后执行
3. before_first_request     # 项目执行后就第一次请求执行
4. template_global()    # 全局函数
5. template_filter()   # 全局函数
6. errorhandler       # 定制错误页面

示例:

@app.errorhandler(404)
def not_found(arg):
    print(arg)
    return "没找到"

11 蓝图

给开发者提供目录结构

1 使用蓝图

//account.py
from flask import Blueprint, render_template

# 1 实例化蓝图对象
ac = Blueprint('ac', __name__)

@ac.route('/login')
def login():
    return render_template('login.html')
    
//app.py
from flask import Flask
app = Flask(__name__)

# 2 将蓝图注册到Flask对象
app.register_blueprint(ac, url_prefix='/ac')
app.register_blueprint(uc)

app.run()

2 其他功能

a. 蓝图指定模板
    # 先去项目路径找, 找不到猜到指定目录找
    ac = Blueprint('ac', __name__, template_folder='xxx')
    
b. 指定静态路径
    # 先去项目路径找, 找不到猜到指定目录找
    ac = Blueprint('ac', __name__, static_url='sssstatic')
    
c 为蓝图url加上前缀 (注册的时候)
    app.register_blueprint(ac, url_prefix='/ac')
    
d 可以给某个视图做特殊装饰器
    @ac.before_request
    def x():
        print("in ac")

12 threading.local

和flask无关, 但flask的上下文用到的Local有点类似threading.local

作用: 为每个线程创建一个独立的空间, 使得线程对自己空间中的数据进行操作(数据隔离)

应用: 

- Flask上下文管理中的Local类更高级(支持到协程)
- DBUtils线程池的模式一:为每个线程创建一个连接。
- SQLAlchemy的连接池连接模式: session = scoped_session(SessionFactory)

示例:

import threading
from threading import local
import time

obj = local()


def task(i):
    obj.xxxxx = i
    time.sleep(2)
    print(obj.xxxxx,i)

for i in range(10):
    t = threading.Thread(target=task,args=(i,))
    t.start()
threading_local.py

学习三部曲

# 获取线程唯一标示 ---> threading.get_ident()    

1 根据字典自定义一个类似于threading.local功能
    import time
    import threading

    DIC = {}

    def task(i):
        ident = threading.get_ident()
        if ident in DIC:
            DIC[ident]['xxxxx'] = i
        else:
            DIC[ident] = {'xxxxx':i }
        time.sleep(2)

        print(DIC[ident]['xxxxx'],i)

    for i in range(10):
        t = threading.Thread(target=task,args=(i,))
        t.start()
        
2 根据字典自定义一个为每个协程开辟空间进行存取数据
    import time
    import threading
    import greenlet

    DIC = {}

    def task(i):
        
        # ident = threading.get_ident()
        ident = greenlet.getcurrent()
        if ident in DIC:
            DIC[ident]['xxxxx'] = i
        else:
            DIC[ident] = {'xxxxx':i }
        time.sleep(2)

        print(DIC[ident]['xxxxx'],i)

    for i in range(10):
        t = threading.Thread(target=task,args=(i,))
        t.start()
        
3 通过getattr/setattr 构造出来 threading.local的(面向对象)加强版(协程)
    import time
    import threading
    try:
        import greenlet
        get_ident =  greenlet.getcurrent
    except Exception as e:
        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)
            return None

        def __setattr__(self, key, value):
            ident = get_ident()
            if ident in self.DIC:
                self.DIC[ident][key] = value
            else:
                self.DIC[ident] = {key:value}
            

    obj = Local()

    def task(i):
        obj.xxxxx = i
        time.sleep(2)
        print(obj.xxxxx,i)

    for i in range(10):
        t = threading.Thread(target=task,args=(i,))
        t.start()
threading_local_demo.py

13 请求上下文管理

# 重要

a. 请求到来时,(wsgi(werkzerg),初步处理请求, (别人写好的)), wsgi(werkzerg)会触发__call__方法, 由__call__方法再次调用wsgi_app方法
    __call__(self, environ, start_response)
        # environ           是请求相关的所有原始数据(由wsgi做了初步封装)
        # start_response    用于设置响应相关数据
    
b. 在wsgi_app方法中     
    - 首先将请求相关+空session 封装到一个RequestContext对象中, 即:ctx
        # ctx = RequestContext(self, environ) # self是app对象,environ请求相关的原始数据
            # ctx.request = Request(environ)
            # ctx.session = None
    - 通过LocalStack, 把ctx对象添加到Local中
        Local存储ctx的结构:
        __storage__ = {
            1321: {stack: [ctx, ]},
            1234: {stack: [ctx, ]},
            1432: {stack: [ctx, ]},
            ...
                    }
    - 从请求cookie中提取名为sessionid的值, 对该值解密+反序列化, 再次赋值给ctx中的session
    
c 视图函数中
    - 把session中的数据再次写入到cookie中
    - 将ctx删除
    
d 结果返回给用户浏览器
e 断开socket连接

14 应用上下文管理

a. 程序启动时
    两个local
    两个localstack
        - _request_ctx_stack
        - _app_ctx_stack
    
b. 请求到来
    对数据进行封装
        ctx = RequestContext(request, session)
        app_ctx = AppContext(app, g)
        
    保存数据
        将包含了(app, g)数据的app_ctx对象, 利用_app_ctx_stack(localstack对象)添加到了Local中
            __storage__ = {
                1233: {stack: [app_ctx, ]},
                ...
            }
        将包含了request, session数据的ctx对象, 利用_request_ctx_stack(localstack)添加到了Local中
            __storage__ = {
                1233: {stack: [ctx, ]},
                ...
            }
    
c. 视图函数处理
    form flask import Flask, request, session, current_app, g
    
    pass
    # 业务逻辑
    
d. 结束
    _app_ctx_stack.pop()
    _request_ctx_stack.pop()

15 localproxy对象

视图函数: 通过localproxy --> 再通过 localstack --> 取local的值

LocalProxy用于代理Local对象和LocalStack对象,而所谓代理就是作为中间的代理人来处理所有针对被代理对象的操作

直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用Proxy,每次调用操作符(这里[]操作符用于获取属性),都会重新获取user,从而实现了动态更新(stack)user的效果。

参考: https://www.jianshu.com/p/3f38b777a621

16 离线脚本的正确打开方式

# 注意:请求上下文和应用上下文需要先放入Local中,才能获取到。

from chun import db,create_app
from flask import current_app

app = create_app()
app_ctx = app.app_context()
with app_ctx:    # __enter__方法通过localstack将app_ctx放入local中, 这之后current_app才存在
    # 正确
    print(current_app.config)        # 注意导入
离线脚本app.py

17 多应用app

from flask import Flask
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple
app1 = Flask("app1")
app1.config['DB'] = 123

app2 = Flask("app2")
app1.config['DB'] = 456

@app1.route('/web')
def web():
    print('web')
    return '12213'

@app1.route('/news')
def news():
    print('news')
    return '12213'


@app2.route('/admin')
def admin():
    print('admin')
    return '12213'


@app2.route('/article')
def article():
    print('article')
    return '12213'

"""
/web
/new
/app2/admin
/app2/article
"""
app = DispatcherMiddleware(app1, {
    '/app2':app2,
})
if __name__ == '__main__':

    run_simple(hostname='127.0.0.1',port=5000,application=app)
multi_app.py