Flask

一,Flask初识

  Python现阶段三大主流web框架Django,Tornad,Flask对比

    1.Django主要特点是大而全,集成了很多组件,例如:Models Admin Form等等,不管是否用上,它全都有,属于全能型框架,Django通常用于大型web应用由于内置组件足够强大所以使用Django开发可以一气呵成,缺点是浪费资源,一次性加载全部资源,肯定会造成一部分资源的浪费想·

    2.Tornado主要特点是原生异步非阻塞,在IO密集型应用和多任务处理上占据绝对性的优势,属于专注型框架,通常用于api后端应用,游戏服务后台,内部实现的异步非阻塞非常牛逼,缺点是干净,不支持Session

    3.Flask主要特点小而轻,原生组件几乎为0,三方提供的组件请参考django非常全面,属于短小精悍型框架,通常应用于小型应用和快速构建应用,其强大的三方库,足以支撑一个大型的web应用

  Flask 安装

pip install flask

二,Flask的WSGI网关接口协议

 WSGI网关接口协议
    django:
       wsgi_ref 封装request,封装socket,一般用于django本地测试
    uwsgi django上线使用,性能更好
    flask:
     Werkzeug是Python的WSGI规范的实用函数库。使用广泛,基于BSD协议
       werkzeug为Flask封装了socket
                from werkzeug.wrappers import Request, Response
                from werkzeug.serving import run_simple

                @Request.application
                def run(request):
                    return Response("hello~~")

                if __name__ == '__main__':
                    run_simple('localhost', 5000, run)
      # 功能特性
        HTTP头解析与封装
        易于使用的request和response对象
        基于浏览器的交互式JavaScript调试器
        与 WSGI 1.0 规范100%兼容
        支持Python 2.6,Python 2.7和Python3.3
        支持Unicode
        支持基本的会话管理及签名Cookie
        支持URI和IRI的Unicode使用工具
        内置支持兼容各种浏览器和WSGI服务器的实用工具
        集成URL请求路由系统

三,Flask的demo

from flask import Flask

# 注意静态文件以及模板的配置
# 默认tamplates static
app = Flask(__name__)

@app.route("/")
def index():
    return # 可以返回的类型 render_templage()/redirect()/"字符串"

app.run()

四,Flask的配置文件

# 配置文件
      #  配置信息 app.config
      #  修改配置信息 app.config["DEBUG"] = True
      #  解耦写法
        -- settings.py
                class DEVConfig(object):
                    DEBUG = True
                    SECRET_KEY = "jalksdjgajh"


                class ProConfig(object):
                    DEBUG = False


               class TestConfig(object):
                    TESTING = True

    -- app.config.from_object("settings.DEVConfig")    # from_object设置配置文件类  
# 可以直接用app.配置的项
app.testing
app.secret_key
app.session_cookie_name
app.permanent_session_lifetime
app.send_file_max_age_default
app.use_x_sendfile

五,Flask的路由

# 一般的路由
    @app.route("/book") 

# 带参数的路由 
    @app.route("/book/<int:nid>")  # 参数类型 不设置数据类型 则默认为str类型
    # 参数的数据类型:略

# 路由的命名:
     @app.route("/book",endpoint="book") # 不配置的话,endpoint默认为视图函数名

# 命名路由的反向解析
    from flask import url_for
    url_for("name",nid=xxx) # => /book/123
    
return redirect(url_for("xxx",nid='xx'))

   路由的实现原理

@app.route("/index")  # 带参数的装饰器
decorator = app.route("/index")

# 源码
def route(self, rule, **options):
    def decorator(f):
        endpoint = options.pop('endpoint', None)
        self.add_url_rule(rule, endpoint, f, **options)
        return f
return decorator

@decorator
def index():
    pass
app.add_url_rule(rule,endpoint=None,view_func=视图函数)
# 可以通过这个方式来创建对象关系

  路由的正则匹配

