2020 Python 开发者调查结果显示Flask和Django是Python Web开发使用的最主要的两个框架。
Flask诞生于2010年,是Armin ronacher用Python 语言基于Werkzeug工具箱编写的轻量级Web开发框架。
Flask本身相当于一个内核,其他几乎所有的功能都要用到扩展(邮件扩展Flask-Mail,用户认证Flask-Login,数据库Flask-SQLAlchemy),都需要用第三方的扩展来实现比如可以用Flask扩展加入ORM,窗体验证工具,文件上传、身份验证等。Flask没有默认使用的数据库,可以选择MySQL或NoSQL。
其WSGI工具箱采用Werkzeug(路由模块),模板引擎使用Jinja2。这两个是Flask框架的核心。
Werkzeug是一个综合的WSGI Web应用程序库。它最初的WSGI应用程序的各种实用程序的简单集合,现已成为最先进的WSGI实用程序库之一。
Flask包装类Werkzeug,使用它来处理WSGI的细节,同时为定义强大的应用程序提供更多的结构和模式。
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello, World!')
if __name__ = '__main__':
form werkzeug.serving import run_simple
run_simple('localhost', 5000, application)
Werkzeug包括:
Werkzeug可以识别Unicode,并且不强制执行任何依赖项。开发人员可以选择模板引擎、数据库适配器、甚至如何处理请求。它可用于构建各种最终用户应用程序,例如博客、维基或公告板。
问题:
Django与Flask谁好?对比一下两个框架?
只有更合适->轻重对比->框架选择上,自由、灵活、高度定制选择Flask,快速实现业务、不考虑技术选型、越简单直接越好选择Django。
扩展列表:http://flask.pocoo.org/extensions/
$ pip install flask
# 导入Flask类 这个类的一个实例将是WSGI应用程序
from flask import Flask
# 创建Flask类实例 第一个参数是应用程序的欧克或包的名称 __name__是适用于大多数情况的方便快捷方式 一边Flask知道在哪里寻找资源
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!
'
if __name__ == '__main__':
app.run()
Bash
$ export FLASK_APP=hello
$ flask run -h 127.0.0.1 -p 5000
* Running on http://127.0.0.1:5000/
CMD
> set FLASK_APP=hello
> flask run
* Running on http://127.0.0.1:5000/
-h
:绑定地址-p
:绑定端口Flask程序实例在创建的时候,需要默认传入当前Flask程序所指定的包(模块),以下是Flask应用程序在创建的时候一些需要我们关注的参数。
__name__
即可。None
。static
。templates
。Django将所有配置信息都放到了settings.py
文件中,而Flask则不同。
Flask将配置信息保存到了app.config
属性中,给属性可以按照字典类型进行操作。
app.config.get(name)
app.config[name]
主要使用以下三种方式:
从配置对象中加载
app.config.from_object(配置对象)
class DefaultConfig(object):
"""默认配置"""
SECRET_KEY= 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
# 从配置对象中加载
app.config.from_object(DefaultConfig)
@app.route('/')
def index():
print(app.config['SECRET_KEY'])
应用场景:
作为默认配置写在程序代码中,可以继承
class DevelopmentConfig(DefaultConfig):
DEBUG=True
优点:
缺点:
从配置文件中加载
app.config.from_pyfile(配置文件)
新建一个配置文件setting.py
SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
Flask程序文件中
app = Flask(__name__)
app.config.from_pyfile('setting.py')
@app.route('/')
def index():
print(app.config['SECRET_KEY'])
return 'hello world'
应用场景:
在项目中使用固定的配置文件
优点:
缺点:
从环境变量中加载
环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数,如:临时文件夹位置和系统文件夹位置等。环境变量是在操作系统中一个具有特定名字的对象,它包含了一个或者多个应用程序所将使用到的信息。
通俗的理解,环境变量就是我们设置在操作系统中,由操作系统代为保存的变量值
在Linux系统中设置和读取环境变量的方式如下:
# 设置变量 变量名=变量值
export variable_name=variable_value
# 读取变量
echo $variable_name
Flask使用环境变量加载配置的本质是通过环境变量值找到配置文件,再读取配置文件的信息,其使用方式为
app.config.from_envvar('环境变量名')
环境变量名的值为配置文件的绝对路径
export PROJECT_SETTING='setting.py'
Flash程序中
app = Flask(__name__)
app.config.from_envvar('PROJECT_SETTING', silent=True)
silent
表示系统环境变量中没有设置相应值时是否抛出异常
Flase
:不安静处理,没有相应值时报错通知,默认为False。Ture
:安静处理,即使没有相应值也让Flask正常运行。优点:
缺点:
使用工厂模式创建Flask应用,并结合使用配置对象与环境变量加载配置
from flask import Flask
def create_flask_app(config):
"""
创建Flask应用
:param config: 配置对象
:return: Flask应用
"""
# 创建Flask应用实例
app = Flask(__name__)
app.config.from_object(config)
# 从环境变量指向的配置文件中读取的配置信息会覆盖掉从配置对象中加载的同名参数
app.config.from_envvar('PROJECT_SETTING', silent=False)
return app
class DefaultConfig(object):
"""默认配置"""
SECRET_KEY = 'X_X_X_X_X_X_X'
class DevelopmentConfig(DefaultConfig):
"""开发配置"""
DEBUG = True
app = create_flask_app(DevelopmentConfig)
@app.route('/')
def index():
print(app.config['SECRET_KEY'])
return 'hello world'
if __name__ == '__main__':
# 运行Flask提供的调试服务器
app.run()
可以指定运行的主机IP地址、端口、是否开启调试模式
app.run(host='0.0.0.0', port=5000, debug=True)
关于DEBUG调试模式
开发环境(development):写程序的时候使用的环境。
开发环境可以使用调试器(网页可以看到错误的详细信息)和重载器(代码修改自动重载)
生产环境(production):程序上线以后使用的环境(默认为生产环境)。
# 开发模式
export FLASK_ENV=development
# 生产模式
export FLASK_ENV=production
命令行方式
$ export FLASK_APP=hello
$ flask routes
Endpoint Methods Rule
----------- ------- -----------------------
hello_world GET /
static GET /static/<path:filename>
程序中获取
在应用程序中的url_map属性中保存着整个Flask应用的路由映射信息,可以通过读取这个属性获取路由信息。
print(app.url_map)
Map([<Rule '/' (HEAD, OPTIONS, GET) -> hello_world>, <Rule '/static/' (HEAD, OPTIONS, GET) -> static>])
如果想在程序中遍历路由信息,可以采用如下方式:
print([(rule.endpoint, rule.rule) for rule in app.url_map.iter_rules()])
[('hello_world', '/'), ('static', '/static/' )]
在Flask中,定义路由其默认的请求方式为:
利用methods
参数可以指定接口的请求方式。
@app.route('/case1/', methods=['POST'])
def hello_world():
return 'Hello, World!
'
@app.route('/case2/', methods=['GET', 'POST'])
def hello_world():
return 'Hello, World!
'
在Flask中,使用蓝图blueprint来分模块组织管理。
蓝图实际可以理解为是一个存储一组视图方法的容器对象,其具有如下特点:
/use/
、/goods/
buleprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。
创建一个蓝图对象
# 创建蓝图对象
use_bp = Blueprint('use', __name__)
在蓝图对象上进行操作,注册路由,指定静态文件夹,注册模板过滤器
# 定义视图
@use_bp.route('/')
def index():
return 'hello blueprint'
在应用对象上注册这个蓝图对象
# 注册蓝图
app.register_blueprint(use_bp)
类似django的app应用
建立一个Python包,起名为goods
,在该包的 __init__.py
文件中定义蓝图。
from flask import Blueprint
# 定义蓝图
goods_bp = Blueprint('goods', __name__)
# 必须在定义蓝图后导入此文件否则项目无法找到视图 避免循环引用
from . import views
在goods
包中建立views.py
,在views.py
定义视图。
from . import goods_bp
@goods_bp.route('/goods/')
def get_goods():
return 'get_goods'
在应用对象中注册蓝图
from goods import goods_bp
app.register_blueprint(goods_bp, url_prefix='/goods')
和应用对象不同,蓝图对象创建时不会默认注册静态目录的路由。需要我们在创建是指定static_folder
参数。
下面的示例将蓝图所在目录下的static_admin目录设置为静态目录,之后可以使用/goods/static_goods/
,可以通过static_url_path
修改访问路径。
goods_bp = Blueprint('goods', __name__, static_folder='static_goods')
蓝图对象默认的模板目录为系统的模板目录,可以在创建蓝图对象时使用template_folder
关键字参数设置模板目录。
goods_bp = Blueprint('goods', __name__, template_folder='template_goods')
例如,有一个请求访问的接口地址为/users/123
,其中123
实际上为具体的请求参数,表面请求123号用户的信息,此时如何从URL中提取出123的数据?
Flask
不同于Django
直接在定义路由时编写正则表达式的方式,而是采用转换器语法:
@app.route('/user/' )
def user_info(user_id):
"""
获取用户信息
:param user_id:
:return:
"""
return f'hello {user_id}'
此处的<>
是一个转换器,默认为字符串类型,将该位置的数据以字符串格式进行匹配、并以字符串为数据类型,user_id
作为参数名传入视图。
类型 | 解释 |
---|---|
string | 接受任何不带斜杠的字符 |
int | 接受正整数 |
float | 接受正浮点值 |
path | 接受字符但也接受斜杠 |
uuid | 接受UUID字符串 |
正整数
@app.route('/user/' )
def user_info(user_id):
"""
获取用户信息
:param user_id:
:return:
"""
return f'hello {user_id}'
创建转化器类,创建匹配时的正则表达式。
from werkzeug.routing import BaseConverter
class MobileConverter(BaseConverter):
"""
手机号码转换器
"""
regex = r'1[1-9]\d{9}'
注意:regex
名字固定
将自定义的转换器告知Flask应用
app = Flask(__name__)
# 将自定义转换器添加到转换器字典中,并指定转换器使用时名字为 mobile
app.url_map.converters['mobile'] = MobileConverter
在使用转化器的地方定义使用。
@app.route('/sms_codes/' )
def sms_codes(mob_num):
"""
获取用户信息
:param mob_num:
:return:
"""
return f'{mob_num}'
如果想要获取其他地方传递的参数,可以通过Flask提供的request
对象来读取。不同的位置的参数都存放在request的不同属性中。
属性 | 说明 | 类型 |
---|---|---|
data | 记录请求的数据,并转化为字符串 | * |
form | 记录请求中的表单数据 | MultiDict |
args | 记录请求中的查询参数 | MultiDict |
cookies | 记录请求中的cookie信息 | Dict |
headers | 记录请求中的报文头 | EnvironHeaders |
method | 记录请求使用的HTTP方法 | GET/POST |
url | 记录请求的URL地址 | string |
files | 记录请求上传的文件 | * |
args请求中的查询参数
http://127.0.0.1:5000/user?user_id=123
from flask import request
@app.route('/user')
def user_info():
"""
获取用户信息
:return:
"""
user_id = request.args.get('user_id')
客户端上传图片到服务器,并保存到服务器。
from flask import request
@app.route('/upload_file', methods=['POST'])
def upload_file():
"""
上传文件
:return:
"""
# 接收文件
file = request.files['picture']
# 保存文件
file.save('./demo.png')
# 文件的参数名
print(file.name)
# 原来的文件名
print(file.filename)
return 'upload file success!'
使用render_template
方法渲染模板并返回。
Flask
from flask import render_template
@app.route('/')
def index_template():
"""
模板渲染
:return:
"""
data = {
'name': 'Jack',
'age': 20,
}
return render_template('index.html', data=data)
jinja2
响应DEMO
响应DEMO
{{ data['name']}}
{{ data['age']}}
from flask import redirect
@app.route('/redirect')
def index_redirect():
"""
重定向
:return:
"""
return redirect('https://wujing.blog.csdn.net/')
from flask import jsonify
@app.route('/json')
def index_json():
"""
返回JSON数据
:return:
"""
data = {
'name': 'Jack',
'age': 20,
}
return jsonify(data)
jsonify
Content-Type:application/json
元组方式
可以返回一个元组,这样的元组必须是(response, status, headers)
的形式,并且至少包含一个元素。
status
值会覆盖状态码,headers
可以是一个列表或字典,作为额外的消息标头值。
from flask import jsonify
@app.route('/demo')
def demo():
"""
自定义状态码和响应头
:return: response, status, headers
"""
# return jsonify({'自定义状态码': 123}), 666, [('token', 123)]
return jsonify({'自定义状态码': 123}), 666, {'token': 123}
make_response方式
from flask import make_response
@app.route('/demo2')
def demo2():
"""
make_response方式自定义状态码和响应头
:return:
"""
resp = make_response('make_response方式自定义状态码和响应头')
resp.headers['token'] = '123'
resp.status = '404 not found'
return resp
设置cookie
from flask import make_response
from flask import jsonify
@app.route('/set_cookie')
def set_cookie():
"""
设置cookie
:return:
"""
# 创建响应响应
resp = make_response(jsonify({'设置cookie': '成功'}))
# 设置cookie
resp.set_cookie('username', 'jack', max_age=60)
return resp
获取cookie
from flask import request
@app.route('/get_cookie')
def get_cookie():
"""
获取cookie
:return:
"""
# 从cookies中获取用户名
username = request.cookies.get('username')
return username
删除cookie
from flask import make_response
from flask import jsonify
@app.route('/delete_cookie')
def delete_cookie():
"""
删除cookie
:return:
"""
# 创建响应
resp = make_response(jsonify({'删除cookie': '成功!'}))
# 删除cookie
resp.delete_cookie('username')
return resp
需要先设置SECRET_KEY
from flask import Flask
app = Flask(__name__)
# 设置加密盐
app.secret_key = 'a_a_a_a_a_a_a_a'
设置session
from flask import session
@app.route('/set_session')
def set_session():
"""
设置session
:return:
"""
session['username'] = 'Jack'
return '设置session成功!'
获取session
from flask import session
@app.route('/get_session')
def get_session():
"""
获取session
:return:
"""
name = session['username']
return f'{name} 获取session成功!'
删除session
from flask import session
@app.route('/delete_session')
def delete_session():
"""
删除session
:return:
"""
session.pop('username', None)
return '删除session成功!'
思考
flask将session数据保存到哪里了?
Flask中的session叫做浏览器session,保存在客户端的缓存中,通过设置secret_key
添加签名来保证加密性。
abort()
抛出一个给定状态代码的HTTPException或者指定响应,例如想要用一个页面未找到异常来终止请求。
abort()抛出状态码智能是HTTP协议的错误状态码。
from flask import abort
@app.route('/')
def index():
"""
异常抛出
:return:
"""
c_id = request.args.get('c_id')
# 如果没有传入参数c_id 抛出错误400
if not c_id:
abort(400)
return c_id
errorhandler
装饰器,注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法。
例如:同一给处理状态码为400的错误,给用户友好的提示。
@app.errorhandler(400)
def request_error(e):
"""
捕获请求错误400
:param e:
:return:
"""
return '请求错误!'
例如:捕获指定异常
@app.route('/')
def index():
"""
异常抛出
:return:
"""
raise TypeError
return 'ok'
@app.errorhandler(TypeError)
def request_error(e):
"""
捕获指定异常
:param e:
:return:
"""
return '捕获了TypeError异常'
Django中间件,中间件的请求流程:
# 注册中间件
middlware_1,
middlware_2,
middlware_3,
# 请求流程
middlware_1.process_request() -> middlware_2.process_request() -> middlware_3.process_request() -> view() -> middlware_3.after_request() -> middlware_2.after_request() -> middlware_1.after_request()
# 中间件处理不区分具体是哪个视图,对所有视图统统生效。
在客户端和服务器交互的过程中,有些准备工作或扫尾工作需要处理,比如:
为了让每个视图避免编写重复功能的代码,Flask提供了通用设施的功能,即请求钩子
请求钩子是通过装饰器的形式实现,Flask支持如下四种请求钩子:
before_first_request(f)
注册一个在第一次请求此应用程序实例之前运行的函数。该函数将在没有任何参数的情况下被调用,并且其返回值将被忽略。
before_request(f)
在每个请求之前注册一个要运行的函数。
例如,这可用于打开数据库连接,或从会话加载登录用户。
@app.before_request
def load_user():
if "user_id" in session:
g.user = db.session.get(session["user_id"])
after_request(f)
注册一个函数以在每次请求此对象后运行。
该函数是用响应对象调用的,并且必须返回一个响应对象。这允许函数在发送之前修改或替换响应。
如果函数引发异常,after_request
则不会调用任何剩余的 函数。因此,这不应用于必须执行的操作,例如关闭资源。teardown_request()
为此使用。
teardown_request(f)
在每个请求结束时运行的函数,不管是否有异常。这些函数在请求上下文弹出时执行,即使没有执行实际请求。
from flask import Flask
# 创建Flask实例
app = Flask(__name__)
@app.route('/')
def index():
"""
请求钩子示例
:return:
"""
print('view')
return '请求钩子示例'
@app.before_first_request
def before_first_request():
print('before_first_request')
@app.before_request
def before_request():
print('before_request')
@app.after_request
def after_request(response):
print('after_request')
response.headers['Content-Type'] = 'application/json'
return response
@app.teardown_request
def teardown_request(response):
print('teardown_request')
if __name__ == '__main__':
app.run()
上下文实现原理:Threadlocal 线程局部变量。
上下文:即使语境。在程序中可以理解为代码执行到某一时刻是,根据之前大漠所做的操作及下文(即将要执行的逻辑),可以决定在当前时刻不可以使用到的变量,或者可以完成的事情。
Flask中有两种上下文:请求上下文和应用上下文。
Flask中上下文对象:相当于一个容器,保存了Flask程序运行过程中的一些信息。
思考:在视图函数中,如果取到当前请求的相关数据?比如:请求地址、请求方式、cookie等等、
在Flask中,可以直接在视图函数中使用request这个对象进行获取相关数据,而request就是请求上下文对象,保存了当前本次请求的相关数据,请求上下文对象有:request、session。
request
封装了HTTP请求的内容,针对的是HTTP请求。例如:user = request.args.get('user')
,获取的是GET请求的参数。
session
用来记录请求会话中的信息,针对的是用户信息。例如: session['name'] = user.id
,可以记录用户信息,还可以通过session.get('name')
获取用户信息。
上下文的作用:
Flask里面内部实现的时候,虽然操纵的(request
, session
)是全局变量,可最终在不同的线程中使用的时候,却反应的线程内部的特征和整体没有关系,这样支持并发处理没有任何问题。
它的字面意思是应用上下文,但它不是一直存在的,它只是request context
中的一个对app的代理(人),所谓local proxy
。它的作用主要是帮助request获取当前的应用,它是伴request
而生,随request而灭。
应用上下文对象有: current_app
、g
。
应用程序上下文,用于存储应用程序中的变量,可以通过current_app.name
打印当前app的名称,也可以在current_app
中存储一些变量,例如:
示例:
主程序
from flask import Flask
from blueprint_demo import bp
# 创建Flask应用示例
app = Flask(__name__)
# 模拟redis连接
app.redis_cli = 'redis连接'
# 注册蓝图
app.register_blueprint(bp)
@app.route('/')
def index():
"""
示例
:return:
"""
return '请求成功!'
if __name__ == '__main__':
app.run()
蓝图
from flask import Blueprint
from flask import current_app
bp = Blueprint('bp', __name__)
@bp.route('/bp')
def bp_view():
"""
蓝图中定义视图
:return:
"""
print(current_app.redis_cli)
return 'bp'
current_app功能示例:current_app就是当前运行的Flask app,在代码不方便直接操作Flask的app对象时,可以操作current_app就等价与操作Flask App对象。
from flask import Flask
from flask import current_app
# 创建Flask应用实例
app_1 = Flask(__name__)
app_2 = Flask(__name__)
# 模拟redis连接
app_1.redis_cli = 'redis连接 1'
app_2.redis_cli = 'redis连接 2'
@app_1.route('/demo_1')
def demo_1():
return current_app.redis_cli
@app_2.route('/demo_2')
def demo_2():
return current_app.redis_cli
g作为Flask程序全局的一个临时变量,充当中间媒介的作用, 我们可以通过它在一次请求调用的多个函数间传递一些数据,每次请求都会重设这个变量。
示例
from flask import Flask
from flask import g
# 创建Flask应用实例
app = Flask(__name__)
def db_query():
"""
数据库查询数据
:return:
"""
user_id = g.user_id
user_name = g.user_name
# 查询profile
g.profile = str(user_id) + user_name
@app.route('/')
def get_user_profile():
g.user_id = 123
g.user_name = 'jack'
db_query()
return g.profile
if __name__ == '__main__':
app.run()
g对象与请求钩子的综合案例
需求
实现
请求-> 请求钩子(尝试判断用户的身份 对于未登录用户方向) 用g对象保存用户身份信息 g.user_id = 123
、g.user_id = None
->普通视图
->强势登录视图->装饰器
from flask import Flask
from flask import abort
from flask import g
from flask import render_template
from flask import jsonify
# 创建Flask应用实例
app = Flask(__name__)
@app.before_request
def authentication():
"""
用户认证
利用before_request请求钩子
在进入所有试图前先尝试判断用户身份
:return:
"""
# TODO 此处利用鉴权机制(cookie、session、jwt等)鉴别用户身份信息
# if
# g.user_id = 123
# else
g.user_id = None
def login_required(func):
"""
强制登录装饰器
:param func:
:return:
"""
def wrapper(*args, **kwargs):
if g.user_id is None:
# 未登录
abort(401)
else:
# 已登录
return func(*args, **kwargs)
return wrapper
@app.route('/')
def index():
"""
首页
不要求用户登录
:return:
"""
return f'首页 {g.user_id}'
@app.route('/profile')
@login_required
def get_user_profile():
"""
获取用户信息
要求必须登录
:return:
"""
return f'用户信息页面 {g.user_id}'
@app.errorhandler(401)
def request_error(e):
"""
捕获指定异常
:param e:
:return:
"""
# return render_template('login.html')
return jsonify({'code': 401, 'data': '', 'msg': '未登录'})
if __name__ == '__main__':
app.run()
思考
在Flask程序未运行的情况下,调试代码时需要使用current_app
、g
、request
这些对象,会不会有问题?该如何使用?
app_context
为我们提供了应用上下文环境,允许我们在外部使用应用上下文current_app
、g
、可以通过with
语句进行使用。
>>> from flask import Flask
>>> app = Flask('')
>>> app.redis_cli = 'redis client'
>>> from flask import current_app
>>> current_app.redis_cli # 错误,没有上下文环境
>>> with app.app_context(): # 借助with语句使用app_context创建应用上下文
... print(current_app.redis_cli)
...
request_context
为我们提供了请求上下文环境,允许我们在外部使用请求上下文request
、session
可以通过with语句进行使用。
>>> from flask import request
>>> app = Flask('')
>>> request.args # 错误,没有上下文环境
>>> environ = {'wsgi.version': (1, 0), 'wsgi.input': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'SERVER_NAME': 'itcast server', 'wsgi.url_scheme': 'http', 'SERVER_PORT': '80'} # 模拟解析客户端请求之后的wsgi字典数据
>>> with app.request_context(environ): # 借助with语句是使用request_context创建请求上下文
... print(request.path)
...