Python-S9-Day115——Flask Web框架基础

  • 01 今日内容概要
  • 02 内容回顾
  • 03 Flask框架:配置文件导入原理
  • 04 Flask框架:配置文件使用
  • 05 Flask框架:路由系统
  • 06 Flask框架:请求和响应相关
  • 07 示例:学生管理(一)
  • 08 示例:学生管理(二)
  • 09 Flask框架:模板
  • 10 Flask框架:Session
  • 11 Flask框架:Flash和特殊装饰器
  • 12 Flask框架:中间件
  • 13 Flask框架:特殊装饰器
  • 14 今日内容总结

01 今日内容概要

1.1 Web框架的基础组成——urls、views;

1.2 路由->视图;

1.3 配置文件的处理;

1.4 路由系统(与Django有些不同,通过装饰器做的)

1.5 视图函数;

1.6 请求相关的数据不一样;

1.7 响应相关的数据不一样;

1.8 模板的渲染;

1.9 不是所有的Web框架都提供session,Flask提供;

1.10 闪现——取一下就没有了;

1.11 中间件;

1.12 蓝图——blueprint,对Flask程序进行目录结构的划分;

1.13 特殊的装饰器;

02 内容回顾

2.1 谈谈Django和Flask的对比、认知;

  2.1.1 Django大而全,内部提供了很多的组件,比如ORM、admin、form以及ModelForm、分页等缓存、信号;Flask短小精悍,轻量级,可拓展性很高,适用于小型网站;

  2.1.2 Flask可拓展性更高,自定义程度高;

  2.1.3 Flask第三方组件拓展,会变得同Django一样;

  2.1.4 Django和Flask哪个好呢?就我个人而言,如果比较熟悉的话,Flask更加适合!

2.2  Flask的Django的最大不同点?

   2.2.1 request、session

2.3 Flask的知识点

  2.3.1 模板和静态文件,在实例化时候配置,app = Flask(__name__,...)

  2.3.2 路由——@app.route('/index',methods = ["GET"])

  2.3.3 请求:request.form equest.args equest.method

  2.3.4 响应:render、redirect

  2.2.5 session引入方式:session['score'] = 123,获取方式推荐:session.get('score')不会报错!

2.4 路飞学城总共有几个项目?

  2.4.1 管理后台

  2.4.2 导师后台

  2.4.3 主站(www.luffycity.com)——Vue:2.0;Django:1.11.1

2.5 路飞学城的主站业务?

  2.5.1 课程

  • 课程列表
  • 课程详细
  • 课程大纲、导师、推荐课程
  • 价格策略
  • 章节和课时
  • 常见问题

  2.5.2 深科技

  • 文章列表
  • 文章详细
  • 收藏
  • 评论
  • 点赞

  2.5.3 支付

  • 购物车
  • 结算中心
  • 立即支付
  1. Redis(是否做持久化,AOM&RDB)
  2. 支付宝
  3. 消息推送——微信服务号
  4. 构建数据结构——redis中的key进行拼接;
  5. 购物车超时限制
  6. 优惠券+贝里+支付宝

  2.5.4 个人中心

  2.5.5 学习中心

  2.5.6 播放视频——CC视频,是否加密?给CC视频打个广告:https://www.bokecc.com/

  • 如果购买

<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="600" height="490" id="cc_A421CCBE1873EA0C9C33DC5901307461"><param name="movie" value="https://p.bokecc.com/flash/single/2660F3A686840FA2_A421CCBE1873EA0C9C33DC5901307461_false_654628F0907DA9AD_1/player.swf" /><param name="allowFullScreen" value="true" /><param name="allowScriptAccess" value="always" /><param value="transparent" name="wmode" /><embed src="https://p.bokecc.com/flash/single/2660F3A686840FA2_A421CCBE1873EA0C9C33DC5901307461_false_654628F0907DA9AD_1/player.swf" width="600" height="490" name="cc_A421CCBE1873EA0C9C33DC5901307461" allowFullScreen="true" wmode="transparent" allowScriptAccess="always" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash"/></object>

03 Flask框架:配置文件导入原理

3.1  找到类的方式方法(通过一个路径“settings.Foo”可以找到类并获取其中的大写的静态字段);

settings.py;

class Foo:
    DEBUG = True
    TEST = True

xx.py; 

import importlib

path = "settings.Foo"

p, c = path.rsplit('.', maxsplit=1)
m = importlib.import_module(p)
cls = getattr(m, c)
print(cls)  # <class 'settings.Foo'>