# 源码自带的正则匹配类
UnicodeConverter
AnyConverter
PathConverter
NumberConverter
IntegerConverter
FloatConverter
UUIDConverter
# 自定义正则
    class RegexConverter(BaseConverter):
        # 自定义URL匹配正则表达式
        def __init__(self,map,regex):
            super(RegexConverter,self).__init__(map)
            self.regex = regex

        def to_python(self,value):
            # 路由匹配是,匹配成功后传递给视图函数中参数的值
            return int(value)

        def to_url(self,value):
            # 使用url_for 反向生成URL时,传递的参数经过该方法处理,返回值用于生成URL参数
            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"))
        print(nid)
        print(type(nid))
        return "Index"

六,Flask的请求相关以响应相关

# 请求相关
     # flask的request不如django一样贯穿整个请求的声明周期,那么python怎么识别哪个请求对的哪个request呢:
        以协程作为唯一标志为key:具体请求的数据为value  # 使用历史于字典的数据结构
    
    from flask import request
# 常用的请求相关的数据
request.method # 请求类型
request.headers # 请求头 request.args
# url的参数?xx=xx reuqest.form # 表单的数据 request.files # 上传文件
request.path # 获取url
request.full_path # 获取完整url # 上传文件
obj = request.files['file_name'] obj.save('/var/www/uploads/'+ secure_filename(f.filename)) # 响应相关 # response三种类型 return str return render_template return redirect # 自定义响应 from flask import make_response # 封装响应对象 response = make_response(render_template("xxx.html")) response.set_cookie("key","value") # 设置cookie response.header["X-Something"] = "A value" # 设置响应头 return response

七,Flask的模板渲染

# flask的模板渲染 跟django的模板语言 基本相同
    # 区别
       # 函数的执行需要加() 
        {{my_func()|safe}}

       # 字典的三种取值方式
            {{ my_dict.ages }}
            {{ my_dict.["ages"] }}
            {{ my_dict.get("ages", 0) }}
       
       # 给页面传递数据,包括函数
       return render_template("xxx.html",**{"book_list":book_list,"myfunc":myfunc})

八,Session

# session
    flask的session底层:base64
    app.config["SECRET_KEY"]="xx"  # 配置盐
    session['userinfo'] = {"name":name }    # 设置session
    session.get("userinfo")     # 在session中取值
session.pop("key") # 删除
# flash 闪现:只能在一个请求里拿值
    from flask import flash,get_flashed_message    

# 原理 # 设置值的时候 session["xxx"]=value flash("value","key") # 取值 session.pop("xxx") name = get_flashed_messages() name = get_flashed_messages(category_filter=["name"])

九,视图

# 路由的实现原理
    # @app.route("/index")
    decorator = app.route("/index")
    @decorator
    def index():
        return "xxx"

    app.add_url_rule(rule,endpoint,f)
       # rule = "/index"
       # endpoint = 别名
       # f = 视图函数名
   # 注意在add_url_rule方法里
   #     endpoint默认取函数名
   #     两个函数不能用一个endpoint
# CBV编程
    class MyView(views.MethodView):
        decorators = [auth,]
        methods = ["GET","POST"]

        def get(self):
            return "GET"

        def post(self):
            return "POST"

    app.add_url_rule("/index",view_func=MyView.as_view(name="index"))  # name ==> endpoint

十,中间件

# 中间件
  #  Flask 请求的入口
       # 1. app.run() ==> run_simple()
# 2. werkzoug的run_simple(host,port,self,**option)
# 3. self() --> app() # app() --> Flask.__call__() # 4. __call__ => return self.wsgi_app(*args,**kwargs) # 实现中间件 # 改源码(不推荐..) # 类实现 class Middleware(object) def __init__(self,old_wsgi_app) self.old = old_wsgi_app def __call__(self,*args,**kwargs): # 请求前做某操作 ret self.old(*args,**kwargs) # 请求后做某操作 return ret
# 把Flask().wsgi_app当成蚕食传递给了Middleware
# app.wsgi_app是经过封装的,就是Middleware实例对象 app.wsgi_app
= Middleware(app.wsgi_app) app.run() app.__call__() self.wsgi_app(*args,**kwargs) # 实例对象()执行__call__ 

十一,特殊的装饰器

