flask
1|0Flask简介
Flask
是主流PythonWeb
三大框架之一,其特点是短小精悍以及功能强大从而获得众多Pythoner
的追捧,相比于Django
它更加简单更易上手,Flask
拥有非常强大的三方库,提供各式各样的模块对其本身进行扩充:
下面是Flask
与Django
本身的一些区别:
Flask | Django | |
---|---|---|
网关接口(WSGI) | werkzeug | wsgiref |
模板语言(Template) | Jinja2 | DjangoTemplate |
ORM | SQLAlchemy | DjangoORM |
下载Flask
:
2|0werkzeug模块
Flask
本质就是对werkzeug
模块进行一些更高层次的封装,就如同Django
是对wsgiref
模块做了一些更高层次封装一样。所以先来看一下werkzeug
模块如何使用:
3|0简单入门
3|1基本使用
使用Flask
的小案例:
3|2构造参数
对于创建Flask
实例对象,传入的构造参数有以下选项:
形参 | 描述 | 默认值 |
---|---|---|
import_name | 为Flask对象取名,一般为__name__即可 | 无 |
static_url_path | 模板中访问的静态文件存放目录,默认情况下与static_folder同名 | None |
static_folder | 静态文件存放的目录名称,默认当前项目中的static目录 | static |
static_host | 远程静态文件所用的Host地址 | None |
host_matching | 如果不是特别需要的话,慎用,否则所有的route都需要host=""的参数 | False |
subdomain_matching | SERVER_NAME子域名,暂时未GET到其作用 | False |
template_folder | template模板目录, 默认当前项目中的templates目录 | templates |
instance_path | 指向另一个Flask实例的路径 | None |
instance_relative_config | 是否加载另一个实例的配置 | False |
root_path | 主模块所在的目录的绝对路径,默认项目目录 | None |
4|0Flask配置项
如同Django
中的settings.py
一样,在Flask
中也拥有它自己的一些配置项。通过以下方式可对配置项进行修改。
4|1debug模式
一般来说对于Flask
的开发模式都是用app.debug=True
来完成的:
当然你也可以依照下面的方式进行修改。
4|2config修改
对Flask
实例直接进行config
的字典操作修改配置项:
4|3from_pyfile
以py
文件形式进行配置:
4|4from_object
以class
与类属性的方式书写配置项:
4|5其他配置
通过环境变量配置:
通过JSON
格式文件配置:
通过字典格式配置:
4|6配置项大全
以下是Flask
的配置项大全:
5|0路由
5|1路由参数
所有路由中的参数如下:
详细描述:
参数 | 描述 |
---|---|
methods | 访问方式,默认只支持GET |
endpoint | 别名、默认为函数名,不可重复。默认为函数名 |
defaults | 当视图函数拥有一个形参时,可将它作为默认参数传递进去 |
strict_slashes | 是否严格要求路径访问,如定义的时候是/index,访问是/index/,默认是严格访问 |
redirect_to | 301永久重定向,如函数help的redirect_to是/doc,则访问help将跳转到doc函数 |
subdomain | 通过指定的域名进行访问,在浏览器中输入域名即可,本地需配置hosts文件 |
5|2转换器
Flask
中拥有Django3
中的转换器来捕捉用户请求的地址栏参数:
转换器 | 含义 |
---|---|
default | 接收字符串,默认的转换器 |
string | 接收字符串,和默认的一样 |
any | 可以指定多个路径 |
int | 接收整数 |
float | 接收浮点数和整数 |
uuid | 唯一标识码 |
path | 和字符串一样,但是它可以配置/,字符串不可以 |
如下所示:
5|3正则匹配
由于参数捕捉只支持转换器,所以我们可以自定义一个转换器让其能够支持正则匹配:
5|4反向解析
使用url_for()
可在视图中反向解析出url
:
如果在模板中,也可以使用url_for()
进行反向解析:
5|5app.add_url_rule
可以发现,Flask
的路由与Django
的有非常大的区别,但是通过app.add_url_rule
也可以做到和Django
相似。
但是这样的做法很少,函数签名如下:
实际应用如下:
6|0视图
6|1请求相关
Flask
的request
对象不是通过参数传递,而是通过导入:
下面是一些常用的属性与方法:
属性/方法 | 描述 |
---|---|
request.headers | 查看所有的请求头 |
request.method | 存放请求方式 |
request.form | 存放form表单中的序列化数据,一般来说就是POST请求的数据 |
request.args | 存放url里面的序列化数据,一般来说就是GET请求的数据 |
request.data | 查看传过来所有解析不了的内容 |
request.json | 查看前端传过来的json格式数据,内部会自动反序列化 |
request.values.to_dict() | 存放url和from中的所有数据 |
request.cookies | 前端传过来的cookies |
request.path | 路由地址,如:/index |
request.full_path | 带参数的请求路由地址,如:/index?name=yunya |
request.url | 全部地址,如:http://127.0.0.1:5000/index?name=yunya |
request.host | 主机位,如:127.0.0.1:5000 |
request.host_url | 将主机位转换成url,如:http://127.0.0.1:5000/ |
request.url_root | 域名 |
file = request.files | 前端传过来的文件 |
file.filename | 返回文件名称 |
file.save() | 保存文件 |
操作演示:
6|2返回响应
返回响应一般有五种:
返回响应 | 描述 |
---|---|
return 'string' | 返回字符串 |
return render_template() | 返回模板文件 |
return redirect() | 302,重定向,可填入别名或者路由匹配地址 |
return jsonify() | 返回Json格式数据 |
return Response对象 | 直接返回一个对象,常用于取消XSS攻击预防、设置返回头等 |
return send_file(path) 打开文件并返回文件内容
注意,在Flask中,都会返回csrftoken,它存放在浏览器的cookie中。当Flask模板渲染的页面发送请求时会自动携带csrftoken,这与Django并不相同
此外,如果返回的对象不是字符串、不是元组也不是Response对象,它会将值传递给Flask.force_type类方法,将它转换成为一个响应对象
如下所示:
6|3session
在Flask
中,session
也是通过导入来操纵的,而不是通过request
对象。
需要注意的是在Flask
中`session
的保存时长为31天,并且默认是保存在内存中,并未做任何持久化处理。
如果想做持久化处理,则可以通过其他的一些第三方模块。
操作 | 描述 |
---|---|
session.get("key",None) | 获取session |
session["key"]=value | 设置session |
session.pop("key",None) | 删除session |
如下案例所示:
6|4flash
消息闪现flash
是基于session
来做的,它只会允许值被取出一次,内部通过pop()
实现。
使用方式如下:
如下所示:
6|5FBV
如果不是做前后端分离,那么Flask
应用最多的还是FBV
:
6|6CBV
使用CBV
必须导入views.MethodView
且继承它,初此之外必须使用app.add_url_rule
添加路由与视图的关系映射:
6|7RESTAPI
如果项目是前后端分离的,则需要借助第三方模块flask-restful
,详情查阅官网:
6|8文件上传案例
保存上传文件的案例:
其他的一些补充知识:
7|0模板
7|1jinja2简介
jinja2
是Flask
中默认的模板语言,相比于DjangoTemplate
它更加的符合Python
语法。
如在模板传参中,如果视图中传入是一个dict
,那么在DTL
中只能通过.
的方式进行深度获取,而在jinja2
中则可以通过[]
的方式进行获取。
此外,在DTL
中如果视图传入一个function
则会自动加括号进行调用,而在jinja2
中就不会进行自动调用而是要自己手动加括号进行调用。
总而言之,jinja2
相比于DTL
来说更加的人性化。
7|2模板传参
模板传参可以通过k=v
的方式传递,也可以通过**dict
的方式进行解包传递:
@app.route('/index')
def index():
context = {
"name": "云崖",
"age": 18,
"hobby": ["篮球", "足球"]
}
return render_template("index.html", **context)
# return render_template("index.html", name="云崖", age=18)
渲染,通过{{}}
进行:
7|3内置过滤器
常用的内置过滤器如下:
过滤器 | 描述 |
---|---|
escape | 转义字符 |
safe | 关闭XSS预防,关闭转义 |
striptags | 删除字符串中所有的html标签,如果有多个空格连续,将替换为一个空格 |
first | 返回容器中的第一个元素 |
last | 返回容器中的最后一个元素 |
length | 返回容器总长度 |
abs | 绝对值 |
int | 转换为int类型 |
float | 转换为float类型 |
join | 字符串拼接 |
lower | 转换为小写 |
upper | 转换为大写 |
capitialize | 把值的首字母转换成大写,其他子母转换为小写 |
title | 把值中每个单词的首字母都转换成大写 |
trim | 把值的首尾空格去掉 |
round | 默认对数字进行四舍五入,也可以用参数进行控制 |
replace | 替换 |
format | 格式化字符串 |
truncate | 截取length长度的字符串 |
default | 相当于or,如果渲染变量没有值就用default中的值 |
使用内置过滤器:
7|4分支循环
if
和for
都用{% %}
进行包裹,与DTL
中使用相似。
在for
中拥有以下变量,用来获取当前的遍历状态:
for循环的遍历 | 描述 |
---|---|
loop.index | 当前遍历次数,从1开始计算 |
loop.index0 | 当前遍历次数,从0开始计算 |
loop.first | 第一次遍历 |
loop.last | 最后一次遍历 |
loop.length | 遍历对象的长度 |
loop.revindex | 到循环结束的次数,从1开始 |
loop.revindex0 | 到循环结束的次数,从0开始 |
下面是一则示例:
结果如下:
7|5宏的使用
在模板中的宏类似于Python
中的函数,可对其进行传值:
可以在一个模板中专门定义宏,其他模板中再进行导入:
7|6定义变量
在模板中可通过{% set %}
和{% with %}
定义变量。
{% set %}是全局变量,可在当前模板任意位置使用
{% with %}是局部变量,只能在{% with %}语句块中使用
7|7模板继承
使用{% extends %}
引入一个定义号的模板。
使用{% blocak %}
和{% endblock %}
定义块
使用{{ super() }}
引入原本的模板块内容
定义模板如下:
导入模板并使用:
8|0中间件
在Flask
中的中间件使用非常少。由于Flask
是基于werkzeug
模块来完成的,所以按理说我们只需要在werkzeug
的启动流程中添加代码即可。
下面是中间件的使用方式,如果想了解它的原理在后面的源码分析中会有涉及。
在Flask
请求来临时会执行wsgi_app
这样的一个方法,所以就在这个方法上入手:
9|0装饰器
9|1如何添加装饰器
由于Flask
的每个视图函数头顶上都有一个装饰器,且具有endpoint
不可重复的限制。
所以我们为单独的某一个视图函数添加装饰器时一定要将其添加在下方(执行顺序自下而上),此外还要使用functools.wraps
修改装饰器inner()
让每个装饰器的inner.__name__
都不相同,来突破endpoint
不可重复的限制。
如下所示,为单独的某一个接口书写频率限制的装饰器:
9|2befor_request
每次请求来的时候都会走它,由于Flask
的中间件比较弱鸡,所以这种方式更常用。
类似于Django
中间件中的process_request
,如果有多个顺序是从上往下,可以用它做session
认证。
如果返回的不是None,就拦截请求
9|3after_request
请求走了就会触发,类似于Django
的process_response
,如果有多个,顺序是从下往上执行:
必须传入一个参数,就是视图的return值
9|4before_first_request
目启动起来第一次会走,以后都不会走了,也可以配多个(项目启动初始化的一些操作)
如果返回的不是None,就拦截请求
9|5teardown_request
每次视图函数执行完了都会走它。
可以用来记录出错日志:
9|6errorhandler
绑定错误的状态码,只要码匹配就走它。
常用于重写404
页面等:
9|7template_global
定义全局的标签,如下所示:
9|8template_filter
定义全局过滤器,如下所示:
9|9多request顺序
如果存在多个berfor_request
与多个after_request
那么执行顺序是怎样的?
如果第一个before_request
就返回了非None
进行拦截,执行顺序则和Django
的不一样,Django
会返回同级的process_response
,而Flask
还必须要走所有的after_request
的:
10|0蓝图
蓝图Blueprint
的作用就是为了将功能和主服务分开。
说的直白点就是构建项目目录,划分内置的装饰器作用域等,类似于Django
中app
的概念。
10|1小型项目
下面有一个基本的项目目录,如下所示:
这样的目录结构看起来就比较清晰,那么如何对它进行管理呢?就可以使用蓝图:
10|2url前缀
启动服务后发现两个功能区的login
都是相同的url
,导致后注册的蓝图对象永远无法访问登录页面。
在__init__.py
中注册蓝图对象的代码中添加前缀:
访问时:
10|3蓝图资源
每个蓝图应用的资源都不相同,如下:
如何指定他们的资源呢?其实在创建蓝图对象的时候就可以指定:
如果是静态资源的访问,并不会加上前缀/backend
:
10|4蓝图装饰器
蓝图装饰器分为全局装饰器和局部装饰器两种:
全局装饰器全局有效:
局部装饰器只在当前蓝图对象bck
有效:
10|5大型项目
构建大型项目,就完全可以将它做的和Django
相似,让每个蓝图对象都拥有自己的templates
与static
。
11|0多app应用
一个Flask
程序允许多个实例,如下所示:
12|0解决跨域
解决跨域请求,可以用第三方插件,也可以自定义响应头:
13|0上下文机制
13|1全局变量
在Flask
项目启动时,会自动初始化一些全局变量。其中有几个变量尤为重要,可通过以下命令查看:
就是下面的6个变量,将贯穿整个HTTP
请求流程。
13|2偏函数
上面的6个变量中有两个变量执行了类的实例化,并且有传入了一个偏函数。
偏函数的作用在于不用传递一个参数,设置好后自动传递:
13|3列表实现栈
栈是一种后进先出的数据结构,使用列表可以实现一个栈:
在Flask
源码中多次有构建这个栈的地方(目前来看至少两处)。
13|4Local
Local
对象在全局变量中会实例化两次,作用是实例化出一个字典,用于存放线程中的东西:
我们来看看它的源码:
13|5LocalStack
用于操纵Local
中构建的字典:
13|6LocalProxy
访问Local
时用LocalProxy
,实际上是一个代理对象:
这四句话代表四个意思,使用最多的范围如下:
源码如下:
13|7基本概念
在Flask
中,每一次HTTP
请求的到来都会执行一些操作。
举个例子,Django
里面request
是通过形参的方式传递进视图函数,这个很好实现,那么Flask
中的request
则是通过导入的方式作用于视图函数,这意味着每次request
中的数据都要进行更新。
它是如何做到的呢?这个就是Flask
的精髓,上下文管理。
上面说过,Local
对象会实例化两次:
它的实现原理是这样的,每一次HTTP
请求来临都会创建一个线程,Local
对象就会依照这个线程的pid
来构建出一个字典,这里用掉的对象是_request_ctx_stack
,它内部有一个叫做__storage__
的变量,最终会搞成下面的数据格式:
而除开_request_ctx_stack
外还会有一个叫做_app_ctx_stack
的东西,它会存放当前Flask
实例app
以及一个g
对象:
每一次请求来的时候都会创建这样的两个字典,请求走的时候进行销毁。
在每次导入request/session
时都会从上面的这个__storage__
字典中,拿出当前线程对应的pid
中的request/session
,以达到更行的目的。
类 | 功能 |
---|---|
Local | 构建大字典 |
Localstack | 构建stack这个列表实现的栈 |
LocalProxy | 控制获取stack列表中栈的数据,如导入时引入request,怎么样将stack中的request拿出来 |
13|8Flask流程之__call__
Flask
基本请求流程是建立在werkzeug
之上:
可以看到,werkzeug
在开启服务后,会执行一个叫run_simple
的函数,并且会调用被装饰器包装过后的index
函数。
也就意味着,在run_simple
传参时,第三个参数形参名application
会加括号进行调用。
如果你传入一个类,它将执行__init__
方法,如果你传入一个实例对象,它将执行其类的__call__
方法。
如下所示:
示例二:
OK,现在牢记一点,如果传入的是函数,执行其类的__call__
。
接下来我们看Flask
程序:
当请求来时,会执行run
方法,我们朝里看源码,直接拉到run
方法的下面,在run
方法中调用了run_simple
方法,其中的第三个参数就是当前的Flask
实例对象app
:
所以到了这里,实例调用父类的__call__
(Flask
类本身),继续看app.__call__
,实例本身没有找其类的。
可以看见,在这里它调用的是app.wsgi_app
方法,这也就能解释Flask
中间件为什么重写下面这段代码。
13|9Flask流程之wsgi_app
原生的Flask.wsgi_app
中的代码是整个Flask
框架中的核心,如下所示:
13|10Flask流程之Resquest
看下面这一行代码,它其实是实例化一个对象,用于封装environ
这个原始的HTTP
请求:
点开它,发现会返回一个实例对象:
去找它的__init__
方法:
先看上面的一句,封装request
对象,由于self
是app
,所以找Flask
类中的request_class
,它是一个类属性:
加括号进行调用,并且传递了environ
,所以要进行实例化,找它的__init__
方法,发现是一个多继承类:
找它的父类,RequestBase
,也是一个多继承类:
再继续向上找,找BaseRequest
类:
然后将结果返回给ctx
,这个ctx
就是RequestContext
的实例对象,里面有个Request
实例对象request
,还有个session
,不过是None
:
13|11Flask流程之ctx.push
接着往下看代码,记住现在的线索:
执行ctx.push
,这个方法可以说非常的绕。
现在来看 app_ctx
到底是个神马玩意儿:
继续走:
现在回来,第二个重要点来了:
13|12Flask流程之app_ctx.push
这个push
是AppContext
中的push
:
其实就是往Local
的大字典中进行存放,不过是通过LocalStack
这个类的push
方法:
数据结构:
然后返回app_ctx.push
:
13|13Flask流程之_request_ctx_stack.push
继续向下看ctx.push
中的代码,这里主要是封装请求上下文:
13|14Flask流程之session
保存session
,从Cookie
中获取数据,反序列化后保存到session
中。
13|15导入源码
如果使用from flask import request
它会通过LocalProxy
去拿到Local
中的ctx
对象然后进行解析,拿到request
对象。
在这里可以看见实例化了一个LocalProxy
对象:
当要使用request.method
时,触发LocalProxy
的__getattr__
方法:
在_get_current_object
中执行self.__local()
:
偏函数,第二个参数是request
,也就是说_lookup_req_object
的参数默认就是request
:
拿到request
对象后继续看__getattr__
方法,获取method
:
13|16Flask流程之pop
wsgi_app
返回和清栈还没有看:
最主要就是看清栈:
接着看pop
方法:
LocalProxy.pop
中的代码:
13|17Flask流程之before_request
before_request
实现挺简单的,用一个列表,将所有被装饰函数放进来。再执行视图函数之前把列表中所有被berore_request
装饰的函数先执行一遍:
在wsgi_app
中查看源码:
点进去看,执行视图函数前发生了什么:
关键代码:
而使用after_request
装上后的函数也会被放到一个列表中,其他的具体实现都差不多:
13|18源码流程图
13|19g的作用
一次请求流程中的一些共同数据,可以用g
进行存储:
13|20current_app的作用
可以导入当前的配置:
14|0WTforms
wtforms
类似于Django
中的forms
组件,用于数据验证和生成HTML
官方文档:https://wtforms.readthedocs.io/en/stable/index.html#
14|1基本使用
首先进行安装:
一个简单的注册示例:
前端渲染:
14|2Form实例化
以下是Form
类的实例化参数:
参数 | 描述 |
---|---|
formdata | 需要被验证的form表单数据 |
obj | 如果formdata为空或未提供,则检查此对象的属性是否与表单字段名称匹配,这些属性将用于字段值 |
prefix | 字段前缀匹配,当传入该参数时,所有验证字段必须以这个开头(无太大意义) |
data | 当formdata参数和obj参数都有时候,可以使用该参数传入字典格式的待验证数据或者生成html的默认值,列如:{'usernam':'admin’} |
meta | 用于覆盖当前已经定义的form类的meta配置,参数格式为字典 |
使用data
来构建默认值,常用于文章编辑等,需要填入原本数据库中查询出的文章。
下面用默认用户做演示:
14|3字段介绍
字段的继承,fields
是常用类,它继承了其他的一些类:
一般都字段都会默认生成一种HTML
标签,但是也可以通过widget
进行更改,下面是常用的一些字段:
字段类型 | 描述 |
---|---|
StringField | 文本字段, 相当于type类型为text的input标签 |
TextAreaField | 多行文本字段 |
PasswordField | 密码文本字段 |
HiddenField | 隐藏文本字段 |
DateField | 文本字段, 值为datetime.date格式 |
DateTimeField | 文本字段, 值为datetime.datetime格式 |
IntegerField | 文本字段, 值为整数 |
DecimalField | 文本字段, 值为decimal.Decimal |
FloatField | 文本字段, 值为浮点数 |
BooleanField | 复选框, 值为True 和 False |
RadioField | 一组单选框 |
SelectField | 下拉列表 |
SelectMultipleField | 下拉列表, 可选择多个值 |
FileField | 文件上传字段 |
SubmitField | 表单提交按钮 |
FormFiled | 把表单作为字段嵌入另一个表单 |
FieldList | 子组指定类型的字段 |
每个字段可以为其配置一些额外的属性,如下所示:
字段属性 | 描述 |
---|---|
label | 字段别名 |
validators | 验证规则列表 |
filters | 过滤器列表 |
description | 字段详细描述 |
default | 默认值 |
widget | 自定义插件,替换默认生成的HTML标签 |
render_kw | 为生成的HTML标签配置属性 |
choices | 复选框的类型 |
如下所示:
14|4内置验证
使用validators
为字段进行验证时,可指定如下的内置验证规则:
验证函数 | 说明 |
---|---|
验证电子邮件地址 | |
EqualTo | 比较两个字段的值,常用于要求输入两次密码进行确认的情况 |
IPAddress | 验证IPv4网络地址 |
Length | 验证输入字符串的长度 |
NumberRange | 验证输入的值在数字范围内 |
Optional | 无输入值时跳过其他验证函数 |
DataRequired | 确保字段中有数据 |
Regexp | 使用正则表达式验证输入值 |
URL | 验证URL |
AnyOf | 确保输入值在可选值列表中 |
NoneOf | 确保输入值不在可选列表中 |
EqualTo
是常用的验证方式,验证两次密码是否输入一致:
14|5Meta配置
Meta
主要用于自定义wtforms
的功能,用的比较少,大多都是配置选项,以下是配置参数:
14|6钩子函数
一般都用局部钩子:
14|7自定义验证
自定义验证规则:
使用:
14|8插件大全
以下代码包含所有可能用到的插件:
14|9源码解析
15|0Flask-session
、通过第三方插件Flask-session
能够将session
存放至其他地方,而不是只能存放在内存中:
使用的时候:
原理也很简单,替换掉了默认的session
接口,与Django
的redis
缓存差不多。
16|0Flask-SQLALchemy
Flask-SQLALchemy
是SQLALchemy
与Flask
之间的粘合剂。让Flask
与SQLALchemy
之间的关系更为紧密:
使用Flask-SQLALchemy
也非常简单,首先是创建项目:
代码如下,做主蓝图,使用flaskSQLAlchemy
模块:
settings.py
中的配置项:
然后是书写模型类:
视图:
启动文件:
17|0Flask-Script
该插件的作用通过脚本的形式启动Flask
项目,同时还具有自定义脚本命令的功能。
下载安装:
在启动文件中进行使用:
启动命令:
你可以自定义一些启动脚本,如下所示:
也可以使用关键字传参的方式,进行脚本的启动:
自定义脚本可以配置是否创建数据库,配合Flask-SQLAlchemy
,如下所示:
18|0Flask-Migrate
该插件的作用类似于Django
中对model
的命令行操作,由于原生Flask-SQLALchemy
不支持表结构的修改,所以用该插件的命令行来弥补。
值得一提的是,该插件依赖于Flask-Script
:
在启动文件中进行导入:
命令行:
命令 | 描述 |
---|---|
python 启动文件.py db init | 初始化model |
python 启动文件.py db migrate | 类型于makemigrations,生成模型类 |
python 启动文件.py db upgrade | 类似于migrate,将模型类映射到物理表中 |
在第一次使用时,三条命令都敲一遍。
如果修改了表结构,只用敲第二条,第三条命令即可,弥补Flask-SQLALchemy
不能修改表结构的缺点。
19|0最后记录
一个完整基础的Flask
项目基础架构:
__EOF__
本文链接:https://www.cnblogs.com/Yunya-Cnblogs/p/14152745.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
@app.route('/index')def index(): context = { "name": "云崖", "age": 18, "hobby": ["篮球", "足球"] } return render_template("index.html", **context) # return render_template("index.html", name="云崖", age=18)