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(<function request_ctx at 0x000001CD40154700>, 'request')
    # functools.partial(<function request_ctx at 0x000001CD40154700>, 'session')
    # functools.partial(<function app_ctx at 0x000001CD40154940>, '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"))
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容