Flask基础

   开篇引入

1.django和flask的相同点和不同点?

共同点:都是基于wsgi的
不同点:
(1) django是一个大而全的框架,提供了方便内置的框架,orm,admin,分页,form,model_form,缓存,信号等很多方便的组件,
我们只要在配置文件中一修改就可以使用。
(2) flask是一个短小精悍,可扩展性非常强,flask适合开发小型的网站,也可以开发大型网站,因为它给我们提供有许多第三方组件,我们就可以结合这些第三方组件集成一个像django一样拥有很多功能的web框架。可定制性非常强。
(3) flask和django最大的不同点:request/session
flask是直接导入的,request在全局起作用。
django是依附于request参数,通过参数传导。
两个框架没有优劣之分,具体应用看实际需求。

2.什么是wsgi?

web服务网关接口,wsgi是一个协议,实现该写一个的模块:
- wsgiref
- werkzeug
实现协议的模块本质上就是socket服务端用于接收用户请求,并处理。
一般web框架基于wsgi实现,这样实现关注点分离,主要负责业务处理。
 1 from wsgiref.simple_server import make_server
 2 
 3 def run_server(environ, start_response):
 4 start_response('200 OK', [('Content-Type', 'text/html')])
 5 return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]
 6 
 7 
 8 if __name__ == '__main__':
 9 httpd = make_server('127.0.0.1', 8000, run_server)
10 httpd.serve_forever()
wsgiref示例
1 from werkzeug.wrappers import Response
2 from werkzeug.serving import run_simple
3 
4 def run_server(environ, start_response):
5     response = Response('hello')
6     return response(environ, start_response)
7 
8 if __name__ == '__main__':
9     run_simple('127.0.0.1', 8000, run_server)
werkzeug示例
from werkzeug.wrappers import Response
from werkzeug.serving import run_simple

class Flask(object):
    def __call__(self,environ, start_response):
        response = Response('hello')
        return response(environ, start_response)

    def run(self):
        run_simple('127.0.0.1', 8000, self)



app = Flask()

if __name__ == '__main__':
    app.run()
flask源码入口

  基础知识

安装

  pip3 install flask

特点: 短小精悍、可扩展强 的一个Web框架。

特色:上下文管理机制
wsgi:web service getway interface web服务网管接口
依赖wsgi:werkzurg

from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple

def run(environ,start_response):
    response = Response('hello')
    return response(environ, start_response)

if __name__ == '__main__':
    run_simple('localhost', 4000, run)
werkzurg示例一
from werkzeug.wrappers import Request, Response

@Request.application
def hello(request):
    return Response('Hello World!')

if __name__ == '__main__':
    from werkzeug.serving import run_simple
    run_simple('localhost', 4000, hello)
werkzurg示例2

   一、配置文件

1.使用:所有配置都在app.config中

  • 方式一:app.config['DEBUG'] = True

    PS: 由于Config对象本质上是字典,所以还可以使用app.config.update(...)

  • 方式二:app.config.from_object('类的路径')
  • 方式三:app.config.from_pyfile('py文件路径')
class Config(object):
    DEBUG = False
    TESTING = False
    DATABASE_URI = 'sqlite://:memory:'


class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'


class DevelopmentConfig(Config):
    DEBUG = True


class TestingConfig(Config):
    TESTING = True
settings.py

2.实现原理
  指定一个字符串(类的路径/文件路径),importlib-->getattr 找到这个类/模块把这个类/模块的所有字段(大写)一个一个获取,获取的时候判断isupper(),给一个路径'settings.Foo',可以找到类并获取其中大写的静态字段。

import importlib

path = 'settings.Foo'

p,c = path.rsplit('.',maxsplit=1)
m = importlib.import_module(p)
# m = __import__(p)
cls = getattr(m,c)


for key in dir(cls):
    if key.isupper():
        print(key,getattr(cls,key))