# 特殊的装饰器
    # 注意被装饰器装饰后的函数名问题
    @app.before_request  # 相当于process_request
    @app.before_first_request  # 只在第一次访问的时候触发 
    @app.after_request  # 相当于process_response
    @app.template_gloal() # 全局模板替换
       {{total(1, 1)}} 

    @app.template_filter()  # 类似django的filter
       {{"hello" | db()}}

    @app.errorhandler(404)  # 当前404时触发

    # 注意执行
    # before_request有返回值的时候还会按顺序执行after_request
    # django 的 <=1.9版本 当process_request有返回值的时候,跟flask是一样的

  使用自定义装饰器实现 认证功能

import functools

def auth(func)
    @functools.wraps(func)  # endpoint 默认为视图名,不修复的话,别名默认都指向inner
    def inner(*args,**kwargs)
        return func(*args,**kwargs)
    return inner

十二,蓝图

主要功能: 做目录结构,解耦
        1.新建一个项目目录 项目目录下建一个同名的python包
        2.在项目目录下建manager.py
导入create_app
app = create_app()
app.run()
3.在包的__init__实例化Flask对象
def create_app():
把蓝图对象注册到app中
app.register_blueprint(userBlue,**option)
app = Flask(__name__)
return app
4.在manager.py 导入app app.run() 5.在Python包里建立views文件夹 任何一个文件都可以生成蓝图对象 from flask import Blueprint bookBule = BluePrint("bookBlue",__name__) @bookBlue.route("/") def index(): return "xxx"-- python项目目录 -- views目录 -- user.py -- book.py -- 同名的py包 -- __init__ 实例化Flask对象 -- app.py -- manager.py 启动项目 -- 导入app app.run()

十三,Flask的上下文管理(可以简单的理解为一个请求的生命周期)

  Flask的上下文管理我们可以简单的理解为一个生命周期,也就是请求进来到请求出去一共做了哪些事情,我们从源码开始走。

  准备知识

#  偏函数
        from functools import partial
        给一个函数固定一个参数
        def func(x,y,z):
            pass

        new_func = partial(函数名func,固定的参数)
        new_func(x,y) --> func(固定的参数,x,y)

#   __setattr__
        对象.xxx --> __getattr__
        对象.xxx = ""  -->  __setattr__
        当我们实例化的时候先走__init__
            如果在__init__  执行self.xxx = {} 也会走__setattr__

  Flask的上下文管理(源码分析)

# 请求来的时候究竟做了什么?
    app.run()
        # 1. 调用了werkzeug中run_simple() self 为app
        run_simple(host, port, self, **options)
        # 2. 在run_simple会执行self(),也就是self.__call__(),所以会走Flask的__call__方法
        Flask.__call__():
            # 2.1 在Flask类的__call__方法执行了wsgi_app()方法
            return self.wsgi_app(environ, start_response) # environ是请求的原始数据,start_response为封装的响应对象
            # 2.2 在wsgi_app中,将environ作为参数传给了request_context方法
            ctx = self.request_context(environ)
                # 2.2.1 request_context的返回值为RequestContext, ctx被赋值为 RequestContext的实例对象
                return RequestContext(self, environ) # self为app,执行RequestContext的__init__方法
                # 2.2.2 在RequestContext的__init__方法中,封装了ctx.request和ctx.session,还有ctx.app
                self.app = app
                if request is None:  # 由于request参数没传值默认为None
                    request = app.request_class(environ)
                self.request = request
                # 2.2.3 ctx.session = None
                self.session = None
                # request_class 的真身为Request类
                request_class = Request
                # 2.2.4 相对于ctx.request = Request(environ) 被封装为Request的实例对象
            # 2.3 ctx继续执行了RequestContext类的push()方法
            ctx.push()

  在ctx.push()  

    请求的上下文管理  