# 如何找到这个类呢?
for key in dir(cls):
    if key.isupper():
        print(key, getattr(cls, key))

04 Flask框架:配置文件使用

4.1 Flask配置文件之app.config;

from flask import Flask, render_template, redirect, request, session

app = Flask(__name__)
print(app.config)
# app.config.from_object("settings.Dev")  # 推荐使用该方式;
app.config.from_object("settings.Production")  # 推荐使用该方式;
print(app.confg)
'''
<Config {
'ENV': 'development', 
'DEBUG': False, 
'TESTING': False, 
'PROPAGATE_EXCEPTIONS': None, 
'PRESERVE_CONTEXT_ON_EXCEPTION': None, 
'SECRET_KEY': None, 
'PERMANENT_SESSION_LIFETIME': datetime.timedelta(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': datetime.timedelta(0, 43200),
'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}>
'''

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

4.2 配置文件的灵活使用方式;

  4.2.1 通过引用settings配置文件中的内容进行赋值修改app.config.from_object("settings.Production")# 推荐使用该方式;

  4.2.2 在settings配置文件中,通过定义父类,子类继承父类的方法,进行“开发、测试、预上线、生产环境等”灵活配置;

class Config(object):
    DEBUG = False
    TESTING = False
    DATABASE_URL = 'jdbc:sqlite:identifier.sqlite'


class Development(Config):
    """开发环境配置"""
    DATABASE_URL = 'mysql://username@localhost/foo'


class Testing(Config):
    """测试环境配置"""
    DATABASE_URL = 'mysql://username@test.cuixiaozhao.com/foo'


class preannouncement(Config):
    """预上线环境配置"""
    DATABASE_URL = 'mysql://username@147.198.189.125/foo'


class Production(Config):
    """生产环境配置"""
    DATABASE_URL = 'mysql://username@47.98.89.123/foo'

05 Flask框架:路由系统

5.1 endpoint,反向生成URL,如果不指定,默认为函数名;

5.2 url_for('endpoint');

5.3 设置动态路由;

from flask import Flask, render_template, redirect, request, session, url_for

app = Flask(__name__)
app.config.from_object("settings.Production")  # 推荐使用该方式;
print(app.config)


# @app.route('/index/<int:nid>', methods=['GET', 'POST'], endpoint='n1')  # 作用类似于Diango中的name属性,默认值为函数名;
@app.route('/index/<int:nid>', methods=['GET', 'POST'])  # 反向生成,作用类似于Diango中的name属性,默认值为函数名;
def index(nid):
    print(nid)
    print(url_for('n1', nid=91))  # 如果没有指定endpoint参数,自行指定!
    return 'Index'


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

5.4 常用的路由系统;

@app.route('/user/<username>')
@app.route('/post/<int:post_id>')
@app.route('/post/<float:post_id>')
@app.route('/post/<path:path>')
@app.route('/login', methods=['GET', 'POST'])
常用路由系统有以上五种,所有的路由系统都是基于一下对应关系来处理:

DEFAULT_CONVERTERS = {
    'default':          UnicodeConverter,
    'string':           UnicodeConverter,
    'any':              AnyConverter,
    'path':             PathConverter,
    'int':              IntegerConverter,
    'float':            FloatConverter,
    'uuid':             UUIDConverter,
}

06 Flask框架:请求和响应相关

6.1 请求相关的数据;

6.2 响应相关的数据;

from flask import Flask, render_template, redirect, request, session, url_for, make_response

app = Flask(__name__)
app.config.from_object("settings.Production")  # 推荐使用该方式;
print(app.config)


# @app.route('/index/<int:nid>', methods=['GET', 'POST'], endpoint='n1')  # 作用类似于Diango中的name属性,默认值为函数名;
@app.route('/index/<int:nid>', methods=['GET', 'POST'])  # 反向生成,作用类似于Diango中的name属性,默认值为函数名;
def index(nid):
    print(nid)
    print(url_for('n1', nid=91))  # 如果没有指定endpoint参数,自行指定!
    # #########################请求相关###################################
    # 请求相关信息
    # request.method
    # request.args
    # request.form
    # request.values
    # request.cookies
    # 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))

    # ###########################响应相关的数据################################
    # 定制响应体;
    # return "字符串"
    # return render_template('html模板路径',**{})
    # return redirect('/index.html')
    # return jsonify({'k1':'v1'})

    # 设置响应头,引入make_response;
    # response = make_response(render_template('index.html'))
    # response是flask.wrappers.Response类型
    # response.delete_cookie('key')
    # response.set_cookie('key', 'value')
    # response.headers['X-Something'] = 'A value'
    # return response

    return 'Index'


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

