Flask记录

Flask 和 Django的区别

Django

Django大而全,功能齐全,开发效率高,但缺点是过于臃肿

Flask

Flask小而精,轻量灵活,但缺点是很多东西需要自己实现或者引入第三方插件

几行代码即可运行起来,极致简洁

# 三行代码启动一个flask服务器
from flask import Flask
app = Flask(__name__)
app.run()

# 六行代码实现一个flask接口
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello Flask!"

app.run(host="127.0.0.1")

Flask 依赖

必须要依赖的

Jinja2:一个用以模板引擎渲染的库
Werkzeug:Web框架的底层库,【本质上是一个socket服务端】,某种程度而言,Flask是Werkzeug的扩展

常用的依赖

Flask-SQLAlchemy:比较常见的MySql ORM库
Flask-Migrate:ORM迁移库
celery:定时任务/异步任务调度库
flask-mongoengine:MongoDB ORM库 
redis:redis数据库操作库

使用Werkzeug手写一个简单的框架

from werkzeug.serving import run_simple


class MyFlask(object):

    def __call__(self, environ, start_response):
        print('----------请求进来----------')
        # flask万物起源,其内所有操作都在这一行代码内完成
        # return self.wsgi_app(environ, start_response)
        start_response('200 OK', [('Content-Type', 'text/html')])
        return [b'Hello, World!']

    def run(self):
        # run_simple内部处理请求时会调用self(),因此会触发__call__方法
        # self()
        run_simple('127.0.0.1', 8000, self)


if __name__ == '__main__':
    app = MyFlask()
    app.run()

Flask特殊装饰器

请求前后钩子,类似django的中间件

@app.before_request :请求前钩子
@app.after_request  :请求后钩子,被装饰函数要接受一个参数response,并且需要在被装饰函数内return该参数
@app.before_first_request :第一次请求前钩子,只有在第一次请求进来前执行

路由管理

@app.route :将url和函数打包成rule,封装到map对象,map再放到app中
@app.do_teardown_request :在请求结束时执行,无论请求成功与否

信号

@signals.appcontext_pushed.connect :在将应用上下文push到栈时触发,被装饰函数要接受一个参数app
@signals.appcontext_popped.connect :在将应用上下文pop出栈时触发,被装饰函数要接受一个参数app
@signals.request_started.connect :在请求准备开始处理之前触发,被装饰函数要接受一个参数app
@signals.before_render_template.connect :在渲染模板前触发,接收参数:app、template、context(模板参数)
@signals.template_rendered.connect :在渲染模板后执行,接收参数:app、template、context(模板参数)
@signals.request_finished.connect :在请求视图函数完成后执行,接收参数:app、response
@signals.got_request_exception.connect :在请求出错时捕获到异常时执行,接收参数:app、exception
@signals.request_tearing_down.connect :在请求结束时pop请求上下文时执行,接收参数:app、exc
@signals.appcontext_tearing_down.connect :在请求结束时pop应用上下文时执行,接收参数:app、exc

threading.local 的作用

用于为每个线程开辟一块空间来存取该线程独有的值
Flask中并没有用到这个,但Flask自身重新实现了一个类似的【Local】

import time
import threading

class Test:

    def __init__(self,num):
        self.num = num

# 多个线程都修改同一个值,所以打印出来的结果均相同
# test_obj = Test(0)
# 多个线程各自维护自己的值,所以打印出来的值各不相同
test_obj = threading.local()

def task(i):
    test_obj.num = i
    time.sleep(1)
    print(test_obj.num)


if __name__ == '__main__':

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

Flask 自己实现的Local【上下文管理】

Flask 实现的Local类和threading.local 功能相似,为每个线程/协程开辟空间存取数据,实现机制都是内部维护一个字典,以线程/协程ID为key,进行数据的隔离,如:storage = {12312:{'stack':1},12432:{'stack':2}}

Flask 还实现了一个LocalStack类,它内部依赖于Local,Local用以具体存取数据,而它则用于将Local维护成一个栈,Flask外部使用时,均为使用此类,注意此类应该为单例模式,一个程序永远只有一个同类型的localstack对象