# 请求上下文管理
    # 2.3.1 在push方法中 _request_ctx_stack真身为LocalStack的实例对象
    # 相当与执行了 LocalStack().push(ctx)
    _request_ctx_stack.push(self)
        # 2.3.1.1 LocalStack类中的__init__方法
        self._local = Local()
        # 2.3.1.2 Local类的中__init__方法,
        #  封装了 self.__storage__ = {}
        object.__setattr__(self, '__storage__', {})
        #  封装了一个获取线程或协程唯一标识的方法:self.__ident_func__ -> get_ident
        object.__setattr__(self, '__ident_func__', get_ident)
    # 2.3.2 LocalStack类中的push方法
    def push(self, obj): # obj = ctx
        # 通过getattr,触发Local类的__getattr__方法 =>return self.__storage__[self.__ident_func__()][name] => name = stack,去__storage__中,唯一标识对应的{stack:[]}
        rv = getattr(self._local, 'stack', None)
        if rv is None:  # 第一次调用,rv为none
            # self._local.stack赋值操作触发了local类的__setattr__
            # ident = self.__ident_func__()
            # storage = self.__storage__
            # storage[ident][name] = value
            self._local.stack = rv = []
            # 由于rv和self._local.stack都是指向同一个列表内存地址
        rv.append(obj)  # 相当与在给self._local.__storage__[ident][value].append(ctx)  # 优点是步骤小了
        return rv

    应用的上下文管理

# 应用上下文管理
    # 跟请求上下文用的是两个实例化对象  _app_ctx_stack = LocalStack()
    app_ctx = _app_ctx_stack.top # 也是取 __storage__[唯一标识][stack],没赋值所以为空
    if app_ctx is None or app_ctx.app != self.app:
        # self.app 就是我们Flask实例化对象app
        # app_context() = AppContext()
        # AppContext封装了app以及g
        app_ctx = self.app.app_context()
        # 调用AppContext里的push方法
        # 依然是调用_app_ctx_stack.push方法
        # 这里就和请求上下文一样了
        # _app_ctx_stack.push(self)
        # self._local.stack = rv = []
        # self._local.__storage__[ident]['stack'].append(ctx) # ident 进程或现线程的唯一标识
        app_ctx.push()
    # 总结,我们请求上下文和应用上下文,分别别建立了两个Local对象,两个Local对象数据结构都是一样的,那么请求上下文和应用上下文为什么要分开存放呢
    
    # 源码注释:在我们推送请求上下文之前,我们必须确保是一个应用程序上下文。    

    导入的request到时是怎么实现的

# Flask的上下文管理
    #在我们的视图中上面那种拿request的方法太费劲了,我们需要简单一点的拿到request~~那么我们导入的request跟我们上面拿到的request一定是一样的~~那导入的这个request到底是如何实现的呢
    from flask import request
    # request是LocalProxy类的实例对象,参数是一个偏函数
    request = LocalProxy(partial(_lookup_req_object, 'request'))
    # _lookup_req_object
    top = _request_ctx_stack.top
    # return self._local.stack[-1] # 返回ctx
    if top is None:
        raise RuntimeError(_request_ctx_err_msg)
    return getattr(top, name)  # 返回 ctx.request
    # LocalProxy的实例化
    def __init__(self, local, name=None): # name没传
        # __slots__ 限制了实例对象的 属性__lacal
        # self._LocalProxy__local = local => 偏函数
        object.__setattr__(self, '_LocalProxy__local', local) # 相当于 self.__local = local
        # self.__name__ = None
        object.__setattr__(self, '__name__', name)
        if callable(local) and not hasattr(local, '__release_local__'):
            # "local" is a callable that is not an instance of Local or
            # LocalManager: mark it as a wrapped function.
            object.__setattr__(self, '__wrapped__', local)
    # 当我们调用request.method等方法的时候就是走的时LocalProxy这个类的__getattr__方法
    return getattr(self._get_current_object(), name) # name => method
    # _get_currnet_object(),由于是调用类内方法,可以直接使用私有变量,所以可以用self.__local取值
    if not hasattr(self.__local, '__release_local__'):
        return self.__local()  # self.__local,引用的就是变形的结果,self._LocalProxy__local ==> 偏函数
    return getattr(self.__local, self.__name__)
    # getattr(self._get_current_object(), name) 相当于去ctx.request.method
    # 就跟我们上面的取值方式一样了,也就是说通过LocalStack方法去Local中取ctx对象,
    # 然后通过getattr 找到ctx.request~~~
    # 也就是说这个LocalProxy就是一个帮助我们取值的代理~让我们的取值变的更加简单
    # 这个代理通过偏函数来绑定参数
    # ctx中封装了request,以及session~只不过到这里我们的session依然是空的

    session的原理