实现原理.py
'ENV':                                  None,
'DEBUG':                                None,                        是否开启DEBUG模式
'TESTING':                              False,                        是否开启测试模式
'PROPAGATE_EXCEPTIONS':                 None,
'PRESERVE_CONTEXT_ON_EXCEPTION':        None,
'SECRET_KEY':                           None,
'PERMANENT_SESSION_LIFETIME':           timedelta(days=31),
'USE_X_SENDFILE':                       False,
'SERVER_NAME':                          None,
'APPLICATION_ROOT':                     '/',
'SESSION_COOKIE_NAME':                  'session',
'SESSION_COOKIE_DOMAIN':                None,
'SESSION_COOKIE_PATH':                  None,
'SESSION_COOKIE_HTTPONLY':              True,
'SESSION_COOKIE_SECURE':                False,
'SESSION_COOKIE_SAMESITE':              None,
'SESSION_REFRESH_EACH_REQUEST':         True,
'MAX_CONTENT_LENGTH':                   None,
'SEND_FILE_MAX_AGE_DEFAULT':            timedelta(hours=12),
'TRAP_BAD_REQUEST_ERRORS':              None,
'TRAP_HTTP_EXCEPTIONS':                 False,
'EXPLAIN_TEMPLATE_LOADING':             False,
'PREFERRED_URL_SCHEME':                 'http',
'JSON_AS_ASCII':                        True,
'JSON_SORT_KEYS':                       True,
'JSONIFY_PRETTYPRINT_REGULAR':          False,
'JSONIFY_MIMETYPE':                     'application/json',
'TEMPLATES_AUTO_RELOAD':                None,
'MAX_COOKIE_SIZE': 4093,
})
默认配置文件

  二、路由系统

重点:基于装饰器实现

技术点:-functools.wraps(func):保留原函数的原信息

装饰器(带参数)

  • methods=['GET','POST']
  • endpoint:反向生成url
  • url_for(endpoint)

自定义装饰器放下面
  注意事项:

  • - endpoint默认是函数名
  • - 不要让endpoint重名,如果重名函数也一定要相同。
  • - 加装饰器时要加functools.wraps(func) + functools.partial
# -*- coding: utf-8 -*-

"""
@Datetime: 2018/12/21
@Author: Zhang Yafei
"""
"""1.装饰器"""
from functools import wraps


def auth(func):
    @wraps(func)   # 伪装的更彻底
    def inner(*args,**kwargs):
        print('')
        ret = func(*args,**kwargs)
        print('')
        return ret
    return inner


@auth
def index():
    print('index')


@auth
def detail():
    print('index')


print(index.__name__)
print(detail.__name__)

"""2.endpoint默认是函数名"""
装饰器注意点

路由设置的两种方式 

方式一
    @app.route('/xxx')
        def index():
            return "index"

方式二
    def index():
        return "index"
    app.add_url_rule("/xxx",None,index)
路由设置的两种方式
-动态路由
    /index/<int:nid>
    def index(nid):
        print(nid)
        return 'index'
动态路由
1 rule,                       URL规则
2 view_func,                  视图函数名称
3 endpoint=None,              名称,用于反向生成URL,即: url_for('名称')
4 methods=None,               允许的请求方式,如:["GET","POST"]
5 strict_slashes=None,        对URL最后的 / 符号是否严格要求,
6 redirect_to=None,           重定向到指定地址
7 
8 defaults=None,              默认值,当URL中无参数,函数需要参数时,使用defaults={'k':'v'}为函数提供参数
9 subdomain=None,             子域名访问
参数
from flask import Flask,url_for

app = Flask(__name__)

# 步骤一:定制类
from werkzeug.routing import BaseConverter
class RegexConverter(BaseConverter):
    """
    自定义URL匹配正则表达式
    """

    def __init__(self, map, regex):
        super(RegexConverter, self).__init__(map)
        self.regex = regex

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

    def to_url(self, value):
        """
        使用url_for反向生成URL时,传递的参数经过该方法处理,返回的值用于生成URL中的参数
        :param value:
        :return:
        """
        val = super(RegexConverter, self).to_url(value)
        return val

# 步骤二:添加到转换器
app.url_map.converters['reg'] = RegexConverter

"""
1.用户发送请求
2.flask内部进行正则匹配
3.调用to_python(正则匹配的结果)方法
4.to_python方法的返回值会交给视图的函数
"""

# 步骤三:使用自定义正则
@app.route('/index/<reg("d+"):nid>')
def index(nid):
    print(nid,type(nid))
    print(url_for('index',nid=987))
    return 'index'


if __name__ == '__main__':
    app.run()
自定义正则
@main.route('/data_list/<table_name>')
def data_list(table_name):
    df = reader.enable_admins[table_name]
    page_obj = Pagination(df['data'].shape[0], request.args.get('p'),per_page_num=5)
    page_str = page_obj.page_str(base_url=url_for('main.data_list', table_name=table_name))
    admin_class = df['admin_class']
    data_list = df['data'][page_obj.start:page_obj.end]
    context = {'table_name':table_name, 'data_list':data_list,
               'page_obj':page_obj, 'page_str':page_str,
               'admin_class':admin_class,
               }
    return render_template('data_list.html', **context)