07 示例:学生管理(一)

7.1 编写路由以及视图函数;

from flask import Flask, render_template, redirect, request, session, url_for, make_response

app = Flask(__name__)
app.config.from_object("settings.Production")  # 推荐使用该方式;
STUDENT_DICT = {
    1: {'name': '崔晓昭', 'age': 18, 'gender': ''},
    2: {'name': '崔晓姗', 'age': 20, 'gender': ''},
    3: {'name': '崔晓丝', 'age': 22, 'gender': ''},
}


@app.route('/index/')
def index():
    return render_template('index.html', stu_dic=STUDENT_DICT)


@app.route('/delete/<int:nid>')
def delete(nid):
    del STUDENT_DICT[nid]
    return redirect(url_for('index'))


@app.route('/detail/<int:nid>')
def detail(nid):
    info = STUDENT_DICT[nid]
    return render_template('detail.html', info=info)


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

7.2 模本配置,多数语法与Python十分相似;

index.html;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>学生列表</h1>
<table border="1px">
    <thead>
    <tr>
        <th>ID</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>性别</th>
        <th>选项</th>
    </tr>
    </thead>
    <tbody>
    {% for k,v in stu_dic.items() %}
        <tr>
            <td>{{ k }}</td>
            <td>{{ v.name }}</td>
            <td>{{ v.age }}</td>
            <td>{{ v.gender }}</td>
            <td>
                <a href="/detail/{{ k }}">查看详细</a>&nbsp;|
                <a href="/delete/{{ k }}">删除</a>
            </td>
        </tr>

    {% endfor %}

    </tbody>
</table>
</body>
</html>

detail.html;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>学生详细</h1>
<ul>
    {% for item in info.values() %}
        <li>{{ item }}</li>
    {% endfor %}

</ul>
</body>
</html>

08 示例:学生管理(二)

8.1 史上最LOW的实现方式,Copy、Paste不符合高端程序员的秉性;

from flask import Flask, render_template, redirect, request, session, url_for, make_response

app = Flask(__name__)
app.config.from_object("settings.Production")  # 推荐使用该方式;
STUDENT_DICT = {
    1: {'name': '崔晓昭', 'age': 18, 'gender': ''},
    2: {'name': '崔晓姗', 'age': 20, 'gender': ''},
    3: {'name': '崔晓丝', 'age': 22, 'gen der': ''},
}


@app.route('/login/', methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template('login.html')
    user = request.form.get('user')
    pwd = request.form.get('pwd')

    if user == 'oldboy' and pwd == '666':
        session['user'] = user
        return redirect('/index')
    return render_template('login.html', error='用户名或者密码错误!')


@app.route('/index/')
def index():
    if not session.get('user'):
        return redirect(url_for('login'))
    return render_template('index.html', stu_dic=STUDENT_DICT)


@app.route('/delete/<int:nid>')
def delete(nid):
    if not session.get('user'):
        return redirect(url_for('login'))
    del STUDENT_DICT[nid]
    return redirect(url_for('index'))


@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)


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

8.2 添加装饰器的方法,适用于小范围添加功能;

from flask import Flask, render_template, redirect, request, session, url_for, make_response

app = Flask(__name__)
app.config.from_object("settings.Production")  # 推荐使用该方式;
STUDENT_DICT = {
    1: {'name': '崔晓昭', 'age': 18, 'gender': ''},
    2: {'name': '崔晓姗', 'age': 20, 'gender': ''},
    3: {'name': '崔晓丝', 'age': 22, 'gen der': ''},
}

import functools