# Session的实现原理
    # 首先请求进来的时候在Local中存放了ctx对象~这个对象里的session是None~~
    # 当我们走完这个_reqeust_cts_stack.push(ctx)后,我们看它走了什么
    if self.session is None:
        # session_interface 赋值为 SecureCookieSessionInterface的实例对象
        session_interface = self.app.session_interface
        self.session = session_interface.open_session(
            self.app, self.request # self=>ctx
        )

        if self.session is None:
            self.session = session_interface.make_null_session(self.app) # 如果cookie为,session被赋值为一个空字典

    # SecureCookieSessionInterface类的open_session方法
    def open_session(self, app, request):
        # s是我们加密解密方法
        s = self.get_signing_serializer(app)
        if s is None:
            return None
        # 从cookie中获取数据
        val = request.cookies.get(app.session_cookie_name)
        if not val:
            return self.session_class()
        # 获取配置信息的超时时间
        max_age = total_seconds(app.permanent_session_lifetime)
        try:
            # 解密cookie
            data = s.loads(val, max_age=max_age)
            # 把解密号的数据转成字典返回赋值给了ctx.session
            return self.session_class(data)
        except BadSignature:
            return self.session_class()

    # 请求进来把ctx放入Local中后,从前端解密了cookie,然后把解密数据好的数据给了self.session
    response = self.full_dispatch_request()
    # full_dispatch_request() 中执行了finalize_request
    return self.finalize_request(rv)
    # finalize_request方法中与session有关的关键代码
    response = self.process_response(response)
    # process_response方法中调用了save_session
    if not self.session_interface.is_null_session(ctx.session):  # 相当于判断前端是否有传cookie
        self.session_interface.save_session(self, ctx.session, response)
    # save_session方法执行了设置cookie操作
    response.set_cookie(
        app.session_cookie_name,
        val,
        expires=expires,
        httponly=httponly,
        domain=domain,
        path=path,
        secure=secure,
        samesite=samesite
    )
    # 总结:从cookie中获取数据~解密存入session,请求走的时候,把session中数据取出来,加密, 给响应设置cookie
    # 那么我们平时在视图中设置删除session的值~原来跟request是一样的,通过代理去修改Local中的数据

    g对象

# 在应用上下文封装了g对象,那么这个g对象到底是什么
    请求进来会为每个请求在Local中建立一个独立空间,也就是在应用上下文的Local对象中建立了一个g对象,当请求走的时候,就会删除
    g对象一般情况用于before_request中设置值,只为这一次请求建立全局变量
    g的生命周期是请求进来到走

# 注意:在我们重定向的时候还可以取g的值

对比session和全局变量
    全局变量:是在项目启动创建的,无论多少请求进来都可以访问全局变量
    session:保存在cookie中,所以下次请求来的时候cookie中还会带着数据

  一个demo

# demo
from flask import Flask, request, session, g, current_app
from flask.globals import _request_ctx_stack


app = Flask(__name__)


@app.before_request
def auth():
    g.xxx = "alex"


@app.route("/")
def index():
    ctx = _request_ctx_stack.top # 取ctx对象
    # ctx.request
    print(ctx.request.method) # 跟request.method一样
    print(current_app) # 返回ctx.app 就是当前的app => flask的实例对象
    # request.method
    # request --> LocalProxy(偏函数)
    # request.xxx --> LocalProxy  __getattr__
    # __getattr__  --> getattr(偏函数的执行,xxx )
    # 偏函数-->  _request_ctx_stack.top.request

    # g.xxx = "nezha"
    print(g.xxx)
    return "INDEX"

@app.route("/user")
def user():
    # print(g.xxx)
    return "USER~~"


if __name__ == '__main__':
    app.run()
原文地址:https://www.cnblogs.com/lianyeah/p/10186106.html