• Flask基础


    Flask基础

    对于一个web框架而言,一定要有的就是路由,视图,模板语法

    对于一个没有见到的框架,从路由入口,然后走视图

    1.谈谈你对django和flask的认识?
    	1.django是一个大而全的框架,内置了很多的组件,比如分页,缓存,orm...
        2.flask轻量级的,可扩展性强,可定制性强
        3.两个框架你选择哪个?因人而异
    2.flask和django最大的不同点:
    	1.request/session(django中的session是依附在request对象中传递进来的,但是flask是导入的)
    3.flask知识点
    	- 可以设置静态文件和模板(实例化flask对象的时候配置的...)
        - 路由装饰器,对象点route  @app.route('/index',methods=['GET','POST'])
        - 请求相关的
        	request.form
            request.args
            request.method
        - 响应
        	render
            redirect
        -session 
        	# 放值
            session['xx'] = 123
            # 取值
            session.get('xx')
    

    内容详细

    知识点:
    	给你一个路径,如'xx.xx.ClassFoo',找到这个类获取里面的静态字段
        
        import importlib
        # 如何找到这个类
        path = 'settings.Foo'
        # 先拿到这个路径,然后利用importlib
        p, c = path.rsplit('.',maxsplit=1)
        m    = importlib.import_module(p)
        cls  = getattr(m,c)  # 路径, 类名
        # print(cls)
        print(dir(cls))  # 拿到的是这个类所有的方法
        for k in dir(cls):
            if k.isupper():  
                print(k)  # DEBUG
                v = getattr(cls,k)  # 第一个参数是你要对谁操作,第二个是你要拿谁的值
                print(v)  # True
    
    

    配置文件

    	print(app.config)   # 配置文件的查看
        
        # 修改配置文件的方法一
        	app.config['DEBUG'] = True
        	app.config['DEBUG'] = True
        	app.config['DEBUG'] = True
        	app.config['DEBUG'] = True
            
        # 修改配置文件的方法二
        app.config.from_object('settings.Foo')
        
        # 去看settings中的Foo这个类
        class Foo:
            DEBUG = True
            
       	# 方法二修改配置文件的源码
        
            def from_object(self, obj):
                if isinstance(obj, string_types):  # obj就是'settings.Foo'
                    obj = import_string(obj)  # 根据字符串的形式去导入他
                    # obj 就是那个传进来的类,在这里就是我们的那个Foo
                for key in dir(obj):
                    if key.isupper():
                        # self就是配置文件对象,封装了所有的配置文件
                        self[key] = getattr(obj, key)  # 在这一步进行了修改
                        
            def import_string(import_name, silent=False):  # import_name='settings.Foo'
                import_name = str(import_name).replace(":", ".")  
                try:
                    try:
                        __import__(import_name)
                    except ImportError:
                        if "." not in import_name:
                            raise
                    else:
                        return sys.modules[import_name]
    
                    module_name, obj_name = import_name.rsplit(".", 1)  # 获取路径和类
                    module = __import__(module_name, globals(), locals(), [obj_name])
                    # __import__类似于importlib.import_moudle(module)  
                    try:
                        # 在这一步将获取到的类传了出去,拿到的是路径和类名
                        return getattr(module, obj_name)  
                    except AttributeError as e:
                        raise ImportError(e)
    
                except ImportError as e:
                    if not silent:
                        reraise(
                            ImportStringError, ImportStringError(import_name, e), 									sys.exc_info()[2]
                        )
                        
       	# 配置分为开发环境和线上环境
            class Base(object):
                xxx = '123'
            class Pro(Base):  # 线上环境
                DEBUG = False
            class Dev(Base):  # 开发环境
                DEBUG = True
        # 后面我们上线了,只需要更改app.config.from_object('settings.Dev')就好,后面如果还有公用的,只需要来一个继承就好
    

    路由系统

    	- endpoint   # 反向生成url,默认是函数名
    	- url_for('endpoint')  # 就是反向解析,类似django的reverse
        # 如果没有参数,反向生成路由
        url_for('endpoint')/url_for('index',nid=999)
      
        # 动态路由,得有参数接收一下
        @app.route('/index/<int:nid>', methods=['GET', 'POST'])
        def index(nid):
            print(nid)
            return 'Index'
      # 注意
    	'''
    		endpoint就是反向解析的name,不定义就是函数名
    		路由中可以跟参数,这个参数中间不要留空格,这个值是一个动态的,int表示的是什么类型
    		什么都不写就是字符串,但是不允许正则表达式
    	'''
    

    视图

    FBV和CBV
    FBV
    @app.route('/index/<int:nid>', methods=['GET', 'POST'])
    def index(nid):
        print(nid)
        # 反向生成url
        print(url_for('/index',nid=999)) 
        return 'Index'
    

    请求相关的数据

    '''
        flask是直接引用,django是作为一个参数,内部处理机制完全不同
        django请求数据是一个个的传递(门口有个人给了一塌子钱,同学一个个的传递过来)
        flask(直接扔到桌子上,自己导入去取)
    '''
    	@app.route('/index/<int:nid>', methods=['GET', 'POST'])
        def index(nid):
            print(nid)
            # 请求相关信息
            '''
            request.method  
            request.args
            request.form
            request.value
            request.cookies
            request.headers
            request.path
            request.full_path
            request.script_root
            request.url
            request.base_url
            request.host
            rrequest.files  # 上传文件
            obj = request.files['the_file_name']
            obj.save('/var/www/uploads/' + secure_filenaem(f.filename))  # 将上传的文件保存起来
            '''
          
            return 'Index'  
        
    

    响应

      	# 响应相关的数据
         	   # Json格式也可以返回
               # dic = {'k': v1, 'k2': v2}
               # 返回json格式的有这几种
               # 1.json.dumps(dic)
               # 2.jsonify(dic)
            '''
                return 'Index' 
                return render_template()
                return redict()
                return json.dumps(dic)
                return jsonify(dic)   和django的JsonResponse类似
              
            '''
        定制响应头/cookie
        # 响应头在哪写
        # return 'Index'
    	# 对于上面的这种数据,我们如果想要设置响应头,那么我们可以导入make_response,然后进行封装
        obj = make_response('Index')
        obj.headers['xxx'] = '123'
        return obj
    	# 对于剩下的几种格式都一样,我们都可以设置响应头
        obj = make_response(jsonify(dic))
        obj.headers['xxx'] = '123'
        return obj
    	# 我们可以设置响应头,那么也可以设置cookie
        obj = make_response('Index')
        obj.headers['xxx'] = '123'
        obj.set_cookie('key','value')
        return obj
    

    示例程序一

    from flask import Flask,render_template,redirect,session,url_for
    
    app = Flask(__name__)
    app.config.from_object('settings.DevelopmentConfig')
    
    STUDENT_DICT = {
        1 : {'name': 'mm', 'age': 2, 'gender': 'male'},
        2 : {'name': 'cc', 'age': 18, 'gender': 'female'},
        3 : {'name': 'yy', 'age': 18, 'gender': 'female'},
    }
    
    @app.route('/index')
    def index():
        return render_template('index.html',stu_dic=STUDENT_DICT)
    
    @app.route('/info/<int:nid>')
    def detail(nid):
        info = STUDENT_DICT[nid]
        return render_template('detail.html',info=info)
    
    
    @app.route('/delete/<int:nid>')  # 传过来是字符串,int还有个作用转成int
    def remove(nid):  # 只需要保证路由一致就好,具体函数名可以不用管
        STUDENT_DICT.pop(nid)
        print(STUDENT_DICT)
        return redirect(url_for('index'))  # 直接跟函数名就好
    
    
    if __name__ == '__main__':
        app.run()
    

    html页面

    <!--index页面-->
    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Title</title>
        </head>
        <body>
        	<h1>学生列表</h1>
            <table border="1">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>姓名</th>
                        <th>年龄</th>
                        <th>性别</th>
                        <th>选项</th>
                    </tr>
                </thead>
                <tbody>
                    {% for key,value in stu_dic.items() %}
                    <tr>
                        <td>{{ key }}</td>
                        <td>{{ value['name'] }}</td>
                        <td>{{ value.get('ae','默认') }}</td>
                        <td>{{ value.gender }}</td>
                        <td>
                            <a href="/info/{{key}}">详细信息</a>
                            <a href="/add/{{key}}">添加</a>
                            <a href="/delete/{{key}}">删除</a>
                        </td>
                    </tr>
                    {% endfor %}
                </tbody>
            </table>
        </body>
    </html>
    
    <!--详细信息页面-->
        <!DOCTYPE html>
        <html lang="en">
            <head>
                <meta charset="UTF-8">
                <title>Title</title>
            </head>
            <body>
                <h1>学生详细</h1>
                <ul>
                    <!--和python语法一模一样-->
                    {% for item in info.values() %}
                    <li>{{item}}</li>
                    {% endfor %}
    
                </ul>
            </body>
        </html>
    <!--和python的使用一模一样,python怎么用他就怎么用-->
    

    安全登录版本一

    @app.route('/login',methods=['GET', 'POST'])  # method默认是GET
    def login():
        if request.method == 'GET':
            return render_template('login.html')
        user = request.form.get('user')
        pwd = request.form.get('password')
        if user == 'mcc' and pwd == '123':
            session['user'] = user  # 将东西存到了cookie中
            return redirect('/index')
        return render_template('login.html', **{'error': '用户名或者密码错误'})
    
    
    @app.route('/index')
    def index():
        if session.get('user'):
            return render_template('index.html',stu_dic=STUDENT_DICT)
        return redirect(url_for('login'))
    # 方式一:类似上述模式实现登录认证, 但是上述方式每一个函数都要写,我们用装饰器来做
    

    知识点

    # 知识点一
    import functools
    
    def auth(func):
        # @functools.wraps(func)  # django是wraps(func)
        @functools.wraps(func)  
        def inner(*args, **kwargs):  # 其实最后执行的是inner
            ret = func(*args, **kwargs)
            return ret
        return inner
    
    @auth
    def login():
        print('login')
    
    @auth
    def detail():
        print('detail')
    
    print(login.__name__)  # 不加 @functools.wraps(func)的时候是inner
    print(detail.__name__)  # 加 @functools.wraps(func)的时候是detail
    
    # 知识点二
    endpoint默认是函数名,那么如果同名了,怎么解决
    def auth(func):
        def inner(*args, **kwargs):  # 其实最后执行的是inner
            ret = func(*args, **kwargs)
            return ret
        return inner
    @auth
    def login():
        print('login')
    @auth
    def detail():
        print('detail')
    # 如果我们不加@functools.wraps(func)程序报错了
    AssertionError: View function mapping is overwriting an existing endpoint function: inner
    # 这时候我们打印这个的函数发现我们的名字都是inner,没有指定endpoint,此时所有的都是inner
    print(login.__name__)  # inner
    print(detail.__name__)  # inner
    
    # 看看源码如何实现的?
       def route(self, rule, **options):  # 入口  rule='/index'
            def decorator(f):  # f就是我们传进来的func
                endpoint = options.pop("endpoint", None)  # 别名
                self.add_url_rule(rule, endpoint, f, **options)  # 走这一步添加url
                return f
            return decorator
    	@setupmethod
        def add_url_rule(
            self,
            rule,
            endpoint=None,
            view_func=None,
            provide_automatic_options=None,
            **options
        ):
            if endpoint is None:
                endpoint = _endpoint_from_view_func(view_func)  # 不写默认是函数名
            options["endpoint"] = endpoint
            methods = options.pop("methods", None)  # GET/POST
            if methods is None:
                methods = getattr(view_func, "methods", None) or ("GET",)
            if isinstance(methods, string_types):
                raise TypeError(
                    "Allowed methods have to be iterables of strings, "
                    'for example: @app.route(..., methods=["POST"])'
                )
            methods = set(item.upper() for item in methods)
    
            # Methods that should always be added
            required_methods = set(getattr(view_func, "required_methods", ()))
    
            # starting with Flask 0.8 the view_func object can disable and
            # force-enable the automatic options handling.
            if provide_automatic_options is None:
                provide_automatic_options = getattr(
                    view_func, "provide_automatic_options", None
                )
    
            if provide_automatic_options is None:
                if "OPTIONS" not in methods:
                    provide_automatic_options = True
                    required_methods.add("OPTIONS")
                else:
                    provide_automatic_options = False
    
            # Add the required methods now.
            methods |= required_methods
    
            rule = self.url_rule_class(rule, methods=methods, **options)
            rule.provide_automatic_options = provide_automatic_options
            '''
            {
                endpoint:函数
                ‘endpoint':inner函数        }
            '''
            self.url_map.add(rule)
            if view_func is not None:  # view_func我们自己的函数,就是inner
                # view_functions认为他就是一个字典,上面看
                old_func = self.view_functions.get(endpoint) 
                if old_func is not None and old_func != view_func:    # 不加functools.wrap(func)的报错信息
                    raise AssertionError(
                        "View function mapping is overwriting an "
                        "existing endpoint function: %s" % endpoint
                    )
                self.view_functions[endpoint] = view_func  # 第一次进来‘函数名’=inner
            
     # 装饰器的先后顺序
     # 装饰器需要放在路由的下面,这样子一进来先做的是路由的匹配,之后才会走装饰器,如果装饰器放在路由的上面,那么一进来就会连同下面的函数一起去进行装饰,不可理,所以会将装饰器放在视图函数的下面
    

    装饰器适用--版本二

    # 方式二:类似上述模式实现登录认证, 但是上述方式每一个函数都要写,我们用装饰器来做
    def auth(func):
        @functools.wraps(func)
        def inner(*args, **kwargs):  # 其实最后执行的是inner
            if session.get('user'):
                ret = func(*args, **kwargs)
                return ret
            return redict(url_for('login'))
        return inner
    
    @app.route('/index')
    @auth
    def index():
        return render_template('index.html', stu_dic=STUDENT_DICT)
    # 应用场景:比较少的函数中需要额外添加功能
    

    版本三****实现权限管理

    基于before_request
    # 记住,当before_request返回的是None,是正常往下走,返回其他都是阻拦程序的运行
    @app.before_request
    def mmmmrrrr():
        # print('before_request')
        if request.path == '/login':
            return None
        return 'gong'  # 当请求不是login的时候就会直接返回
    
    # 所以我们直接使用这种方式,给函数批量的加入登录认证
    @app.before_request
    def mmmmrrrr():
        # print('before_request')
        if request.path == '/login':
            return None
        if session.get('user'):
            return None
        return redirect(url_for('login'))
    

    1566713023131

    模板的渲染

    --基本数据类型:可以执行python的语法,如:dict.get() list['xx']
    --传入函数
    -django,自动执行
    -flask,不自动执行
    --全局定义函数,如下
    --模板继承
    同django一模一样,{% block %}
    --静态模板的导入
    include
    --宏定义,就是自定义一个函数,后面可以多次使用
    --安全方式的展示
    前端: {{ u|safe }}
    后端: Makeup()

    <!--模板支持类型-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <!--两种方式都可以支持-->
    {{ users.0 }}
    {{ users[0] }}
    {{ txt }}
    {{ func() }}  # 和django的区别是django会自动加括号调用,而flask不会
    {{ sb(6,3) }}  # 模板全局设置函数的方式一
    {{ 1|aa(2, 4) }}  # 模板全局设置函数的方式二
    {% if 1|aa(2, 4) %}  # 这个可以放在if的后面作为条件
        <div>999</div>
    {% else %}
        <div>7777</div>
    {% endif %}
    </body>
    </html>
    
    <!--模板继承-->
    <!--母版-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>模板</h1>
    {% block content %}
    {% endblock %}
    </body>
    </html>
    
    <!--继承母版-->
    {% extends "base.html"%}
    <!--两种方式都可以支持-->
    {% block content %}
    {{ users.0 }}
    {{ users[0] }}
    {{ txt }}
    {{ sb(6,3) }}
    {{ 1|aa(2, 4) }}
    {% endblock %}
    
    <!--宏-->
    {% extends "base.html"%}
    <!--两种方式都可以支持-->
    {% block content %}
    {% macro aaa(name, type='text', value='') %}
    <h1>宏</h1>
    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
    <input type="submit" value="提交">
    {% endmacro %}
    {{aaa('n1')}}    第一次调用
    <!--可以将这种函数放在一个单独的页面中,在使用的时候直接加括号调用就好,类似django的inclution_tag-->
    {{aaa('n2')}}    第二次调用
    {% endblock %}
    
    <!--静态文件的导入-->
    {% include 'form.html %}
    

    模板后端的书写

    from flask import Markup   # 这个就是django中的mark_safe
    @app.template_global()
    def sb(a, c):
        # {{sb(6, 3)}}
        return a+c
    
    @app.template_filter()
    def aa(a, b, c):
        # {{1 | aa(2, 4)}}  区别就是这个放在if后面做条件
        return a+b+c
    
    @app.route('/tpl')
    def tpl():
        contex = {
            'users': ['longya', 'yyy', 'mcc'],
            'txt': Markup('<input type="text" />')
        }
        return render_template('tpl.html', **contex)
    

    session

    1566820542991

    flask是将这个session对数据进行加密,保存到用户的浏览器的cookie中,其实session就是一个字典
    '''
    当请求刚进来的时候,flask会帮我们读取cookie中session对应的值,将该值解密并反序列成为一个字典,放入内存,以便视图函数使用。
    当请求结束时,flask会读取内存中字典的值,进行序列化+加密,写入到用户的cookie中
    '''
    # 使用 
    @app.route('/sess')
    def sess():
        session['k1'] = '123'
        session['k2'] = '234'
        del session['k1']
        return 'session'
    
    
    

    闪现

    # 在sesson种存储一个数据,读取时通过通过pop将数据移除,只能存储取值一次
    # 普通的session设置值,然后发现通过这种方式,session中的值会永久存在,如果我们设置了好多的session的话,那么就会一直存储下来
    @app.route('/page1')
    def page1():
        session['k1'] = '123'
        return 'session'
    
    
    @app.route('/page2')
    def page2():
        print(session.get('k1'))
        return 'ok'
    # 基于上面的这种机制,flask引入了闪现,也就是flash,他会将session设置的值变成一个临时存储的值
    
    from flask import session,get_flashed_messages
    
    @app.route('/page1')
    def page1():
        flash('临时存储的数据','error')  # 分类是error
        return 'session'
    
    @app.route('/page2')
    def page2():
        print(get_flashed_messages(category_filter=['error']))  # 取分类是category_filter
        return 'ok'
    
    # flash内部源码实现原理
    def flash(message, category="message"):
        # 从session中取值,取不到就给一个默认的空列表[]
        flashes = session.get("_flashes", [])  [1,2,3]
        # 支持分类,所以将分类和分类信息加入到列表中
        flashes.append((category, message))
        # 通过session设置从session中取到的值,所以设置的就是一个列表的值
        session["_flashes"] = flashes  # {'_flashes':[1,2,3]}
        # 信号暂时不用看
        message_flashed.send(
            current_app._get_current_object(), message=message, category=category
        )
        
    # get_flashed_messages内部源码实现原理
    def get_flashed_messages(with_categories=False, category_filter=()):
        # 
        flashes = _request_ctx_stack.top.flashes
        if flashes is None:
            _request_ctx_stack.top.flashes = flashes = (
                session.pop("_flashes") if "_flashes" in session else []
            )   # 通过session给pop出来了,出来的要么是个空,要么是一个空列表
        if category_filter:
            flashes = list(filter(lambda f: f[0] in category_filter, flashes))
        if not with_categories:
            return [x[1] for x in flashes]
        return flashes
    

    中间件

    # 需求,想在程序启动之前做一些操作,启动之后做一些操作,通过中间件的形式
    '''
    我们知道的是,当一个请求开始执行的时候,基于wsgi,走的是run_simple(localhost,port,执行的函数),
    那么flask内部是怎么实现?
    
    '''
    from flask import Flask
    app = Flask(__name__)
    
    @app.route('/index')
    def index():
        return 'index'
    
    if __name__ == '__main__':
        app.run()  # 开始启动flask,开始看执行流程
    
    # 内部源码开始啦
        def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
            ......# 感兴趣的可以看看,我们只看这下面的这一行
            from werkzeug.serving import run_simple
            try:
                # 走的是run_simple方法,第三个参数就是当前对象,就是app,传的是对象的时候,会自动加括号调用,那么对象加括号调用走的是类的__call__方法。
                run_simple(host, port, self, **options) 
            finally:
                self._got_first_request = False
        # 直接搜索当前的__call__方法
         def __call__(self, environ, start_response):
            # 执行的就是对象自己的wsgi_app方法
            return self.wsgi_app(environ, start_response)
    # 所以我们想要实现在请求开始之前就做一些操作,我们可以考虑从这几个方面下手
    '''
    1.直接修改源码      ---不好点就是同事也会拉取到你的代码,所以淘汰
    2.重写__call__方法   --可以尝试
    3.提供一种新的方法,具体请看下方
    '''
    # 第三种方法书写
    class MiddleWare(object):
        def __init__(self, old):
            self.old = old
    
        def __call__(self, *args, **kwargs):
            print('来人了')
            return self.old(*args, **kwargs)
    
    
    if __name__ == '__main__':
        # 将对象的wsgi_app变成了自定义类的对象,此时的old就是原来的wsgi_app
        app.wsgi_app = MiddleWare(app.wsgi_app)
        # 那么后面在执行wsgi_app的时候,发现这个方法已经被我们给覆盖了,就会走我们书写的这个方法,这个时候他已经是一个对象了,对象+()走的就是call方法,那么此时我们在call方法里面做的操作就达到了在不修改源码的情况下增加了新功能。
        app.run()
    # 这就是flask的中间件的执行,但是用的不多,主要用的还是before_request这些 
    

    特殊的装饰器****

    before_request  ***
    after_request   ***
    template_global
    template_filter
    before_first_request   # 第一次请求的时候执行的,之后的时候都不会执行这个装饰器
    errorhandler(404)  # 用来返回错误日志的
    
    # django1.9版本之前,中间件的执行顺序也是和flask一样,都是在request的时候返回一个return,就会在返回的时候从最后一个response返回这个请求,在1.9版本之后变成了从当前返回
    @app.before_request
    def re2():
        print('re2')
        
    @app.before_request
    def re3():
        print('re3')
    
    @app.after_request
    def res1(response):
        print('res1')
        return response
    
    @app.after_request
    def res3(response):
        print('res3')
    # TypeError: after_request() takes 0 positional arguments but 1 was given  是因为没有接收response
    # TypeError: 'NoneType' object is not callable  是因为没有返回response
        return response
    
    # 定制错误页面
    @app.errorhandler(404)  # 用来捕获404错误的,返回一个自定义的错误页面
    def error(response):
        print('303')
        return 'not found'
    
    @app.route('/index')
    def index():
        return 'index'
    if __name__ == '__main__':
        app.run()
    # 如果有多个request和response,那么执行的顺序分为request和response
    # request,按照书写顺序从上到下依次执行
    # response,按照书写顺序从下到上依次执行,进行了一个反转
    
  • 相关阅读:
    高精度模板(未完待续)
    $CH0201$ 费解的开关
    $POJ2288$ $Islands$ $and$ $Bridges$
    luoguP1445 [Violet]樱花
    P3694 邦邦的大合唱站队
    [NOI2009]管道取珠
    [AHOI2006]基因匹配
    luogu P3411 序列变换
    HNOI2001 产品加工
    牛客ACM赛 B [小a的旅行计划 ]
  • 原文地址:https://www.cnblogs.com/mcc61/p/11426121.html
Copyright © 2020-2023  润新知