def auth(func):
    @functools.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('/login/', methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template('login.html')
    user = request.form.get('user')
    pwd = request.form.get('pwd')

    if user == 'oldboy' and pwd == '666':
        session['user'] = user
        return redirect('/index')
    return render_template('login.html', error='用户名或者密码错误!')


@app.route('/index/')
@auth
def index():
    return render_template('index.html', stu_dic=STUDENT_DICT)


@app.route('/delete/<int:nid>')
@auth
def delete(nid):
    del STUDENT_DICT[nid]
    return redirect(url_for('index'))


@app.route('/detail/<int:nid>')
@auth
def detail(nid):
    info = STUDENT_DICT[nid]
    return render_template('detail.html', info=info)


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

8.3 适用于批量添加功能,类似于“中间件”的原理;

from flask import Flask, render_template, redirect, request, session, url_for, make_response

app = Flask(__name__)
app.config.from_object("settings.Production")  # 推荐使用该方式;
STUDENT_DICT = {
    1: {'name': '崔晓昭', 'age': 18, 'gender': ''},
    2: {'name': '崔晓姗', 'age': 20, 'gender': ''},
    3: {'name': '崔晓丝', 'age': 22, 'gen der': ''},
}


@app.before_request
def XXXX():
    if request.path == '/login':
        return None
    if session.get('user'):
        return None
    return redirect('/login')


@app.route('/login/', methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template('login.html')
    user = request.form.get('user')
    pwd = request.form.get('pwd')

    if user == 'oldboy' and pwd == '666':
        session['user'] = user
        return redirect('/index')
    return render_template('login.html', error='用户名或者密码错误!')


@app.route('/index/')
def index():
    return render_template('index.html', stu_dic=STUDENT_DICT)


@app.route('/delete/<int:nid>')
def delete(nid):
    del STUDENT_DICT[nid]
    return redirect(url_for('index'))


@app.route('/detail/<int:nid>')
def detail(nid):
    info = STUDENT_DICT[nid]
    return render_template('detail.html', info=info)


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

09 Flask框架:模板

9.1 模板渲染;

  9.1.1 基本数据类型——可以执行Python的基础语法,如:dict.get(),list['xx'];

tpl.html;

{% extends 'layout.html' %}
{% block content %}
    {{ users.0 }}
    {{ users[0] }}
    {{ txt|safe }}
    {{ func }}
    {{ func(6) }}
    {{ sb(1,100) }}
    {{ 1|db(1800,92) }}
    {% if 1|db(2,3) %}
        <div>666</div>
    {% else %}
        <div>999</div>
    {% endif %}
    {% include 'form.html' %}
    {% macro ccc(name,type = 'text',value = '') %}
    <h1></h1>
        <input type="{{ type }}" name="{{ name }}" value="{{ value }}">
        <input type="submit" value="提交">
    {% endmacro %}
    {{ ccc('n1') }}
    {{ ccc('n2') }}

{% endblock %}

  9.1.2 传入函数func;

  9.1.3 全局定义函数——@app.template_global()、@app.template_filter();

  9.1.4 模板继承extends结合block方法;

layout.html;

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>layout</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
{% block content %}

{% endblock %}
</body>
</html>

  9.1.5 include方法引入模板文件{% include 'form.html' %};

form.html;

<form action="">
    cuixiaozhao
    cuixiaoshan
    cuixiaosi
    cuixiaolei
</form>

  9.1.6 定义宏;

  9.1.7 安全;

  • 前端'txt'|safe参数;
  • 后端Markup('fdskhfdjksa');

app.py;

from flask import Flask, render_template, redirect, request, session, url_for, make_response, Markup

app = Flask(__name__)
app.config.from_object("settings.Production")  # 推荐使用该方式;
STUDENT_DICT = {
    1: {'name': '崔晓昭', 'age': 18, 'gender': ''},
    2: {'name': '崔晓姗', 'age': 20, 'gender': ''},
    3: {'name': '崔晓丝', 'age': 22, 'gen der': ''},
}


# @app.before_request
# def XXXX():
#     if request.path == '/login':
#         return None
#     if session.get('user'):
#         return None
#     return redirect('/login')


@app.route('/login/', methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template('login.html')
    user = request.form.get('user')
    pwd = request.form.get('pwd')

    if user == 'oldboy' and pwd == '666':
        session['user'] = user
        return redirect('/index')
    return render_template('login.html', error='用户名或者密码错误!')


@app.route('/index/')
def index():
    return render_template('index.html', stu_dic=STUDENT_DICT)


@app.route('/delete/<int:nid>')
def delete(nid):
    del STUDENT_DICT[nid]
    return redirect(url_for('index'))


@app.route('/detail/<int:nid>')
def detail(nid):
    info = STUDENT_DICT[nid]
    return render_template('detail.html', info=info)


@app.route('/tpl/')
def tpl():
    context = {
        'users': ['longtai', 'liusong', 'zhaohuhu'],
        'txt': Markup("<input type='text'/>"),
        'func': func,

    }
    return render_template('tpl.html', **context)


def func(arg):
    return arg + 1


@app.template_global()
def sb(a1, a2):
    return a1 + a2


@app.template_filter()
def db(a1, a2, a3):
    return a1 + a2 + a3  # {{ 1|db(1800,92) }}


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

10 Flask框架:Session

10.1 session的生成;

10.2 Flask中的session原理——读取cookie中session对应的值;eyJrMiI6NDU2fQ.DnFwbQ.TF4LMMk1g1WEk1NfQnmCS1OSN6w ,将改值解密并反序列化成字典;当请求结束时候,Flask会读取内存中的字典的值,进行序列化+加密,写入到用户cookies中。

11 Flask框架:Flash和特殊装饰器

11.1 闪现的概念——Flash;

from flask import Flask, render_template, redirect, request, session, url_for, make_response, Markup, flash, 
    get_flashed_messages

app = Flask(__name__)
app.config.from_object("settings.Production")  # 推荐使用该方式;
STUDENT_DICT = {
    1: {'name': '崔晓昭', 'age': 18, 'gender': ''},
    2: {'name': '崔晓姗', 'age': 20, 'gender': ''},
    3: {'name': '崔晓丝', 'age': 22, 'gen der': ''},
}


# @app.before_request
# def XXXX():
#     if request.path == '/login':
#         return None
#     if session.get('user'):
#         return None
#     return redirect('/login')


@app.route('/login/', methods=["GET", "POST"])
def login():
    if request.method == "GET":
        return render_template('login.html')
    user = request.form.get('user')
    pwd = request.form.get('pwd')

    if user == 'oldboy' and pwd == '666':
        session['user'] = user
        return redirect('/index')
    return render_template('login.html', error='用户名或者密码错误!')


@app.route('/index/')
def index():
    return render_template('index.html', stu_dic=STUDENT_DICT)


@app.route('/delete/<int:nid>')
def delete(nid):
    del STUDENT_DICT[nid]
    return redirect(url_for('index'))


@app.route('/detail/<int:nid>')
def detail(nid):
    info = STUDENT_DICT[nid]
    return render_template('detail.html', info=info)


@app.route('/tpl/')
def tpl():
    context = {
        'users': ['longtai', 'liusong', 'zhaohuhu'],
        'txt': Markup("<input type='text'/>"),
        'func': func,

    }
    return render_template('tpl.html', **context)


@app.route('/ses/')
def ses():
    print(type(session))  # <class 'werkzeug.local.LocalProxy'>
    session['k1'] = 123
    session['k2'] = 456
    del session['k1']
    return "Session!"


@app.route('/page1/')
def page1():
    flash("cuixiaozhao19930911!", 'error')
    flash("临时存储数据!", 'info')
    flash("临时存储数据!", 'warning')
    # session['uuu'] = 123
    return "Session uuu123!"


@app.route('/page2/')
def page2():
    # print(session['uuu'])
    print(get_flashed_messages(category_filter=['error']))
    return "Session uuu456!"


def func(arg):
    return arg + 1


@app.template_global()
def sb(a1, a2):
    return a1 + a2


@app.template_filter()
def db(a1, a2, a3):
    return a1 + a2 + a3  # {{ 1|db(1800,92) }}


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

12 Flask框架:中间件

12.1   聊一下Flask的源码;

12.2 Flask中的中间件说明,使用场景较少,一般通过装饰器来做;

from flask import Flask, render_template, redirect, request, session, url_for, make_response, Markup, flash, 
    get_flashed_messages

app = Flask(__name__)


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


class Middleware(object):
    def __int__(self, old):
        self.old = old

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


if __name__ == '__main__':
    app.wsgi_app = Middleware(app.wsgi_app)
    app.run()

13 Flask框架:特殊装饰器(非常重要)

  • before_request
  • after_request
  • before_first_request
  • after_first_request
  • template_global
  • template_filter
  • errorhandler(404),应用非常广泛!

14 今日内容总结

14.1 配置文件;

14.2 路由;

14.3 但凡写装饰器,都要加上 functools;

14.4 视图FBV;Django以参数传输,Flask导入;

14.5 请求和响应;

14.6 模板——继承、include、自定义函数、特殊的装饰器(global)、语法更接近Python;

14.7 session和flash(闪现)以加密的形式存储在用户的token中,默认session的超时时间是31天,Django是2周的时长;

14.8 中间件和特殊的装饰器;__call__方法来做;

14.9 6个装饰器;

原文地址:https://www.cnblogs.com/tqtl911/p/9592534.html