注:为什么要将stack维护成一个栈呢?主要目的是为了解决多个app的问题,比如离线脚本等。秉承“先进后出,后进先出”原则

一个Flask对象,一个有2个localstack,分别是:请求上下文(request_ctx_stack) 和 应用上下文(app_ctx_stack)

# context locals

__storage__ = {
    1111: {'stack': [RequestContext(request, session)]},
    2222: {'stack': [RequestContext(request, session)]}
}
_request_ctx_stack = LocalStack()

__storage__ = {
    1111: {'stack': [AppContext(app, g)]},
    2222: {'stack': [RequestContext(app, g)]}
}
_app_ctx_stack = LocalStack()

# 一个请求进来时,把构建请求上下文和应用上下文,push到对应的Local内
_request_ctx_stack.push(RequestContext(request, session))
_app_ctx_stack.push(AppContext(app, g))

原理代码实现【简写】:

import time
import threading


class Local:

    def __init__(self):
        # 存放数据,格式:{'线程/协程id1':{'stack':[]},'线程/协程id2':{'stack':[]}}
        super(Local, self).__setattr__('storage', {})
        # 获取线程唯一标识,flask中兼容了协程,可以获取协程唯一标识
        super(Local, self).__setattr__('ident', threading.get_ident)

    def __getattr__(self, item):
        if self.ident() not in self.storage:
            return
        return self.storage[self.ident()][item]

    def __setattr__(self, key, value):
        try:
            self.storage[self.ident()][key] = value
        except KeyError:
            self.storage[self.ident()] = {key: value}

    def release_local(self):
        # 释放线程/协程本地资源
        del self.storage[self.ident()]


class LocalStack:

    def __init__(self):
        self._local = Local()

    def top(self):
        # 返回栈顶
        stack = getattr(self._local, 'stack', None)
        return stack[-1]

    def pop(self):
        # 出栈
        stack = getattr(self._local, 'stack', None)
        if len(stack) == 1:
            # 当栈内数据只有1个时,释放调线程/协程的本地资源
            self._local.release_local()
        return stack.pop()

    def push(self, obj):
        # 入栈
        stack = getattr(self._local, 'stack', None)
        # 数据不存在时,初始化一个
        if not stack:
            self._local.stack = stack = []
        stack.append(obj)
        return stack


if __name__ == '__main__':
    stack = LocalStack()


    def task(i):
        stack.push(i)
        stack.push(i + 1)
        stack.push(i + 2)
        stack.pop()
        print(stack._local.storage)

        # {746504: {'stack': [0, 1]}}
        # {746504: {'stack': [0, 1]}, 757820: {'stack': [1, 2]}}


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

偏函数

在functools包中有partial函数,其作用为将传入函数封装成一个新的函数,并可自动传递值进目标函数

from functools import partial


def request_ctx(attr):
    a = {'request': 1, 'session': 2}
    return a.get(attr)


def app_ctx(attr):
    a = {'app': 3, 'g': 4}
    return a.get(attr)


if __name__ == '__main__':
    request = partial(request_ctx, 'request')
    session = partial(request_ctx, 'session')
    g = partial(app_ctx, 'g')

    print(request)
    print(session)
    print(g)
    print(request(),session(),g())

    # 此时得到的输出内容为三个函数,输出结果如下:
    # functools.partial(, 'request')
    # functools.partial(, 'session')
    # functools.partial(, 'g')
    # 1 2 4

Flask使用LocalProxy结合偏函数取app、g、request、session

def _lookup_req_object(name):
    top = _request_ctx_stack.top
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)


def _lookup_app_object(name):
    top = _app_ctx_stack.top
    if top is None:
        raise RuntimeError(_app_ctx_err_msg)
    return getattr(top, name)

_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()

current_app = LocalProxy(partial(_lookup_app_object,"app"))
g = LocalProxy(partial(_lookup_app_object, "g"))
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))

版权声明:
作者:ht
链接:https://www.techfm.club/p/42531.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>