动态路由2

  三、视图

 视图:FBV和CBV 

 技术点:反射

视图函数中获取request或session
    方式一:直接找LocalStack获取
            from flask.globals import _request_ctx_stack
            print(_request_ctx_stack.top.request.method)
            
    方式二:通过代理LocalProxy(小东北)获取
            from flask import Flask,request
            print(request.method)
FBV
import functools
from flask import Flask,views
app = Flask(__name__)


def wrapper(func):
    @functools.wraps(func)
    def inner(*args,**kwargs):
        return func(*args,**kwargs)

    return inner


class UserView(views.MethodView):
    methods = ['GET']
    decorators = [wrapper,]

    def get(self,*args,**kwargs):
        return 'GET'

    def post(self,*args,**kwargs):
        return 'POST'

app.add_url_rule('/user',None,UserView.as_view('uuuu'))

if __name__ == '__main__':
    app.run()
CBV

  四、请求相关

  技术点:面向对象的封装

  request属性

 # 请求相关信息
        # request.method  请求方法 GET/POST
        # request.args   GET请求参数    
        # request.form   POST请求参数
        # request.values   GET和POST请求参数
        # request.cookies   cookie值
        # request.headers   请求头信息
        # request.path   请求地址路径
        # request.full_path   请求地址全路径
        # request.script_root
        # request.url
        # request.base_url
        # request.url_root
        # request.host_url
        # request.host
        # request.files
        # obj = request.files['the_file_name']
        # obj.save('/var/www/uploads/' + secure_filename(f.filename))
    print('headers:', request.headers)
    print('cookies:', request.cookies)
    print('path:', request.path)
    print('full_path:', request.full_path)
    print('url:', request.url)
    print('script_root:', request.script_root)
    print('base_url:', request.base_url)
    print('url_root:', request.url_root)
    print('host:', request.host)
    print('host_url:', request.host_url)

