zoukankan      html  css  js  c++  java
  • 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()
  • 相关阅读:
    CF1051F The Shortest Statement 题解
    CF819B Mister B and PR Shifts 题解
    HDU3686 Traffic Real Time Query System 题解
    HDU 5969 最大的位或 题解
    P3295 萌萌哒 题解
    BZOJ1854 连续攻击游戏 题解
    使用Python编写的对拍程序
    CF796C Bank Hacking 题解
    BZOJ2200 道路与航线 题解
    USACO07NOV Cow Relays G 题解
  • 原文地址:https://www.cnblogs.com/lianyeah/p/10186106.html
Copyright © 2011-2022 走看看