共计 29449 个字符,预计需要花费 74 分钟才能阅读完成。
引入
1. 三大主流 web 框架
- django 框架(同步)
特点 : 大而全, 自带的功能组件非常多! 类似于航空母舰
不足 : 有时候过于笨重
- flask 框架(同步)
特点 : 小而精 自带的功能特别特别特别的少, 类似于游骑兵, 但第三方的模块特别特别特别的多,如果将 flask 第三方的模块加起来完全可以盖过 django
不足 : 比较依赖于第三方的开发者
ps : 三行代码就可以启动一个 flask 后端服务
- tornado 框架(异步)
异步非阻塞 速度非常的快 快到可以开发游戏服务器
- Sanic 框架
- FastAPI 框架
- .....
2.Web 框架三部分
- A : socket 部分
- B : 路由与视图匹配部分
- C : 模板语法部分
3. 三种主流框架三部分的使用情况
- Django
A : 用的是别人的 (wsgiref 模块)
B : 用的是自己的
C : 用的是自己的 (没有 jinja2 好用 但是也很方便)
- flask
A : 用的是别人的 (werkzeug(内部还是 wsgiref 模块))
B : 自己写的
C : 用的别人的 (jinja2)
- tornado
A,B,C 都是自己写的
4.wsgi 与 wsgiref
👉CGI、FastCGI、WSGI、uWSGI、uwsgi 关系
- 实现 wsgi 协议本质就是一个 socket 服务端
- django 框架是一个可调用的对象
- uwsgi 是一个 web 服务器, 上线的时候用
- 而 wsgiref 一般本地测试(并发能力弱)
一.Flask 由来
Flask 诞生于 2010 年,是 Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级 Web 开发框架。
Flask 本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展 Flask-Mail,用户认证 Flask-Login,数据库 Flask-SQLAlchemy),都需要用第三方的扩展来实现。比如可以用 Flask 扩展加入 ORM、窗体验证工具,文件上传、身份验证等。Flask 没有默认使用的数据库,你可以选择 MySQL,也可以用 NoSQL。
其 WSGI 工具箱采用 Werkzeug(路由模块),模板引擎则使用 Jinja2。这两个也是 Flask 框架的核心。
Flask 官网文档 : https://flask.palletsprojects.com/en/1.1.x/
Flask 中文文档 : https://dormousehole.readthedocs.io/en/latest/
二.Flask 常用第三方扩展包
- Flask-SQLalchemy:操作数据库,ORM. 号称操作数据库最快的框架 SQLalchemy;
- Flask-script:终端脚本工具,脚手架;
- Flask-migrate:管理迁移数据库. 比 Django 的更加强大, 迁移数据的过程中还可以回滚;
- Flask-Session:Session 存储方式指定;
- Flask-WTF:表单;
- Flask-Mail:邮件;
- Flask-Bable:提供国际化和本地化支持,翻译;
- Flask-Login:认证用户状态;
- Flask-OpenID:认证, OAuth;
- Flask-RESTful:开发 REST API 的工具;
- Flask JSON-RPC: 开发 rpc 远程服务 [过程] 调用
- Flask-Bootstrap:集成前端 Twitter Bootstrap 框架
- Flask-Moment:本地化日期和时间
- Flask-Admin:简单而可扩展的管理接口的框架
- 更多 flask 官方推荐的扩展
三.werkzeug 介绍
1. 简介
-
Werkzeug 是一个WSGI 工具包,它可以作为一个Web 框架的底层库
-
werkzeug 不是一个 web 服务器,也不是一个 web 框架,而是一个工具包
-
官方的介绍说是一个 WSGI 工具包,它可以作为一个 Web 框架的底层库,因为它封装好了很多 Web 框架的东西,例如 Request,Response 等等
2. 使用
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)
四.Flask 安装
1.Pycharm 中安装
在搜索框中搜索 Flask(大写开头)
2. 命令行安装
pip3 install flask==[版本号]
pip3 install flask==1.1.1
五. 创建 Flask 项目
与 django 不同,flask 不会提供任何的自动操作, 所以需要手动创建项目目录, 需要手动创建启动项目的管理文件
例如: 创建项目目录 flaskdemo, 在目录中创建 manage.py. 在 pycharm 中打开项目并指定上面创建的虚拟环境
六.Flask 的使用
1. 简单使用
- 展示 "hello word"
from flask import Flask
app = Flask(__name__)
@app.route('/')
def index():
return '<h1>Hello Word!</h1>'
if __name__ == '__main__':
# flask 默认的端口是 5000
app.run() # 本质还是调用了 run_simple()方法:(源码)run_simple(host, port, self, **options)
- 效果
- 方法及参数分析
from flask import Flask # 导入 Flask 类
app = Flask(__name__)
"""
Flask 内参数:
import_name : Flask 程序所在的包(模块), 传 __name__ 就可以, 用来决定 Flask 在访问静态文件时查找的路径
static_path : 静态文件访问路径(不推荐使用,使用 static_url_path 代替)
static_url_path : 静态文件访问路径,可以不传,默认为:/ + static_folder
static_folder : 静态文件存储的文件夹,可以不传,默认为 static
template_folder : 模板文件存储的文件夹,可以不传,默认为 templates
"""
@app.route('/')
def index():
return '<h1>Hello Word!</h1>'
"""
flask 的路由是通过给视图添加装饰器的方式进行编写的。当然也可以分离到另一个文件中
flask 的视图函数,flask 中默认允许通过 return 返回 html 格式数据给客户端
"""
if __name__ == '__main__':
# 运行 flask, 指定 ip 和 port(默认是本机,5000 端口)
app.run(host="0.0.0.0", port=5000)
七.Flask 四剑客、jinja2、request、登入入示例
1.Falsk 三剑客
- request : 类比 Django 中 HttpResponse (HTTP 请求)
- render_template : 类比 Django 中 render (渲染)
- redirect : 类比 Django 中 redirect (重定向)
- jsonify:类比 Django 中 JsonResponse(返回 json 格式数据)
2.jinja2 模板语法
- 与 Django 的 DTL 类似,但是比 DTL 强大,支持加括号执行,字典支持中括号取值和 get 取值
3. 请求数据的存放的位置
- get 请求 :
request.query_string.get('xxx')
- post 请求 :
request.form.get('xxx')
3. 登入示例
- run.py
from flask import Flask, render_template, request, redirect
app = Flask(__name__)
app.debug = True
"""
开发阶段, 设置 debug 模式
会在页面中显示错误信息
可以在修改代码之后自动重启服务
"""
# 用户信息
USERS = {1: {'name': '派大星', 'age': 18, 'gender': '男', 'info': " 星星星星星 "},
2: {'name': '海绵宝宝', 'age': 28, 'gender': '男', 'info': " 宝宝宝宝宝 "},
3: {'name': '章鱼哥', 'age': 18, 'gender': '女', 'info': " 鱼鱼鱼鱼鱼鱼 "},
}
@app.route('/')
def index():
# 渲染页面, 展示用户信息, 前端使用的是 jinjia2 模板语法
return render_template('index.html', user=USERS)
# 前端通过转换器传入 id 参数
@app.route('/detail/<int:id>')
def detail(id):
# 拿到该 id 用户渲染到详情页面
user = USERS[id]
return render_template('detail.html', user=user)
# flask 默认只允许 get 请求, 可以通过 methods 添加请求类型
@app.route('/login/', methods=["GET", "POST"])
def login():
if request.method == "POST":
password = request.form.get('password')
username = request.form.get('username')
if username == "shawn" and password == "123":
# 登入成功重定向到首页
return redirect('/')
else:
return render_template('login.html', error='用户名或密码错误!')
return render_template('login.html')
if __name__ == '__main__':
app.run()
- index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1> 用户信息 </h1>
<table>
{% for k,v in user.items() %}
<tr>
<td>{{k}}</td>
<td>{{v.name}}</td>
<td>{{v['name']}}</td>
<td>{{v.get('name')}}</td>
<td><a href="/detail/{{k}}"> 查看简介 </a></td>
</tr>
{% endfor %}
</table>
</body>
</html>
- detail.html
<body>
<h1> 用户信息: {{user.name}}</h1>
<div>
{{user.info}}
</div>
</body>
- login.html
<body>
<h1> 用户登录 </h1>
<form method="post">
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value=" 登录 "><br><span style="color: red">{{error}}</span>
</form>
</body>
八. 使用配置的三种方式
1. 第一种
- app 的配置文件全在 config 字典中,但是有一些常用的,比如 debug,会直接提到 app 这一层
from flask import Flask
app = Flask(__name__)
# 1. 直接使用 app 对象的 debug 属性
app.debug = True
# 2.app 对象的 config 属性:flask.config.Config 对象, 存放 flask 的默认配置
app.config['DEBUG'] = True
- Flask 的默认配置
flask 中的配置文件是一个 flask.config.Config 对象(继承字典){
'DEBUG': False, # 是否开启 Debug 模式
'TESTING': False, # 是否开启测试模式
'PROPAGATE_EXCEPTIONS': None, # 异常传播(是否在控制台打印 LOG) 当 Debug 或者 testing 开启后, 自动为 True
'PRESERVE_CONTEXT_ON_EXCEPTION': None, # 一两句话说不清楚, 一般不用它
'SECRET_KEY': None, # 之前遇到过, 在启用 Session 的时候, 一定要有它
'PERMANENT_SESSION_LIFETIME': 31, # days , Session 的生命周期 (天) 默认 31 天
'USE_X_SENDFILE': False, # 是否弃用 x_sendfile
'LOGGER_NAME': None, # 日志记录器的名称
'LOGGER_HANDLER_POLICY': 'always',
'SERVER_NAME': None, # 服务访问域名
'APPLICATION_ROOT': None, # 项目的完整路径
'SESSION_COOKIE_NAME': 'session', # 在 cookies 中存放 session 加密字符串的名字
'SESSION_COOKIE_DOMAIN': None, # 在哪个域名下会产生 session 记录在 cookies 中
'SESSION_COOKIE_PATH': None, # cookies 的路径
'SESSION_COOKIE_HTTPONLY': True, # 控制 cookie 是否应被设置 httponly 的标志,'SESSION_COOKIE_SECURE': False, # 控制 cookie 是否应被设置安全标志
'SESSION_REFRESH_EACH_REQUEST': True, # 这个标志控制永久会话如何刷新
'MAX_CONTENT_LENGTH': None, # 如果设置为字节数,Flask 会拒绝内容长度大于此值的请求进入,并返回一个 413 状态码
'SEND_FILE_MAX_AGE_DEFAULT': 12, # hours 默认缓存控制的最大期限
'TRAP_BAD_REQUEST_ERRORS': False, # 如果这个值被设置为 True,Flask 不会执行 HTTP 异常的错误处理,而是像对待其它异常一样,通过异常栈让它冒泡地抛出。这对于需要找出 HTTP 异常源头的可怕调试情形是有用的。'TRAP_HTTP_EXCEPTIONS': False, # Werkzeug 处理请求中的特定数据的内部数据结构会抛出同样也是“错误的请求”异常的特殊的 key errors。同样地,为了保持一致,许多操作可以显式地抛出 BadRequest 异常。因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。'EXPLAIN_TEMPLATE_LOADING': False, # 如果这个值被设置为 True,你只会得到常规的回溯。'PREFERRED_URL_SCHEME': 'http', # 生成 URL 的时候如果没有可用的 URL 模式话将使用这个值
'JSON_AS_ASCII': True, # 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False,Flask 不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。'JSON_SORT_KEYS': True, # 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。'JSONIFY_PRETTYPRINT_REGULAR': True, # 默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。'JSONIFY_MIMETYPE': 'application/json', # 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。'TEMPLATES_AUTO_RELOAD': None, # 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。}
2. 第二种
- 使用配置文件的方式
app.config.from_pyfile("settings.py") # 配置文件与当前文件处于同一级
- settings.py
DEBUG = True
3. 第三种
- 使用配置文件中的类(常用)
app.config.from_object("settings.TestingConfig")
- settings.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
4. 其他配置
# 提示: 从 sys.path 中已经存在路径开始写
# 提示: settings.py 文件默认路径要放在程序 root_path 目录,如果 instance_relative_config 为 True,则就是 instance_path 目录(Flask 对象 init 方法的参数)# 通过环境变量配置: 环境变量的值为 python 文件名称名称,内部调用 from_pyfile 方法
app.config.from_envvar(" 环境变量名称 ")
#app.config.from_pyfile(os.environ['YOURAPPLICATION_SETTINGS'])
# JSON 文件名称,必须是 json 格式,因为内部会执行 json.loads
app.config.from_json("json 文件名称 ")
# 字典格式
app.config.from_mapping({'DEBUG': True})
九.Flask 路由及书写的两种方式
1. 路由作用于基本定义
- 使用路由匹配规则与前端访问的路径对比, 触发视图的执行
- 路由和视图的名称必须全局唯一, 不能出现重复,否则报错
# 指定访问的路径为 "/index"
@app.route(rule='/index')
def index():
return 'Hello Word!'
2. 路由参数介绍
- 查看 "route()" 方法源码介绍可以知道其 本质就是 "add_url_rule()"
- add_url_rule() 内参数介绍:
rule='/index' # 符合 URL 规则的路由
view_func=index # 视图函数名称
defaults=None # 当 URL 中无参数,函数需要参数时,使用 defaults = {'k': 'v'}可以为函数提供默认参数
endpoint=None # 名称,用于反向解析生成 URL,即:url_for('名称')
methods=None # 允许的请求方式,如:["GET", "post"] 大小写都可以, 内部会执行 upper()
strict_slashes=None # 对 URL 最后的 / 符号是否严格要求, 默认严格. False 就是不严格
redirect_to = None # 重定向到指定地址
subdomain = None # 子域名访问
- 参数
strict_slashes
示例
# 非严格模式
@app.route('/index', strict_slashes=False)
# 访问 http://www.xx.com/index/ 或 http://www.xx.com/index 的路由格式都可以
# 严格模式
@app.route('/index', strict_slashes=True)
# 仅支持这种路由格式访问: http://www.xx.com/index
- 参数
redirect_to
示例
@app.route('/index/<int:nid>', redirect_to='/home/<nid>')
# 只要访问 "index" 页面就会跳转到 "home" 页面
- 参数
subdomain
示例
@app.route("/dynamic", subdomain="<username>")
# 可以传入任意的字符串,如传入的字符串为 aa,显示为 aa.xxxx.com
# 传入 bbbb : bbbb.xxxx.com
# 传入 cccc : cccc.xxxx.com
3. 装饰器写法
- 装饰器的执行过程 : 将装饰器下方的被装饰函数的内存地址传给装饰器函数执行, 得到结果, 将结果赋值给被装饰器函数, 完成偷梁换柱
from flask import Flask
app = Flask(__name__)
app.debug = True
@app.route(rule='/',endpoint='index'strict_slashes=False,methods=["post","GEt"])
def index():
return 'Hello Word!'
if __name__ == '__main__':
app.run()
4.app.add_url_rule()
写法
def index():
return 'Hello Word!'
app.add_url_rule(rule='/', view_func=index, endpoint='index')
# 类比 Django 中的 urls.py 文件中的路由匹配
# path('/',view.IndexView,name='index')
if __name__ == '__main__':
app.run()
十.url_for() 的使用
1.url_for() 的作用
- 利用视图函数的名字动态的获取访问该视图函数的路由
- 一般视图函数名不会随意变化, 而视图访问的路由可能频繁变化, 如果是大项目手动去修改 url 就不方便了
- 使用
url_for()
方法动态获取路由就很好的解决了这种问题
2. 使用示例
from flask import Flask, url_for
app = Flask(__name__)
app.debug = True
@app.route('/index')
def index():
url = url_for('login')
print(url) # /login
return url
@app.route('/login')
def login():
return 'hahah'
if __name__ == '__main__':
app.run()
十一. 转换器
1. 五种转换器(与 Django 中转换器相同)
转换器名称 | 描述 |
---|---|
string | 默认类型,接受不带斜杠的任何文本 |
int | 接受正整数 |
float | 接受正浮点值 |
path | 接收 string 但也接受斜线 |
uuid | 接受 UUID(通用唯一识别码)字符串 xxxx-xxxx-xxxxx-xxxxx |
2. 使用
- 路由格式:
< 类型: 参数名 >
# int 整型
@app.route(rule='/user2/<int:id>')
def user2(id):
print('type(id), id:', type(id), id) # type(id), id: <class 'int'> 1
return f'<mark>Hello {id}!</make>'
# string 字符串
@app.route(rule='/user3/<string:id>')
def user3(id):
print('type(id), id:', type(id), id) # type(id), id: <class 'str'> zcdsb123
return f'<mark>Hello {id}!</make>'
# string 字符串
@app.route(rule='/user4/<float:id>')
def user4(id):
print('type(id), id:', type(id), id) # type(id), id: <class 'float'> 1.1
return f'<mark>Hello {id}!</make>'
# path 路径
@app.route(rule='/user5/<path:id>')
def user5(id):
print('type(id), id:', type(id), id) # type(id), id: <class 'str'> lxdsb/zcdsb
return f'<mark>Hello {id}!</make>'
# uuid 唯一识别码
@app.route(rule='/user6/<uuid:id>')
def user6(id):
print('type(id), id:', type(id), id) # type(id), id: <class 'uuid.UUID'> 95db2e6c-e7a7-11ea-9ca3-48ba4e4e6384
return f'<mark>Hello {id}!</make>'
3. 转换器默认配置
- flask 系统自带转换器编写在 werkzeug.routing.py 文件中
#: the default converter mapping for the map.
DEFAULT_CONVERTERS: t.Mapping[str, t.Type[BaseConverter]] = {
"default": UnicodeConverter,
"string": UnicodeConverter,
"any": AnyConverter,
"path": PathConverter,
"int": IntegerConverter,
"float": FloatConverter,
"uuid": UUIDConverter,
}
# 每种转换器都映射到一个类, 类中书写了相应的匹配规则
十二. 自定义转换器
1. 为什么自定义转换器
- 关于路由参数的限制,flask 内置的类型不够具体,在开发中,我们经常接受参数,需要更加精确的限制
- 这时候,可以使用正则匹配路由参数
- 正则匹配路由参数,其实就是扩展 flask 内置的路由限定类型,需要完成 4 个步骤
2. 自定义转换器步骤
- 导入转换器基类:在 Flask 中,所有的路由的匹配规则都是使用转换器对象进行记录
- 自定义转换器:自定义类继承于转换器基类
- 添加转换器到默认的转换器字典中
- 使用自定义转换器实现自定义匹配规则
3. 自定义一个专门处理手机号的转换器
from flask import Flask, request
# 初始化
app = Flask(import_name=__name__)
# 编写路由视图
@app.route(rule='/')
def index():
return "<h1>hello world!</h1>"
# 1. 引入 flask 的路由转换器
from werkzeug.routing import BaseConverter
# 2. 创建自定义路由转换器, 继承 BaseConverter
class MobileConverter(BaseConverter):
def __init__(self, map, *args):
super().__init__(map)
self.regex = "1[3-9]\d{9}" # 匹配手机号
# 3. 把自定义转换器添加到 flask 默认的转换器字典中,也就是和原来的 int,float 等放在一块
app.url_map.converters['mobile'] = MobileConverter
# 4. 类似原来的路由参数限制一样,调用自定义转换器名称即可
@app.route(rule='/user/<mobile:mobile>')
def user(mobile):
return mobile
4. 自定义一个匹配邮箱的转换器
# 1. 引入 flask 的路由转换器
from werkzeug.routing import BaseConverter
# 2. 创建自定义路由转换器, 继承 BaseConverter
class RegexConverter(BaseConverter):
def __init__(self, map, *args):
super().__init__(map)
self.regex = args[0] # 拿到匹配时传过来的匹配规则 args=(\w+@\w+\.\w+,)
# 3. 把自定义转换器添加到 flask 默认的转换器字典中,也就是和原来的 int,float 等放在一块
app.url_map.converters['re'] = RegexConverter
# 4. 类似原来的路由参数限制一样,调用自定义转换器名称, 这里传入的匹配规则会赋值给 args
@app.route(rule='/user/<re("\w+@\w+\.\w+"):email>')
def user2(email):
print(app.url_map) # 获取所有的路由列表
return email
# 声明和加载配置
class Config():
DEBUG = True
app.config.from_object(Config)
5. 万能转换器
from flask import Flask, jsonify, views, url_for
from werkzeug.routing import BaseConverter
app = Flask(__name__)
app.debug = True
class RegexConverter(BaseConverter):
"""
自定义 URL 匹配正则表达式
"""
def __init__(self, map, regex):
super(RegexConverter, self).__init__(map)
# self.regex 固定的属性,专门用来存放正则表达式, flask 会去使用这个属性来进行路由的正则匹配
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
# regex(参数): 参数传入正则表达式,这样就可以转换任何你传入的规则
@app.route('/index/<regex("\d+"):nid>', endpoint='index')
def index(nid):
print(nid)
return 'This is index!'
if __name__ == '__main__':
app.run()
十三.CBV
from flask import Flask, jsonify, views
app = Flask(__name__)
app.debug = True
def auth(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print("auth")
return res
return wrapper
def login(func):
def wrapper(*args, **kwargs):
res = func(*args, **kwargs)
print("login")
return res
return wrapper
# 继承 MethodView
class IndexView(views.MethodView):
methods = ['GET', "POST"] # 限制请求方式
decorators = [auth, login] # 添加装饰器, 执行顺序自上而下
def get(self, k1):
print(k1)
return 'GET 请求!'
def post(self):
return 'POST 请求!'
# CBV 路由注册,as_view( )中必须传 name, name 是该路由用于反向解析时的别名
app.add_url_rule('/', view_func=IndexView.as_view(name='index'),
defaults={'k1': 'hello word!'},strict_slashes=False)
if __name__ == '__main__':
app.run()
十四. 模板
flask 使用的是 jinja2 的模板语法, 想比较 Django 的 DTL 更强大,它 支持加括号执行,支持传值, 字典支持中括号取值和 get 取值
1. 变量渲染 : {{}}
2. 循环变量 : {% for i in items %}
<body>
<h1> 用户列表 </h1>
<table>
{% for k,v in user_dict.items() %}
<tr>
<td>{{k}}</td>
<td>{{v.name}}</td>
<td>{{v['name']}}</td>
<td>{{v.get('name')}}</td>
<td><a href="/detail/{{k}}"> 查看详细 </a></td>
</tr>
{% endfor %}
</table>
</body>
3. 逻辑判断 : {% if|else %}
<body>
<h1> 用户列表 </h1>
<table>
{% if name %}
<h1>Hello {{name}}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
</table>
</body>
4. 处理 XSS 攻击
- 在 Django 中处理 XSS 攻击可以使用
make_safe
方法将其转意
from django.utils.safestring import mark_safe
html_safe = mark_safe('<h1> 你好 </h1>')
- 在 falsk 中可以使用
Markup()
方式将其转意
# 被弃用的方法(但还能使用)
from flask import Flask,Markup
Markup("<input type='text' value='%s' />" %(args,))
# 但查看源码可以发现在 jinja2-3.1 版本已经将该中方法弃用了, 使用了新的方法
import markupsafe
markupsafe.Markup("<input type='text' value='%s' />" %(args,))
- 代码示例
from flask import Flask,render_template,Markup,jsonify,make_response
import markupsafe
app = Flask(__name__)
app.debug = True
def func(args):
# return Markup("<input type='text' value='%s' />" %(args,))
return markupsafe.Markup("<input type='text' value='%s' />" %(args,))
@app.route('/')
def index():
return render_template('index.html',func=func)
if __name__ == '__main__':
app.run()
<!-- index.html -->
<body>
<h3>{{func(222)}}</h3>
<h3>{{func(333)}}</h3>
</body>
- 效果
- 为了防止 XSS 攻击, flask 会将标签符号 "< >" 变成 "\>", "\<" 这种, 如果我们想将这种格式的字符串渲染到前端页面是个标签而不是字符串, 我们就可以使用 "Markup()" 方法
from flask import Flask,request,render_template,jsonify,make_response
import markupsafe
app = Flask(__name__)
app.debug = True
@app.route('/')
def index():
# string = "<input type='text' value='123'>"
string = " < input type='text' value='123' >"
test = markupsafe.Markup(string)
return render_template('index.html',test=test)
if __name__ == '__main__':
app.run()
<body>
{{test}}
</body>
<!-- 或者后端不使用 "Markup" 处理, 前端使用 "safe" 过滤器来实现(django 中也是如此) -->
<body>
{{test|safe}}
</body>
十五. 请求对象与响应对象
1. 请求对象
from flask import Flask,request,render_template,redirect,make_response
app = Flask(__name__)
@app.route('/',methods=['get',"POst"])
def test():
print(request.method) # 获取请求方式
print(request.args) # 获取 get 请求数据
print(request.form) # 获取 post 请求数据(不包含文件数据)
print(request.values) # 包含 get 请求,post 请求数据
print(request.cookies) # 获取客户端携带的 cookie
print(request.headers) # 获取请求头中所携带的数据
print(request.path) # 获取不带域名只带参数的请求路径
print(request.full_path) # 获取不带域名的但携带 "?" 后参数的路径
print(request.script_root) #
print(request.url) # 获取请求路径 http://127.0.0.1:5000/
print(request.base_url) # 获取请求路径 http://127.0.0.1:5000/
print(request.url_root) # 获取请求路径 http://127.0.0.1:5000/
print(request.host_url) # 获取请求路径 http://127.0.0.1:5000/
print(request.host) # 获取当前请求用户 IP 和 port: 127.0.0.1:500
print(request.files) # 获取文件数据
return 'ok'
if __name__ == '__main__':
app.run()
ps : 在 Flask 中 request 是一个全局变量, 而在 Django 中是每一个视图函数都有一个 request 对象, 为什么在 flask 中使用 request 不会混乱呢? 一定是 Flask 帮我们处理了这个问题, 后面讲解
2. 响应对象
from flask import Flask,request,render_template,redirect,jsonify,make_response
app = Flask(__name__)
@app.route('/',methods=['get',"POst"])
def test():
# return 'ok' # 直接返回字符串, 支持返回 HTML 标签
# return render_template('index.html',locals()) # 渲染一个 HTML 页面, 并可以将数据传入
# return redirect('/index.html') # 重定向到某个页面
# return jsonify({"k1":"v1"}) # 返回 json 格式数据(Django 中使用 JsonResponse)
# 返回 response 对象
response = make_response("Hello word!")
# 在 response 中操作 cookie
response.set_cookie("name","shawn") # 设置 cookie : key-value
# response.delete_cookie('name') # 删除 cookie
# 设置响应头
response.headers['NAME'] = "I am Shawn!"
# response 是 flask.wrapper 中 Response 的对象
print(type(response)) # <class 'flask.wrappers.Response'>
from flask.wrappers import Response
return response
if __name__ == '__main__':
app.run()
- 浏览器 cookie
- 响应头 Response Headers
十六.session
1. 介绍
-
除请求对象之外,还有一个 session 对象。它允许你在不同请求间存储特定用户的信息
-
它是在 Cookies 的基础上实现的,并且对 Cookies 进行密钥签名要使用会话,你需要设置一个密钥 (
app.session_interface
对象)
2.cookie, session, token 区别
- cookie: 存放在客户端的键值对
- session:存放在客户端的键值对
- token: 存放在客户端,通过算法来校验(Django 中使用的是 jwt 认证实现)
3.session 操作
- 注意 : 在使用 session 之前必须设置一个秘钥
app.secret_key = "the_secret_key" # 秘钥值随便
- session 实现的源码位置
app.session_interface # 后面源码详细讲解..
- ` 示例
from flask import Flask, request, jsonify, make_response, session
app = Flask(__name__)
app.debug = True
# 设置秘钥
app.secret_key = 'the_secret_key'
@app.route('/', methods=['GET', 'POST'])
def index():
# 设置 session
session['name'] = 'shawn'
return '<h1>Hello Word!</h1>'
@app.route('/check', methods=['GET', 'POST'])
def order():
name = session.get('name', None)
if name:
return '<h1>session["name"]=%s</h1>' % name
return '<h1>session 中没有找到 name</h1>'
@app.route('/delete', methods=['GET', 'POST'])
def delete():
# 删除 session
session.pop('name')
return '<h1>name 被删除 </h1>'
if __name__ == '__main__':
print(app.config)
app.run()
- 演示
十七. 闪现 (message)
1. 介绍
- 作用 : 当页面 a 出错时, 会跳转到页面 b, 并将页面a 的错误信息显示出来
- 设值 :
flash('value')
- 取值 :
get_flashed_message()
, 默认取所有闪现信息 - 注意 : 闪现是基于 session 的,所以也必须设置
app.secret_key
秘钥
2. 原理
比如有两个函数 error 和 index,在 index 页面发起请求时,index 生成一个错误,放到 session 中,跳转到 error 页面显示错误,然后再把 session 移除,并且这个错误只能取一次(也就是让你看一次就会被删除)
3. 特点示例
# 设置多个闪现值
flash('超时错误 1')
flash('超时错误 2',category="x2")
flash('超时错误 3',category="x3")
# 使用 get_flashed_message()默认一次性全部取完
data = get_flashed_message()
print(data)
"""
超时错误 1
超时错误 2
超时错误 3
"""
# 如果按闪现分类来取, 可以实现在不同地方取出不同的闪现值
flash('超时错误 1',category="x1")
flash('超时错误 2',category="x2")
flash('超时错误 3',category="x3")
# 按分类取闪现值
data = get_flashed_messages(category_filter=['x1'])
print(data) # 超时错误 1
data = get_flashed_messages(category_filter=['x2','x3'])
print(data0) # ['超时错误 2','超时错误 3']
4. 使用
from flask import Flask,flash,get_flashed_messages,request,redirect
app = Flask(__name__)
app.debug = True
# 闪现基于 session, 所以也需要设置秘钥
app.secret_key = 'the_secret_key'
@app.route('/index')
def index():
val = request.args.get('value')
if val == 'hello':
return 'Hello World!'
flash('超时错误',category="x1") # category 表示对数据进行分类
return redirect('/error')
@app.route('/error')
def error():
"""
展示错误信息
:return:
"""
# 取出该分类的所有数据, 并删除该数据, 如果有多个分类则是个列表的形式["x1","x2"],
data = get_flashed_messages(category_filter=['x1'])
if data:
msg = data[0]
else:
msg = "..."
return " 错误信息:%s" %(msg,)
if __name__ == '__main__':
app.run()
- 演示
十八. 请求扩展
1.before_request
- 作用 : before_request 相当于 django 中的 process_request,每一个请求在被处理前都会经过这个方法
- 应用 : 用户登录认证(这样避免了每一个视图函数都加用户登录认证的装饰器)
- 注意 : before_request 的返回值为 None 才会往后走, 否则直接返回你的返回值, 如果定义了 after_request 那么会接着它执行, 最终本次请求响应结束
- 示例
from flask import Flask, request, render_template, session, url_for, redirect, flash, get_flashed_messages
from markupsafe import Markup
app = Flask(__name__)
app.debug = True
app.secret_key = 'the_secret_key'
@app.before_request
def process_request(*args, **kwargs):
# 判断访问的是不是登入路径, 是的话返回 None 继续往后走
if request.path == '/login':
return None
else:
# 不是的话判断是否携带用户信息(判断是否登入状态)
username = session.get('username')
print('username', username)
if username:
return None
else:
# 如果没有, 则重定向到登入界面
return redirect('/login')
@app.route('/login')
def login():
username = request.args['username']
print(username)
if username == 'shawn':
session['username'] = username
return redirect('/index')
else:
return render_template('login.html')
@app.route('/index')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
2. after_request
-
作用 : 类比 django 中间件中的 process_response,如果请求没有出现异常的情况下, 会在请求返回 return 之前执行. 但是如果有多个顺序是从下往上执行.
-
与 Django 中 process_response 的区别
Django 中当请求返回 return 后, 会从当前位置结束接着从当前位置 response 出去
Flask 中的 after_request 请求返回 return 之后, 后面的 response 也会一个个走完
@app.after_request # 后执行
def process_response1(response):
print('process_response1')
return response
@app.after_request # 先执行
def process_response2(response):
print('process_response2')
return response
3.before_first_request
- 作用 : 顾名思义, 项目启动第一次请求时触发执行
- 应用 : 项目初始化用来保证以后项目只要不重启就不再继续执行
@app.before_first_request
def before_first():
print("before_first")
4. teardown_request
- 效果 : 不管什么情况, 都会触发, 即便遇到了异常, 并且返回 return 没有任何效果, 无法控制返回结果
- 应用 : 记录日志
@app.teardown_request
def ter(e):
print(" 不管什么情况,都会触发,即便遇到了异常 ")
5.errorhandler
- 作用: 绑定错误的状态码进而可以捕获服务器的错误, 并返回对应的错误页面
@app.errorhandler(404)
def error_404(arg):
return "404 页面找不到了..."
6.template_global
- 作用: 全局的标签, 在任意的 html 页面中就可以直接使用, 不需要在 render_template 中传递参数以后才能使用
@app.template_global()
def gl(a1, a2):
return a1 + a2
# html 文件中使用
{{gl(1,2) }}
7.template_filter
- 作用: 全局的过滤器, 在任意的 html 页面中就可以直接使用, 不需要在 render_template 中传递参数以后才能使用
@app.template_filter()
def db(a1, a2, a3):
return a1 + a2 + a3
# html 文件中使用, 相比较 Django 的过滤器最多只能传两个参数, 这里可以传多个
# 1 传给 a1,2-->a2,3-->a3
{{1|db(2,3) }}
8. 示例
- 测试 1
from flask import Flask, request, render_template, session, url_for, redirect, flash, get_flashed_messages
from markupsafe import Markup
app = Flask(__name__)
app.debug = True
app.secret_key = 'the_secret_key'
@app.before_request
def before_request(*args,**kwargs):
print("before_request1")
return 'before_request1' # 这里 return 非 None
@app.before_request
def before_request(*args,**kwargs):
print("before_request2") # 前面 return 非 None, 这里不走了
@app.after_request
def after_request(response):
print("after_request1")
return response # 后出去
@app.after_request
def after_request(response):
print("after_request2")
return response # 先出去
# 第一次请求的时候触发
@app.before_first_request
def before_first():
print("before_first")
@app.teardown_request
def ter(e):
print(" 不管什么情况,都会触发,即便遇到了异常 ")
@app.route('/index')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
"""
输出结果 :
before_first
before_request1
after_request2
after_request1
不管什么情况,都会触发,即便遇到了异常
"""
- 测试 2
from flask import Flask, request, render_template, session, url_for, redirect, flash, get_flashed_messages
from markupsafe import Markup
app = Flask(__name__)
app.debug = True
app.secret_key = 'the_secret_key'
@app.errorhandler(404)
def error_404(arg):
return "404 页面找不到了..."
@app.template_global()
def gl(a1, a2):
return a1 + a2
@app.template_filter()
def db(a1, a2, a3):
return a1 + a2 + a3
@app.route('/index')
def index():
return render_template('index.html')
if __name__ == '__main__':
app.run()
<!--html-->
<body>
<h2>{{gl(10, 20)}}</h2>
<hr>
<h2>{{10|db(10,10)}}</h2>
</body>
- 效果
十九. 中间件
1. 自定义中间件
- 严格来讲, flask 中并没有中间件, 但是我们查看其源码可以模拟一个中间件
from flask import Flask
app = Flask(__name__)
app.debug = True
class MyMiddleware(object):
def __init__(self, old_wsgi_app):
self.old_wsgi_app = old_wsgi_app
def __call__(self, environ, start_response):
print('开始之前')
ret = self.old_wsgi_app(environ, start_response)
print('结束之后')
return ret
if __name__ == '__main__':
app.wsgi_app = MyMiddleware(app.wsgi_app)
app.run()
1. 源码分析
- 我们发现当执行
app.run
方法的时候,最终执行run_simple
,最后执行app()
, app 是 Flask 类的对象, 对象加括号触发类中的__call__()
方法, 也就是在执行app.__call__
方法
- 所以, 我们找到 Flask 类中的
__call__()
方法, 该方法直接返回了self.wsgi_app(environ, start_response)
- 该返回值执行
wsgi_app()
方法, 接收 environ(请求), start_response(响应), 也就是说 flask 中所有的内容调度都是从这里开始的, 这个方法就是 程序的入口 - 那么好办了, 我们想要实现中间件就必须在内重写
wsgi_app()
方法, 在该方法前后添加一些自己想要的功能 - 定义一个中间件 MyMiddleware, 在内部书写初始化方法
__init__()
, 用来接收原始的app.wsgi
对象并转成自己的对象 - 因为
app+()
会触发类中__call__()
方法, 所以在类中书写__call__()
方法, 里面重写wsgi_app()
方法, 于是你就可以在wsgi_app()
方法前后进行自己功能的添加
- 最后将自己的
wsgi_app
赋值给app
对象完成替换
二十. 蓝图 (Blueprint)
1. 蓝图作用
- 一般做测试的时候我们习惯将所有的程序 : 视图函数, 路由, 入口等放在一个 py 文件中
- 但如果视图函数成千上百个, 那是不是会显得运行文件特别乱, 我们是不是应该抽取出来专门的 py 文件来进行管理呢
- 这个时候蓝图就派上用场了, 我们可以 利用蓝图对程序目录进行划分
2. 不使用蓝图, 手动分文件
- 目录结构
-templates # 模板文件
-views # 视图文件
-__init__.py
-user.py
-order.py
-app.py # 启动文件
app.py
from views import app
if __name__ == '__main__':
app.run()
views/__init__.py
from flask import Flask,request
from . import account # 必须导
from . import order
from . import user
app = Flask(__name__)
views/user.py
from . import app
@app.route('/user')
def user():
return 'user'
views/order.py
from . import app
@app.route('/order')
def order():
return 'order'
3. 蓝图基本使用命令
- 导入蓝图
from flask import Blueprint
- 创建蓝图对象
user_bu = Blueprint('user',__name__) # user.py
order_bu = Blueprint('order',__name__) # order.py
- 创建蓝图参数 :
url_prefix
前缀
user_bu = Blueprint('user',__name__,url_prefix='/user',template_folder='views_templates')
# 使用 url_prefix 参数为访问视图的路径添加一个前缀, 可以更加方便的管理视图函数
# 比如之前访问的路径是 : 127.0.0.1:5000/login/
# 那么添加之后访问的路径是 : 127.0.0.1:5000/user/login/
- 创建蓝图参数 :
template_folder
指定 templates
# 使用 template_folder 参数给当前蓝图单独使用 templates
# 向上查找,当前找不到,会找总 templates 文件夹
- 利用蓝图创建路由关系
@user_bu.route('/login/') # user.py
@order_bu.route('/chaeck_order/') # order.py
- 注册蓝图
app.register_blueprint(user_bu)
app.register_blueprint(order_bu)
4. 使用蓝图构建中小型系统目录示例
- 目录结构
-flask_pro
-flask_app
-__init__.py
-static
-templates
-views
-order.py
-user.py
-manage.py
__init__.py
from flask import Flask
app=Flask(__name__)
from flask_app.views import user
from flask_app.views import order
app.register_blueprint(user.user_bu)
app.register_blueprint(order.order_bu)
manage.py
from flask_app import app
if __name__ == '__main__':
app.run(port=8008)
user.py
from flask import Blueprint
user_bu=Blueprint('user',__name__)
@user_bu.route('/login')
def login():
return 'login'
order.py
from flask import Blueprint
order_bu=Blueprint('order',__name__)
@order_bu.route('/test')
def test():
return 'order test'
5. 使用蓝图构建大型系统
-
所谓的大型并不是真正的大型, 只是做一个参考, 毕竟我们更愿意使用 Django 去创建大型项目
-
目录结构
│ run.py
│
│
└─pro_flask # 文件夹
│ __init__.py
│
├─admin # admin 应用文件夹
│ │ views.py # 视图
│ │ __init__.py
│ │
│ ├─static # 静态资源文件夹
│ └─templates # 模板文件夹
│
└─web # web 应用文件夹
│ views.py # 视图
│ __init__.py
│
├─static # 静态资源文件夹
└─templates # 模板文件夹
二十一. 自定义 local 对象 (实现并发处理请求)
1. 思考及需求
- 要 实现并发效果, 每一个请求进来的时候我们都开启一个进程, 这显然是不合理的, 于是就可以使用线程
- 如果我们的需求是每个线程都对变量 num 进行设值, 并打印其线程号, 其效果如下 :
from threading import Thread, get_ident # 可以获取线程 id
import time
num = -1
def task(arg):
global num
num = arg
time.sleep(2)
print(num, f'线程{get_ident()}')
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
"""
9 线程 12244
9 线程 13324
9 线程 7972
9 线程 134249
9 线程 12476
9 线程 13408
...
"""
很明显, 数据改乱了, 是个线程更改的都是同一份数据, 对数据产生了不安全性
为了让他们的更改相互隔离, 于是就可以 :
- 加锁 : 不使用该方法, 加锁的思路是多个线程要真正实现共用一个数据, 而我们是要做请求对象的并发, 实现的是该线程对于请求对象这部分内容有任何修改并不影响其他线程
- 所以使用 local 对象 将要修改的数据复制一份,使得每个数据互不影响
2. 使用 local 对象
- local 对象只支持线程,不支持协程,生成自己的 local 对象,各改各的,互不影响
from threading import Thread
from threading import local
from threading import get_ident
import time
# local 对象, 当识别到新的进程会为其开辟一块新的内存空间, 相当于每个线程都对该值进行了拷贝
local_obj = local()
'''{' 线程 1':{'value':1},' 线程 2':{'value':1},' 线程 3':{'value':1},' 线程 4':{'value':1}}'''
def task(arg):
local_obj.value = arg
time.sleep(2)
print(local_obj.value,f'线程号:{get_ident()}')
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
"""
2 线程号:11896
3 线程号:8796
9 线程号:3100
8 线程号:9188
....
"""
3. 通过字典自定义 local 对象
- 面向过程式编写
from threading import get_ident, Thread
storage = {} # 初始化一个字典
'''{' 线程 1':{'value':1},' 线程 2':{'value':1},' 线程 3':{'value':1},' 线程 4':{'value':1}}'''
def set(k, v):
# 获取线程 id
ident = get_ident()
if ident in storage:
storage[ident][k] = v
else:
storage[ident] = {k: v}
def get(k):
ident = get_ident()
return storage[ident][k]
def task(arg):
set('val', arg)
v = get('val')
print(v)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
"""
0
1
2
3
4
5
6
7
8
9
"""
- 面向对象式编写
from threading import get_ident, Thread
class Local(object):
storage = {}
def set(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def get(self, k):
ident = get_ident()
return Local.storage[ident][k]
obj = Local()
def task(arg):
obj.set('val', arg)
v = obj.get('val')
print(v)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
- 面向对象方式二 : 点拦截方法
__setattr__
,__getattr__
from threading import get_ident, Thread
class Local(object):
storage = {}
def __setattr__(self, k, v):
ident = get_ident()
if ident in Local.storage:
Local.storage[ident][k] = v
else:
Local.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident()
return Local.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
print(obj.val)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
上面已经实现了 local 的功能, 但存在一个问题 : 如果我们想生成多个 local 对象,但是会导致多个 local 对象所管理的线程设置的内容都放到了类属性
storage = {}
里面, 于是我们可以想到将 storage 设置成对象属性
- 将
storage = {}
设置成对象属性, 实现每一个 local 对象所对应的线程设置的内容都放到自己的 storage 里面
from threading import get_ident, Thread
class Local(object):
def __init__(self):
object.__setattr__(self, 'storage', {})
# self.__setattr__('storage', {}) # 该种方式会产生递归调用
def __setattr__(self, k, v):
ident = get_ident()
if ident in self.storage:
self.storage[ident][k] = v
else:
self.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident()
return self.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
print(obj.val)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
4. 使用协程 | 线程来实现请求并发
- 协程属于应用级别的, 协程会替代操作系统自动切换遇到 IO 的任务或者运行级别低的任务, 而应用级别的切换速度远高于操作系统的切换
- 在 flask 中为了实现这种并发需求, 依赖于
werkzeug
包, 我们导入werkzeug
下的local
查看其源码
flask 0.12 版本
try:
from greenlet import getcurrent as _get_ident # 获取协程唯一标识
except ImportError:
from threading import get_ident as _get_ident # 获取线程唯一标识
- 发现最开始导入线程和协程的唯一标识的时候统一命名为
_get_ident
,并且先导入协程模块的时候如果报错说明不支持协程,就会去导入线程的_get_ident
,这样无论是只有线程运行还是协程运行都可以获取唯一标识 - 我们使用该方法实现协程 | 线程并发处理请求
try:
from greenlet import getcurrent as get_ident # 获取协程唯一标识
except Exception as e:
from threading import get_ident # 获取进程唯一标识
from threading import Thread
class Local(object):
def __init__(self):
object.__setattr__(self, 'storage', {})
def __setattr__(self, k, v):
ident = get_ident()
if ident in self.storage:
self.storage[ident][k] = v
else:
self.storage[ident] = {k: v}
def __getattr__(self, k):
ident = get_ident()
return self.storage[ident][k]
obj = Local()
def task(arg):
obj.val = arg
obj.xxx = arg
print(obj.val)
for i in range(10):
t = Thread(target=task, args=(i,))
t.start()
二十二. 偏函数 (partial)
1.partial 的作用
- 当函数的参数个数太多,需要简化时,使用
functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单
2. 示例
- partial 的第一个参数是原函数, 后面是原函数的参数
from functools import partial
def func(a, b, c):
return a + b + c
func1 = partial(func, 1, 2)
sum1 = func1(3)
print(sum1) # 6
func2 = partial(func, a=1, b=2)
sum2 = func2(c=3)
print(sum2) # 6
func4 = partial(func, 1)
sum3 = func4(b=2, c=3)
print(sum3) # 6
二十三.pipreqs 生成项目依赖
1.pipreqs 的作用
- 与
pip freeze
命令一样, 生成和安装项目依赖
2.pipreqs 与 pip freeze 的区别
pip freeze > requirements.txt
这种方式配合 virtualenv(虚拟环境)才好使,否则把整个环境中的包都列出来了pipreqs
这个工具的好处是可以通过对项目目录的扫描,自动发现使用了那些类库,自动生成依赖清单
3.pipreqs 工具的安装及使用
- 安装
pip install pipreqs
- 生成依赖文件
# 在项目的根目录下使用
pipreqs ./
# windowns 系统下可能产生编码错误, 使用时可以指定编码格式
pipreqs ./ --encoding=utf8
- 安装安装依赖文件
pip3 install -r requirements.txt