headers: Host: 172.25.0.246:8999
Connection: keep-alive
Content-Length: 37
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 9_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 S
afari/601.1 wechatdevtools/1.02.1901230 MicroMessenger/6.7.3 Language/zh_CN webview/ token/a46ac17280ff1fcb82b684b2084ee168
Origin: http://127.0.0.1:23672
Authorization: f3c4e30debf4030ace5c3cdf40e6332d#1
Content-Type: application/x-www-form-urlencoded
Accept: */*
Referer: https://servicewechat.com/wx49bec3712cb29bf5/devtools/page-frame.html
Accept-Encoding: gzip, deflate


cookies: {}
path: /api/member/check-reg
full_path: /api/member/check-reg?
url: http://172.25.0.246:8999/api/member/check-reg
script_root:
base_url: http://172.25.0.246:8999/api/member/check-reg
url_root: http://172.25.0.246:8999/
host: 172.25.0.246:8999
host_url: http://172.25.0.246:8999/
示例
@main.route('/data_list/<table_name>')
def data_list(table_name):
    df = reader.enable_admins[table_name]
    page_obj = Pagination(df['data'].shape[0], request.args.get('p'),per_page_num=5)
    page_str = page_obj.page_str(base_url=url_for('main.data_list', table_name=table_name))
    admin_class = df['admin_class']
    data_list = df['data'][page_obj.start:page_obj.end]
    context = {'table_name':table_name, 'data_list':data_list,
               'page_obj':page_obj, 'page_str':page_str,
               'admin_class':admin_class,
               }
    return render_template('data_list.html', **context)
get请求
@ac.route('/login',methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    username = request.form.get('username')
    password = request.form.get('pwd')
    """数据库验证"""
    password = get_md5(password)
    # conn = Connect(host='localhost', user='root', password='0000', database='flask_code', charset='utf8')
    # cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)   # 每一行是字典
    # cursor.execute("select id,username,nickname from userinfo where username=%(us)s and password=%(pw)s",{'us':username,'pw':password})
    # data = cursor.fetchone()
    data = fetchone("select id,username,nickname from userinfo where username=%(us)s and password=%(pw)s",{'us':username,'pw':password})
    if not data:
        return render_template('login.html',error='用户名或密码错误')

    session['user_info'] = {'id':data['id'],'username':data['username'],'nickname':data['nickname']}
    if request.form.get('remember'):
        session.permanent = True
        ac.permanent_session_lifetime = timedelta(days=31)

    return redirect('/home')
post请求

   五、响应相关

技术点:面向对象的封装

响应体:4种

return '欢迎使用'
return jsonify({'k1':'v1'})
return render_template('xxx.html')
return redirect('/index')
4种响应体

定制响应头

obj = make_response(render_template('index.html'))
obj.headers['xxxxx'] = '123'
obj.set_cookie('key','value')
return obj
定制响应头

示例程序:用户访问限制

@app.route('/detail/<int:nid>')
def detail(nid):
    if not session.get('user'):
        return redirect(url_for('login'))
    info = STUDENT_DICT[nid]
    return render_template('detail.html', info=info)
版本一
def auth(func):
    @wraps(func)
    def inner(*args,**kwargs):
        if not session.get('user'):
            return redirect(url_for('login'))
        ret = func(*args,**kwargs)
        return ret
    return inner
        
@app.route('/index')
@auth
def index():
    return render_template('index.html',stu_dict=STUDENT_DICT)
    
应用场景:比较少的函数中需要添加额外的功能
版本二
@app.before_request
def xxxx():
    if request.path == '/login':
        return None
    if not session.get('user'):
        return None
    return redirect(url_for('login'))

应用场景:比较多的函数中需要添加额外的功能
版本三

  六、模板渲染

基本数据类型:可以执行python语法,如:dict.get()   list['xx']

控制代码块:条件语句和循环语句

- if/else if /else / endif
- for / endfor
    <ul class="pagination">
        {% if page_obj.num_pages > 1 %}
            {{ page_str }}
        {% else %}
        {% endif %}
    </ul>
if else
   <table class="table table-bordered">
        <thead>
        <tr style="background-color: pink">
            <th>序号</th>
            <th>文件名</th>
            <th>样本数</th>
            <th>特征数</th>
            <th>操作</th>
        </tr>
        </thead>
        <tbody>
        {% for k,v in data_list.items() %}
            <tr>
                <td>{{ loop.index }}</td>
                <td><a href="{{ url_for('main.data_list',table_name=k)}}">{{ k }}</a></td>
                <td>{{ v['data'].shape }}</td>
                <td>{{ v['data'].shape }}</td>
                <td><a href="{{ url_for('main.data_list',table_name=k)}}">查看</a></td>
            </tr>
        {% endfor %}
        </tbody>
    </table>
for循环

除此之外,还有一个特殊的循环函数loop

  • 在循环内部,你可以使用一个叫做loop的特殊变量来获得关于for循环的一些信息

    • 比如:要是我们想知道当前被迭代的元素序号,并模拟Python中的enumerate函数做的事情,则可以使用loop变量的index属性,例如:

    • {% for post in posts%}
      {{loop.index}}, {{post.title}}
      {% endfor %}
      会输出这样的结果
      1, Post title2, Second Post
    • cycle函数会在每次循环的时候,返回其参数中的下一个元素,可以拿上面的例子来说明:
    • {% for post in posts%}
      {{loop.cycle('odd','even')}} {{post.title}}
      {% endfor %}
      会输出这样的结果:
      
      odd Post Title
      even Second Post

传入函数

  • -django,自动执行
  • -flask,不自动执行

自定义函数

@app.template_global()
def sb(a,b):
    return a+b

@app.template_filter() # 适合if 判断
def db(a,b,c):
    return a+b+c
全局定义函数
html
{{ 1|db(2,3) }}
    {% if 1|db(2,3) %}
        <div>666</div>
    {% else %}
        <div>999</div>
    {% endif %}

@app.template_filter()   # 适合if 判断
def db(a,b,c):
    return a+b+c
template_filter()
用装饰器来实现自定义过滤器。装饰器传入的参数是自定义的过滤器名称。

@app.template_filter('lireverse')
def do_listreverse(li):
    # 通过原列表创建一个新列表
    temp_li = list(li)    # 将新列表进行返转
    temp_li.reverse()    return temp_li

在 html 中使用该自定义过滤器

<br/> my_array 原内容:{{ my_array }}<br/> my_array 反转:{{ my_array | lireverse }}
运行结果

my_array 原内容:[3, 4, 2, 1, 7, 9] 
my_array 反转:[9, 7, 1, 2, 4, 3]
例:添加列表反转的过滤器

模板继承

layout.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        {% block content %}{% endblock %}
    </body>
    </html>
    
tpl.html
    {% extends 'layout.html' %}
    {% block content %}
        {% include 'form.html' %}

        {% macro ccccc(name,type='text', value='') %}
            <h1>宏</h1>
            <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
            <input type="submit" name="提交">
        {% endmacro %}

        {{ ccccc('n1') }}
        {{ ccccc('n2') }}

    {% endblock %}
模板继承extends
<include 'from.html'>

form.html
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <div>sasasasa sasasasasasa</div>
    </body>
</html>
include

{% macro ccccc(name,type='text', value='') %}
    <h1>宏</h1>
    <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
    <input type="submit" name="提交">
{% endmacro %}

{{ ccccc('n1') }}
{{ ccccc('n2') }}

安全

   前端:{{ txt|safe }}
   后端:Markup(txt)

   七、session

  原理:加密后放置在用户浏览器的cookie中

  流程:

请求到来:flask读取cookie中的session对应的值:eyJrMiI6NDU2LCJ1c2VyIjoiZmVpIn0,将该值解密并反序列化成字典,放入内存以便视图函数使用。
视图函数:
    @app.route('/sess')
    def sess():
        print(session.__class__)
        session['k1'] = 123
        session['k2'] = 456
        del session['k1']
        return 'Session'

请求结束,flask会读取内存中字典的值,进行序列化+加密,写入用户cookie中。
session执行流程

  实现原理(源码)

def wsgi_app(self, environ, start_response):
            """
            1.获取environ并对其进行再次封装
            2.从environ中获取名称为session的cookie,解密,反序列化
            3.两个东西放到‘某个神奇'的地方
            """
            # ctx = RequestContext(self, environ)  #self是app对象,evviron是原始数据对象
            # ctx.request = Request(environ)
            # ctx.session = None
            ctx = self.request_context(environ)
            error = None
            try:
                try:
                    ctx.push()
                    # 4. 执行视图函数
                    # 5.'某个神奇’获取session,加密,序列化,写入cookie
                    response = self.full_dispatch_request()
                except Exception as e:
                    error = e
                    response = self.handle_exception(e)
                except:
                    error = sys.exc_info()[1]
                    raise
                return response(environ, start_response)
            finally:
                if self.should_ignore_error(error):
                    error = None
                """
6.'某个神奇'位置清空
"""
ctx.auto_pop(error)
源码

   闪现(flash): 在session存储一个数据,读取时通过pop将数据删除,形成一种数据只能取一次的效果

from flask import Flask,flash,get_flashed_messages

@app.route('/page1')
def page1():
    flash('临时数据存储','error')
    flash('sasasasa','error')
    flash('sasasas','info')
    return 'Session'

@app.route('/page2')
def page2():
    print(get_flashed_messages(category_filter=['error']))
    return 'Session'
闪现示例

  八、中间件

      一般不常用,因为它的执行顺序很靠前,无序获取request对象,与请求相关信息难以获得

  • call方法什么时候触发?

    -用户发起请求时,才执行

  • -任务:在执行call方法之前,做一个操作,call方法执行之后,做一个操作
class Middleware(object):
    def __init__(self,old):
        self.old = old

    def __call__(self, *args, **kwargs):
        print('')
        ret = self.old(*args,**kwargs)
        print('')
return ret

if __name__ == '__main__':
    app.wsgi_app = Middleware(app.wsgi_app)
    app.run()
中间件示例

  九、特殊的装饰器

    技术点:before_request和after_request的实现原理

 六大装饰器

before_first_request

before_request 视图函数之前,原理是将视图函数放入到一个列表中,循环,如果有返回值停止循环,后面的函数也将不会执行

after_request  视图函数之后,原理是将视图函数放入到一个列表中reverse,循环执行

template_global

template_filter

errorhandler
    @app.errorhandler(404)
    def not_found(arg):
        print(arg)
        return '404 没找到'
# -*- coding: utf-8 -*-

"""
@Datetime: 2018/12/21
@Author: Zhang Yafei
"""
from flask import Flask

app = Flask(__name__)


@app.before_first_request
def x():
    print('before_first')


@app.before_request
def x1():
    print('before:x1')


@app.before_request
def xx1():
    print('before:xx1')


@app.after_request
def x2(response):
    print('after:x2')
    return response


@app.after_request
def xx2(response):
    print('after:xx2')
    return response


@app.route('/index')
def index():
    print('index')
    return 'index'


@app.route('/order')
def order():
    print('order')
    return 'order'


@app.errorhandler(404)
def not_found(arg):
    print(arg)
    return '404 没找到'


if __name__ == '__main__':
    app.run()
装饰器测试

  十、蓝图

 (1) 目标:目录结构的划分
 (2) 自定义模板、静态文件

admin = Blueprint(
  'admin',
  __name__,
  template_folder='templates',
  static_folder='static'
)

(3)  给某一类url添加前缀变量 app.register_blueprint(admin, url_prefix='/admin')
(4)  给一类url添加before_request

@app.before_request
def x1():
    print('app.before_request')

  十一、多app

from flask import Flask
from werkzeug.wsgi import DispatcherMiddleware
from werkzeug.serving import run_simple

app1 = Flask("app1")
app1.config['DB'] = 123

app2 = Flask("app2")
app1.config['DB'] = 456


@app1.route('/web')
def web():
    print('web')
    return '12213'


@app1.route('/news')
def news():
    print('news')
    return '12213'


@app2.route('/admin')
def admin():
    print('admin')
    return '12213'


@app2.route('/article')
def article():
    print('article')
    return '12213'


"""
/web
/new
/app2/admin
/app2/article
"""
app = DispatcherMiddleware(app1, {
    '/app2': app2,
})


if __name__ == '__main__':
    run_simple(hostname='127.0.0.1', port=5000, application=app)
mul_app.py
from multi_app import app1
from multi_app import app2


with app1.app_context():
    pass # 为app1创建数据库
    with app2.app_context():
        pass  # 为app2创建数据库
离线脚本

  什么是响应式布局?                                                               

技术点:@media

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.css" />
</head>
<body>
    <div class="row" style="background-color: #28a4c9">
      <div class="col-lg-6">.col-lg-6</div>
      <div class="col-lg-6">.col-lg-6</div>
    </div>
    <div class="row" style="background-color: #67b168">
        <div class="col-md-6">.col-md-6</div>
        <div class="col-md-6">.col-md-6</div>
    </div>
    <div class="row" style="background-color: red">
        <div class="col-sm-6">.col-sm-6</div>
        <div class="col-sm-6">.col-sm-6</div>
    </div>
    <div class="row" style="background-color: gold">
        <div class="col-xs-6">.col-xs-6</div>
        <div class="col-xs-6">.col-xs-6</div>
    </div>
</body>
</html>
响应式布局.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        body{
            margin: 0;
        }
        .pg{
            width: 100%;
            background-color: rebeccapurple;
        }
        @media (min- 666px) {
            .pg{
                background-color: green;
            }
        }
        @media (min- 888px) {
            .pg{
                background-color: red;
            }
        }
    </style>
</head>
<body>
    <div>
        <div class="pg">asdfasdf</div>
    </div>
</body>
</html>
本质

注:

1. 上下文管理的实现?
    当请求到来的时候,
        flask会把请求相关和session相关信息封装到一个ctx = RequestContext对象中,
        会把app和g封装到app_ctx = APPContext对象中去,
        通过localstack对象将ctx、app_ctx对象封装到local对象中

    获取数据(执行视图函数的时候)
        通过导入一个模块,用模块.的方式获取我们想要的数据
        实现细节:
            通过localproxy对象+偏函数,调用localstack去local中获取对应的响应ctx、app_ctx中封装值
            问题:为什么要把ctx = request/session  app_ctx = app/g
                因为离线脚本需要使用app_ctx
    请求结束
        调用localstk的pop方法,将ctx和app_ctx移除    
2. 为什么把上下文管理分成:
    - 应用上下文:request/session
    - 请求上下文: app/g 
    离线脚本应用
            
3. Local的作用?
    类似于threading.local的作用,但是是他的升级版(greentlet.get_current())
    __storage__ = {
        1231: {},
        1231: {}
    }
4. LocalStack作用?
    将Local中__storage__的值维护成一下结构:
    __storage__ = {
        1231: {stack:[],},
        1231: {stack:[],}
    }
5. 为什么要维护成一个栈?

6. 为什么导入request,就可以使用?
    每次执行request.xx方法时,会触发LocalProxy对象的__getattr__等方法,由方法每次动态的使用
    LocalStack去Local中获取数据
flask进阶问题预览
原文地址:https://www.cnblogs.com/zhangyafei/p/10204463.html