Flask
- 一 前言
- 二 快速使用
- 三 内置配置变量
- 四 配置文件的写法
- 五 路由
- 六 cbv写法
- 6.1 快速使用
- 6.2 cbv加装饰器
- 6.3 as_view的执行流程
- 6.4 as_view的name参数
- 6.5 继承View写cbv
- 七 模板语法
- 7.1 渲染变量
- 7.2 变量的循环
- 7.3 逻辑判断
一 前言
Flask是一个基于Python开发并且依赖jinja2模板(模板语言)和Werkzeug WSGI服务的一个微型框架,对于Werkzeug本质是Socket服务端,其用于接收http请求并对请求进行预处理,然后触发Flask框架,开发人员基于Flask框架提供的功能对请求进行相应的处理,并返回给用户,如果要返回给用户复杂的内容时,需要借助jinja2模板来实现对模板的处理,即:将模板和数据进行渲染,将渲染后的字符串返回给用户浏览器。
“微”(micro) 并不表示你需要把整个 Web 应用塞进单个 Python 文件(虽然确实可以 ),也不意味着 Flask 在功能上有所欠缺。微框架中的“微”意味着 Flask 旨在保持核心简单而易于扩展。Flask 不会替你做出太多决策——比如使用何种数据库。而那些 Flask 所选择的——比如使用何种模板引擎——则很容易替换。除此之外的一切都由可由你掌握。如此,Flask 可以与您珠联璧合。
默认情况下,Flask 不包含数据库抽象层、表单验证,或是其它任何已有多种库可以胜任的功能。然而,Flask 支持用扩展来给应用添加这些功能,如同是 Flask 本身实现的一样。众多的扩展提供了数据库集成、表单验证、上传处理、各种各样的开放认证技术等功能。Flask 也许是“微小”的,但它已准备好在需求繁杂的生产环境中投入使用。
Flask官方文档:
https://dormousehole.readthedocs.io/en/latest/
二 快速使用
安装
pip install Flask
使用
from flask import Flask # 导入Flask类
# 实例化产生Flask对象 有了__name__ Flask才知道在哪里找到模板和静态文件
app = Flask(__name__)
# 使用route装饰器来声明路由匹配成功执行的视图函数
@app.route('/')
def hello_word():
return 'Hello World!'
if __name__ == '__main__':
app.run()
三 内置配置变量
在Flask类实例化app对象时将默认配置赋值给了app.config
。
Flask/_init_
#: The configuration dictionary as :class:`Config`. This behaves
#: exactly like a regular dictionary but supports additional methods
#: to load a config from files.
self.config = self.make_config(instance_relative_config))
flask/app.py
default_config = ImmutableDict(
{
"ENV": None,
"DEBUG": None,
"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": None,
"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,
}
ENV
:应用运行于什么环境。 Flask 和 扩展可以根据环境不同而行为不同,如打开或 关闭调试模式。 env 属性映射了这个配置键。本变量由 FLASK_ENV 环境变量设置。如果本变量是在代码中设置的话,可能出 现意外。在生产环境中不要使用 development 。
DEBUG
:是否开启调试模式。使用 flask run 启动开发服务器时,遇到未能处理的 异常时会显示一个交互调试器,并且当代码变动后服务器会重启。 debug 属性映射了这个配置键。当 ENV 是 ‘development’ 时,本变量会启用,并且会被 FLASK_DEBUG 环境变量 重载。如果本变量是在代码中设置的话,可能会出现意外。在生产环境中不要开启调试模式。
TESTING
:开启测试模式。异常会被广播而不是被应用的错误处理器处理。扩展可能也会为 了测试方便而改变它们的行为。你应当在自己的调试中开启本变量。
PROPAGATE_EXCEPTIONS
:异常会重新引发而不是被应用的错误处理器处理。在没有设置本变量的情况下, 当 TESTING 或 DEBUG 开启时,本变量隐式地为真。
PRESERVE_CONTEXT_ON_EXCEPTION
:当异常发生时,不要弹出请求情境。在没有设置该变量的情况下,如果 DEBUG 为真,则本变量为真。这样允许调试器错误请求数据。本变量通常不 需要直接设置。
SECRET_KEY
:密钥用于会话 cookie 的安全签名,并可用于应用或者扩展的其他安全需求。 密钥应当是一个长的随机的 bytes 或者 str 。例如,复制下面的 输出到你的配置中:
import secrets
print(secrets.token_hex())
# cd607def747f7d3f0d15219cf3e89b6e5be13aafe91d163a676ed6d54bb09ecd
PERMANENT_SESSION_LIFETIME
:如果 session.permanent 为真, cookie 的有效期为本变量设置的数字, 单位为秒。本变量可能是一个 datetime.timedelta 或者一个 int 。
USE_X_SENDFILE
:当使用 Flask 提供文件服务时,设置 X-Sendfile 头部。有些网络服务器, 如 Apache ,识别这种头部,以利于更有效地提供数据服务。本变量只有使用这 种服务器时才有效。
SERVER_NAME
:通知应用其所绑定的主机和端口。子域路由匹配需要本变量。
APPLICATION_ROOT
:通知应用应用的根路径是什么。这个变量用于生成请求环境之外的 URL。
SESSION_COOKIE_NAME
:会话 cookie 的名称。假如已存在同名 cookie ,本变量可改变。
SESSION_COOKIE_DOMAIN
:认可会话 cookie 的域的匹配规则。如果本变量没有设置,那么 cookie 会被 SERVER_NAME 的所有子域认可。如果本变量设置为 False ,那么 cookie 域不会被设置。
SESSION_COOKIE_PATH
:认可会话 cookie 的路径。如果没有设置本变量,那么路径为 APPLICATION_ROOT ,如果 APPLICATION_ROOT 也没有设置,那么会是 / 。
SESSION_COOKIE_HTTPONLY
:为了安全,浏览器不会允许 JavaScript 操作标记为“ HTTP only ”的 cookie 。
SESSION_COOKIE_SECURE
:如果 cookie 标记为“ secure ”,那么浏览器只会使用基于 HTTPS 的请求发 送 cookie 。应用必须使用 HTTPS 服务来启用本变量。
SESSION_COOKIE_SAMESITE
:限制来自外部站点的请求如何发送 cookie 。可以被设置为 ‘Lax’ (推荐) 或者 ‘Strict’ 。
SESSION_REFRESH_EACH_REQUEST
:当 session.permanent 为真时,控制是否每个响应都发送 cookie 。每次 都发送 cookie (缺省情况)可以有效地防止会话过期,但是会使用更多的带宽。 会持续会话不受影响。
MAX_CONTENT_LENGTH
:在进来的请求数据中读取的最大字节数。如果本变量没有配置,并且请求没有指 定 CONTENT_LENGTH ,那么为了安全原因,不会读任何数据。
SEND_FILE_MAX_AGE_DEFAULT
:当提供文件服务时,设置缓存控制最长存活期,以秒为单位。可以是一个 datetime.timedelta 或者一个 int 。在一个应用或者蓝图上 使用 get_send_file_max_age() 可以基于单个文件重载 本变量。
TRAP_BAD_REQUEST_ERRORS
:尝试操作一个请求字典中不存在的键,如 args 和 form ,会返回一个 400 Bad Request error 页面。开启本变量,可以把这种错误作为一个未处理的 异常处理,这样就可以使用交互调试器了。本变量是一个特殊版本的 TRAP_HTTP_EXCEPTIONS 。如果没有设置,本变量会在调试模式下开启。
TRAP_HTTP_EXCEPTIONS
:如果没有处理 HTTPException 类型异常的处理器,重新引发该异常用于被 交互调试器处理,而不是作为一个简单的错误响应来返回。
EXPLAIN_TEMPLATE_LOADING
:记录模板文件如何载入的调试信息。使用本变量有助于查找为什么模板没有载入 或者载入了错误的模板的原因。
PREFERRED_URL_SCHEME
:当不在请求情境内时使用些预案生成外部 URL 。
JSON_AS_ASCII
:把对象序列化为 ASCII-encoded JSON 。如果禁用,那么 jsonify 返回 的 JSON 会包含 Unicode 字符。这样的话,在把 JSON 渲染到 JavaScript 时会有安全隐患。因此,通常应当开启这个变量。
JSON_SORT_KEYS
:按字母排序 JSON 对象的键。这对于缓存是有用的,因为不管 Python 的哈希种 子是什么都能够保证数据以相同的方式序列化。为了以缓存为代价的性能提高可 以禁用它,虽然不推荐这样做。
JSONIFY_PRETTYPRINT_REGULAR
:jsonify 响应会输出新行、空格和缩进以便于阅读。在调试模式下总是启用 的。
JSONIFY_MIMETYPE
:jsonify 响应的媒体类型。
TEMPLATES_AUTO_RELOAD
:当模板改变时重载它们。如果没有配置,在调试模式下会启用。
MAX_COOKIE_SIZE
:当 cookie 头部大于本变量配置的字节数时发出警告。缺省值为 4093 。 更大的 cookie 会被浏览器悄悄地忽略。本变量设置为 0 时关闭警告。
四 配置文件的写法
1. 直接通过app对象添加配置
app = Flask(__name__)
print(app.config)
app.debug = True
app.secret_key = secrets.token_hex()
print(app.config)
2. 通过app.config配置
app = Flask(__name__)
print(app.config)
app.config['DEBUG'] = True
app.config['SECRET_KEY'] = secrets.token_hex()
print(app.config)
3. 通过配置文件
settings.py
import secrets
DEBUG = True
SECRET_KEY = secrets.token_hex()
app.py
app = Flask(__name__)
app.config.from_pyfile('settings.py')
print(app.config)
4. 通过类配置
setting_class.py
# 开发配置
class DevelopmentConfig:
DEBUG = True
SERVER_NAME = 'localhost'
# 上线配置
class ProductionConfig:
DEBUG = False
SERVER_NAME = '192.168.1.11'
app.py
app.config.from_object('setting_class.ProductionConfig')
print(app.config)
5. 其他
app.config.from_envvar("环境变量名称")
app.config.from_json("json文件名称")
五 路由
现代 web 应用都使用有意义的 URL ,这样有助于用户记忆,网页会更得到用户的青睐, 提高回头率。
使用 route() 装饰器来把函数绑定到 URL:
@app.route('/')
def index():
return 'index page!'
@app.route('/login')
def login():
return 'login page!'
但是能做的不仅仅是这些!您可以动态变化 URL 的某些部分, 还可以为一个函数指定多个规则。
通过把 URL 的一部分标记为<variable_name>
就可以在 URL 中添加变量。标记的 部分会作为关键字参数传递给函数。通过使用 <converter:variable_name>
,可以 选择性的加上一个转换器,为变量指定规则。请看下面的例子:
from flask import Flask
app = Flask(__name__)
@app.route('/user/<username>')
def show_user_profile(username):
# 显示该用户
return f'User {username}'
@app.route('/post/<int:post_id>')
def show_post(post_id):
# 显示id是一个数字
return f'Post {post_id}'
@app.route('/path/<path:subpath>')
def show_subpath(subpath):
# 显示path后的子路径
return f'Subpath {subpath}'
if __name__ == '__main__':
app.run()
route参数
- methods:列表,规定请求的方式,如果列表中没有,该请求方式不被支持。
- endpoint:路由别名,如果不写,会以被装饰的函数名作为别名。
转换器类型:
类型 | 描述 |
---|---|
string | 接受任何不包含斜杠的字符串 |
int | 接受任意正整数 |
float | 接受正浮点数 |
path | 类似于string,但可以包含斜杠 |
uuid | 接受UUID字符串 |
路由源码
@app.route('/')
def hello_word():
return 'Hello World!'
首先执行app对象(Flask的对象)的route()
方法。
# rule是路由
def route(self, rule, **options):
# 定义decorator函数
def decorator(f):
endpoint = options.pop("endpoint", None)
self.add_url_rule(rule, endpoint, f, **options)
return f
# 返回decorator函数
return decorator
route()
函数执行完后,视图函数就相当于下面:
@decorator
def hello_word():
return 'Hello World!'
接着执行decorator(hello_word)
# f是视图函数(hello_word)
def decorator(f):
# 取出别名,不传就为None
endpoint = options.pop("endpoint", None)
# self是app对象
# 接着调用app对象的add_url_rule方法
self.add_url_rule(rule, endpoint, f, **options)
# 返回视图函数
return f
接着调用app对象的add_url_rule方法。
def add_url_rule(self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options):
if endpoint is None:
# 如果endpoint没传,就执行_endpoint_from_view_func方法
# _endpoint_from_view_func返回视图函数的视图名
# 所以不传endpoint参数,会以视图函数名作为endpoint
endpoint = _endpoint_from_view_func(view_func) # type: ignore
options["endpoint"] = endpoint
methods = options.pop("methods", None)
# 如果没有传methods参数
if methods is None:
# 就从视图函数中反射methods属性,没有的话就为一个元组("GET",)
methods = getattr(view_func, "methods", None) or ("GET",)
# methods不能是字符串
if isinstance(methods, str):
raise TypeError(
"Allowed methods must be a list of strings, for"
' example: @app.route(..., methods=["POST"])'
)
# 这是一个集合生成式,保证集合里的元素不重复
methods = {item.upper() for item in methods}
# 添加必要的方法 也是一个集合
required_methods = set(getattr(view_func, "required_methods", ()))
# starting with Flask 0.8 the view_func object can disable and
# force-enable the automatic options handling.
if provide_automatic_options is None:
provide_automatic_options = getattr(
view_func, "provide_automatic_options", None
)
if provide_automatic_options is None:
if "OPTIONS" not in methods:
provide_automatic_options = True
required_methods.add("OPTIONS")
else:
provide_automatic_options = False
# Add the required methods now.
# | 表示集合求并集
methods |= required_methods
# self.url_rule_class是Flask类实例化时设置的
# url_rule_class = Rule
# rule就是Rule实例化的对象
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options # type: ignore
# self.url_map也是在Falsk实例化时设置的
# self.url_map = self.url_map_class()
# url_map_class = Map
self.url_map.add(rule)
if view_func is not None:
# self.view_functions: t.Dict[str, t.Callable] = {} 是一个字典
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError(
"View function mapping is overwriting an existing"
f" endpoint function: {endpoint}"
)
# self.view_functions = {'别名':视图函数}
self.view_functions[endpoint] = view_func
_endpoint_from_view_func(view_func)
方法
def _endpoint_from_view_func(view_func: t.Callable) -> str:
"""Internal helper that returns the default endpoint for a given
function. This always is the function name.
"""
# 断言视图函数不为空
assert view_func is not None, "expected view func if endpoint is not provided."
# 返回视图函数的函数名
return view_func.__name__
总结:
路由注册其实就是调用了app对象的add_url_rule()
方法,那么也可以不用装饰器的方式来注册路由。
app.add_url_rule('/user/<username>', view_func=show_user_profile)
app.route和app.add_url_rule参数:
rule:URL规则
view_func:视图函数名称
defaults = None:默认值, 当URL中无参数,函数需要参数时,使用defaults = {'k': 'v'}
endpoint = None:名称
methods = None:允许的请求方式,如:["GET", "POST"]
strict_slashes:对URL最后的 / 符号是否严格要求
@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>')
使用装饰器完成登录认证
from flask import Flask, render_template, request, redirect, session, url_for
app = Flask(__name__)
app.debug = True
app.secret_key = 'sdfsdfsdfsdf'
USERS = {
1: {'name': '张三', 'age': 18, 'gender': '男', 'text': "道路千万条"},
2: {'name': '李四', 'age': 28, 'gender': '男', 'text': "安全第一条"},
3: {'name': '王五', 'age': 18, 'gender': '女', 'text': "行车不规范"},
}
from functools import wraps
def auth(func):
# 这里如果不使用wraps装饰器,func视图函数就变成了inner
# endpoint就会重复,程序就会报错
# 这里不加就要在route函数中指定endpoint参数
@wraps(func)
def inner(*args, **kwargs):
user = session.get('user_info')
if user:
res = func(*args, **kwargs)
return res
return redirect('/login')
return inner
@app.route('/detail/<int:nid>', methods=['GET'])
@auth
def detail(nid):
info = USERS.get(nid)
return render_template('detail.html', info=info)
@app.route('/index', methods=['GET'])
@auth
def index():
return render_template('index.html', user_dict=USERS)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == "GET":
return render_template('login.html')
else:
# request.query_string
user = request.form.get('user')
pwd = request.form.get('pwd')
if user == 'xuxiaoxu' and pwd == '123':
session['user_info'] = user
return redirect('/index')
return render_template('login.html', error='用户名或密码错误')
if __name__ == '__main__':
app.run()
六 cbv写法
6.1 快速使用
from flask import Flask
from flask.views import MethodView
app = Flask(__name__)
class Test(MethodView):
def get(self):
return 'get'
def post(self):
return 'post'
app.add_url_rule(rule='/test', view_func=Test.as_view(name='test'))
if __name__ == '__main__':
app.run()
6.2 cbv加装饰器
View/as_view
# cls是视图类
# 如果在视图类中配置了decorators属性
if cls.decorators:
# 将as_view传入的name参数赋值给view函数的名字
view.__name__ = name
view.__module__ = cls.__module__
# 循环拿出每一个装饰器函数
for decorator in cls.decorators:
# 相当于
# @decorator
# def view():
# pass
view = decorator(view)
6.3 as_view的执行流程
View/as_view
@classmethod
# 是一个类方法
# cls是视图类,name必须传,不传会报错。
def as_view(cls, name, *class_args, **class_kwargs):
# 定义view函数
def view(*args, **kwargs):
self = view.view_class(*class_args, **class_kwargs) # type: ignore
return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs)
# 如果有装饰器,在这里执行装饰器
if cls.decorators:
view.__name__ = name
view.__module__ = cls.__module__
for decorator in cls.decorators:
view = decorator(view)
# 修改和增加view函数的属性
view.__name__ = name
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.methods = cls.methods # type: ignore
view.provide_automatic_options = cls.provide_automatic_options # type: ignore
# 返回view函数
return view
请求匹配成功,会执行as_view中的view
def view(*args, **kwargs):
# view.view_class是在执行as_view赋值给view函数的,是视图类
# 所以self就是视图类的对象
self = view.view_class(*class_args, **class_kwargs) # type: ignore
# 先执行self.dispatch_request
return current_app.ensure_sync(self.dispatch_request)(*args, **kwargs)
接着执行self.dispatch_request
self是视图类的对象。
def dispatch_request(self, *args, **kwargs):
# 从视图类对象中获取请求方式的视图函数内存地址
meth = getattr(self, request.method.lower(), None)
# If the request method is HEAD and we don't have a handler for it
# retry with GET.
if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)
assert meth is not None, f"Unimplemented method {request.method!r}"
# 执行视图函数
return current_app.ensure_sync(meth)(*args, **kwargs)
6.4 as_view的name参数
执行了as_view()函数,as_view的内层函数view的函数名就变成了name指定的值。
接着执行app.add_url_rule函数,如果没有指定endpoint参数,endpoint是view函数的函数名,就是as_view传的name参数。如果指定了endpoint参数,endpoint就是指定的endpoint参数值,和view的函数名就没有关系了。
6.5 继承View写cbv
from flask import Flask
from flask.views import View
app = Flask(__name__)
class Test(View):
def get(self):
return 'get'
app.add_url_rule('/test', view_func=Test.as_view(name='test'))
if __name__ == '__main__':
app.run()
def dispatch_request(self) -> ResponseReturnValue:
"""Subclasses have to override this method to implement the
actual view function code. This method is called with all
the arguments from the URL rule.
"""
raise NotImplementedError()
在执行self.dispatch_request
是或抛出NotImplementedError
异常,继承View写cbv需要自己定制dispatch_request
方法。
七 模板语法
7.1 渲染变量
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<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>
</html>
7.2 变量的循环
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<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>
</html>
7.3 逻辑判断
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>用户列表</h1>
<table>
{% if name %}
<h1>Hello {{ name }}!</h1>
{% else %}
<h1>Hello World!</h1>
{% endif %}
</table>
</body>
</html>
比django中多可以加括号,执行函数,传参数
# Markup等价django的mark_safe
from flask import Flask,render_template,Markup,jsonify,make_response
app = Flask(__name__)
def func1(arg):
return Markup("<input type='text' value='%s' />" %(arg,))
@app.route('/')
def index():
return render_template('index.html',ff = func1)
if __name__ == '__main__':
app.run()