• 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()
  • 相关阅读:
    块级标签与预格式化文本标签----------大多数XHTML可以表示为两种类型的标签:块标签(block tag)和内联标签(inline tag)
    下拉框与下拉框之间的联动效果
    下拉框与文本框之间的转换
    设置密码是否为可见
    html表单
    HTML基础2——综合案例3——创建考试报名表格
    HTML基础2——综合案例2——复杂的嵌套列表
    java配置、IntelliJ IDEA Ultimate激活、
    字节流转字符流OutputStreamWriter、InputStreamReader,关闭流的方法
    文件字节流、字符流、缓冲字节流、缓冲字符流、数据流
  • 原文地址:https://www.cnblogs.com/lianyeah/p/10186106.html
Copyright © 2020-2023  润新知