Flask
1.Django与Flask的对比
- Django 主要特点是大而全,集成了很多组件,例如: Models Admin Form 等等, 不管你用得到用不到,反正它全都有,属于全能型框架
2.Tornado 主要特点是原生异步非阻塞,在IO密集型应用和多任务处理上占据绝对性的优势,属于专注型框架
3.Flask 主要特点小而轻,原生组件几乎为0, 三方提供的组件请参考Django 非常全面,属于短小精悍型框架。
Django 通常用于大型Web应用由于内置组件足够强大所以使用Django开发可以一气呵成
Tornado 通常用于API后端应用,游戏服务后台,其内部实现的异步非阻塞真是稳得一批
Flask 通常应用于小型应用和快速构建应用,其强大的三方库,足以支撑一个大型的Web应用
Django 优点是大而全,缺点也就暴露出来了,这么多的资源一次性全部加载,肯定会造成一部分的资源浪费
Tornado 优点是异步,缺点是干净,连个Session都不支持
Flask 优点是精悍简单,缺点理论上的有些不稳定。
2.开启flask
from flask import Flask,render_template,redirect,send_file
app = Flask(__name__)
@app.route("/index") # 路由
def index(): # 视图函数
return "Hello OldBoy EDU" # Django HttpResponse
app.run()
=======================================================================
@app.route("/index") # 路由
def index(): # 视图函数
return "hello" # Django HttpResponse
@app.route("/login") # 路由
def login(): # 视图函数
# 要建一个默认的templates文件夹,存放html文件
return render_template("login.html") # Django render('login.html')
@app.route("/index") # 路由
def index(): # 视图函数
return redirect("/login") # Django redirect
@app.route("/send") # 路由
def send(): # 视图函数
return send_file("static/xxx.jpg") # 返回一个文件,其实是返回一个二进制的流
@app.route("/user/<int:id>") # 路由
def user(id): # 视图函数
print(id)
return 'xxxxxx'
@app.route("/index") # 路由
def index(): # 视图函数
return jsonify({"status":100})
类似与Django里面的JsonResponse,返回json序列化后的数据,在Flask项目中需要对数据进行序列化传输时,用这个比json.dumps要好很多。用jsonify序列化数据时,数据格式必选严格满足json格式,单引号的字符串也不行,它不像json在序列化时会将单引号变成单引号。而它不行!
3.request
-
request.method:与Django里面的方法一致,获取本次请求的请求方式
-
request.form:获取前端用form表单提交过来的数据,得到的是ImmutableMultiDict,跟字典类似。
-
request.args:获取url中传递过来的所有参数值
http://127.0.0.1:5000?id=1&name=tom args获取的就是?后面的参数
-
request.values:只要是参数,无论是post过来的还是get过来都全部接受,同样获取到的数据直接当成一个大字典进行操作即可。但是如果url和form中的Key重名的话,form中的同名的key中value会被url中的value覆盖。个人建议不要用。
# http://127.0.0.1:5000/req?id=1&user=20 print(request.values.to_dict()) # {'user': 20 'pwd': '123', 'id': '1'}
-
request.headers:用来获取本次请求的请求头
print(type(request.headers)) """ Host: 127.0.0.1:5000 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://127.0.0.1:5000/home Content-Type: application/x-www-form-urlencoded Content-Length: 26 Cookie: csrftoken=vDIozqveCEfArdYXlM6goHVlSQEn7h4bDygNphL2Feas60DiM2di0jlqKfxo7xhA Connection: keep-alive Upgrade-Insecure-Requests: 1 Cache-Control: max-age=0 """
-
request.data:没有加请求头的数据都可以在这个里面找到,并且是bytes类型
-
request.files:如果遇到文件上传的话,request.files 里面存的是你上传的文件,但是 Flask 在这个文件的操作中加了一定的封装,让操作变得极为简单
my_file = request.files["file"] my_file.save("test.txt") # 保存文件,里面可以写完整路径+文件名
-
获取各种路径
# 获取当前的url路径 print(request.path)# /req # 当前url路径的上一级路径 print(request.script_root) # # 当前url的全部路径 print(request.url) # http://127.0.0.1:5000/req # 当前url的路径的上一级全部路径 print(request.url_root ) # http://127.0.0.1:5000/
-
request.json:如果在请求中写入了 “application/json” 使用 request.json 则返回json解析数据, 否则返回 None。
4.Flask中的模板语言Jinja2以及render_template的深度用法
Flask对Jinja2稍微做了一点点的加工修饰
针对传入是字典的数据对象操作
<tr>
<td>{{ student.name }}</td>
<td>{{ student["age"] }}</td>
<td>{{ student.get("gender") }}</td>
</tr>
针对传入是列表套字典对象的操作
STUDENT_LIST = [
{'name': 'Old', 'age': 38, 'gender': '中'},
{'name': 'Boy', 'age': 73, 'gender': '男'},
{'name': 'EDU', 'age': 84, 'gender': '女'}
]
<tr>
<td>{{ foo }}</td>
<td>{{ foo.name }}</td>
<td>{{ foo.get("age") }}</td>
<td>{{ foo["gender"] }}</td>
</tr>
针对传入的是{1:{}}这种大字典类型
STUDENT_DICT = {
1: {'name': 'Old', 'age': 38, 'gender': '中'},
2: {'name': 'Boy', 'age': 73, 'gender': '男'},
3: {'name': 'EDU', 'age': 84, 'gender': '女'},
}
<tr>
<td>{{ foo }}</td>
<td>{{ student.get(foo).name }}</td>
<td>{{ student[foo].get("age") }}</td>
<td>{{ student[foo]["gender"] }}</td>
</tr>
后端传入多个数据类型,采用赋值语句传值方式,一个一个传!
@app.route("/allstudent")
def all_student():
return render_template("all_student.html", student=STUDENT ,
student_list = STUDENT_LIST,
student_dict= STUDENT_DICT)
{{ }} 非逻辑代码
{% %}} 逻辑代码
endpoint = "反向url地址"
url_for("反向url地址 ")
动态路由参数/index/<int:arg>
url_for("endpoint",arg=123)
5.Jinja2 的高阶用法
5.1 safe(前端入手)
将你写的html代码直接渲染不转译,跟Django的safe方法一样。
5.2 Markup(后端入手)
from flask import Flask
from flask import render_template
from flask import Markup # 导入 flask 中的 Markup 模块
app = Flask(__name__)
@app.route("/")
def index():
tag = "<input type='text' name='user' value='DragonFire'>"
markup_tag = Markup(tag) # Markup帮助咱们在HTML的标签上做了一层封装,让Jinja2模板语言知道这是一个安全的HTML标签
print(markup_tag,
type(markup_tag)) # <input type='text' name='user' value='DragonFire'> <class 'markupsafe.Markup'>
return render_template("index.html", tag=markup_tag)
app.run("0.0.0.0", 5000, debug=True)
类似于Django里面的mark_safe()
5.3 模板中执行函数
先在后端中定义函数,类似于Django中的sample_tag/inclusion_tag等
from flask import Flask
from flask import render_template
from flask import Markup # 导入 flask 中的 Markup 模块
app = Flask(__name__)
#定义一个函数,把它传递给前端
def a_b_sum(a,b):
return a+b
@app.route("/")
def index():
return render_template("index.html", tag=a_b_sum)
app.run("0.0.0.0", 5000, debug=True)
通过tag参数将我们写的函数传递到前端页面
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ tag }}
<br>
{{ tag(99,1) }}
</body>
</html>
看到结果就是,函数加()执行得到结果
还可以定义全局函数template_global(),无需后端传递给前端,Jinja2直接就可以执行的函数
对于这种处理函数,还可以有一种**偏函数template_filter()**的应用,管道符前面的值作为函数的第一个参数传入函数中
from flask import Flask
from flask import render_template
from flask import Markup # 导入 flask 中的 Markup 模块
app = Flask(__name__)
@app.template_global() # 定义全局模板函数
def a_b_sum(a, b):
return a + b
@app.template_filter() # 定义全局模板函数
def a_b_c_sum(a, b, c):
return a + b + c
@app.route("/")
def index():
return render_template("index.html", tag="")
app.run("0.0.0.0", 5000, debug=True)
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ a_b_sum(99,1) }}
<br>
{{ 1 | a_b_c_sum(197,2) }}
</body>
</html>
管道符前面也可以是一个函数,会将函数的返回值作为后面函数的第一个参数
5.4 Jinja2模板复用 block
跟Django里面的block模板语言一模一样,还有模板继承语言extends,模板应用语言include。跟Django里面的是一模一样的用法。
5.5 Jinja2模板语言中的宏定义
简单的说就是在前端页面中直接定义函数,并直接在前端调用执行定义的函数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Welcome </h1>
{% macro type_text(name,type) %}
<input type="{{ type }}" name="{{ name }}" value="{{ name }}">
{% endmacro %}
<h2>在下方是使用宏来生成input标签</h2>
{{ type_text("one","text") }}
{{ type_text("two","text") }}
</body>
</html>
宏定义一般情况下很少应用到,但是要知道有这么个概念,起码能吹个牛逼见识广博!
6.Flask中的路由系统
6.1. @app.route() 装饰器中的参数
methods : 当前 url 地址,允许访问的请求方式(当前装饰的视图函数支持的请求方式)
@app.route("/info", methods=["GET", "POST"])
def student_info():
stu_id = int(request.args["id"])
return f"Hello Old boy {stu_id}" # Python3.6的新特性 f"{变量名}"
6.2endpoint : 反向url地址,默认为视图函数名 (url_for)
from flask import url_for
@app.route("/info", methods=["GET", "POST"], endpoint="r_info")
def student_info():
print(url_for("r_info")) # /info
stu_id = int(request.args["id"])
return f"Hello {stu_id}"
对比Django里面的url反向解析,这里相当于路由解析url
6.3strict_slashes
url地址结尾符"/“的控制 False : 无论结尾 “/” 是否存在均可以访问 , True : 结尾必须不能是 “/”,这一点不像Django会帮我们自动补全”/"
# 访问地址 : /info
@app.route("/info", strict_slashes=True)
def student_info():
return "Hello info"
# 访问地址 : /infos or /infos/
@app.route("/infos", strict_slashes=False)
def student_infos():
return "Hello infos"
6.4 redirect_to: url地址重定向
需要注意的是这里的重定向并不是我们之前接触到那种先访问出一个页面,然后再跳转,而是直接在后端内部直接给你做重定向,可以称为内重定向。举个例子,相当于我有一个网站已经投入使用很久了,用户都将网站的地址收藏了,这个时候我需要扩展网站功能和修改部分功能,这些新功能对应的地址用户有又不知道,这个时候就可以在后端直接用上内重定向的方式,让用户访问的url在我们的服务器内部直接给他重定向到我们新的url地址中
# 访问地址 : /info 浏览器跳转至 /infos
@app.route("/info", strict_slashes=True, redirect_to="/infos")
def student_info():
return "Hello Old boy info"
@app.route("/infos", strict_slashes=False)
def student_infos():
return "Hello Old boy infos"
6.5 subdomain
子域名前缀 subdomian=“Tom” 这样写可以得到 Tom.sayhello.com 前提是app.config[“SERVER_NAME”] = “sayhello.com”
app.config["SERVER_NAME"] = "sayhello.com"
@app.route("/info",subdomain="Tom")
def student_info():
return "Hello info"
# 访问地址为: Tom.sayhello.com/info
对于初始化配置,我们需要知道的几种不同的配置方式:
app.config.from_object(“setting.FlaskSetting”)
app.DEBUG = True 开启Debug模式,改完代码不用手动重启
app.SECRET_KEY = “xxxxx” 开启session必备参数
app.config[‘DEBUG’]=True
app.run(debug=True)
6.6 动态参数路由:
from flask import url_for
# 访问地址 : http://127.0.0.1:5000/info/1
@app.route("/info/<int:nid>", methods=["GET", "POST"], endpoint="r_info")
def student_info(nid):
print(url_for("r_info",nid=2)) # /info/2
return f"Hello {nid}" # Python3.6的新特性 f"{变量名}"
int:nid 就是在url后定义一个参数接收,并且要是int类型,一般不加限制,默认是string类型,但是数字还是int类型。
但是这种动态参数路由,在url_for的时候,一定要将动态参数名+参数值添加进去,否则会抛出参数错误的异常。
7.Flask实例化参数及配置
from flask import Flask
app = Flask(__name__) # type:Flask
app.config["DEBUG"] = True
这句 app.config[“DEBUG”] = True 可以实现代码只要发生改动,自动重启Flask程序(app.run),在控制台打印的信息非常全面,以上两个功能就是传说中的 DEBUG 模式(调试模式)。
Flask的配置就是在 app.config 中添加一个键值对,但是你存进去的键必须是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 异常。
# 因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。
# 如果这个值被设置为 True ,你只会得到常规的回溯。
'EXPLAIN_TEMPLATE_LOADING': False,
'PREFERRED_URL_SCHEME': 'http', # 生成URL的时候如果没有可用的 URL 模式话将使用这个值
'JSON_AS_ASCII': True,
# 默认情况下 Flask 使用 ascii 编码来序列化对象。如果这个值被设置为 False ,
# Flask不会将其编码为 ASCII,并且按原样输出,返回它的 unicode 字符串。
# 比如 jsonfiy 会自动地采用 utf-8 来编码它然后才进行传输。
'JSON_SORT_KEYS': True,
#默认情况下 Flask 按照 JSON 对象的键的顺序来序来序列化它。
# 这样做是为了确保键的顺序不会受到字典的哈希种子的影响,从而返回的值每次都是一致的,不会造成无用的额外 HTTP 缓存。
# 你可以通过修改这个配置的值来覆盖默认的操作。但这是不被推荐的做法因为这个默认的行为可能会给你在性能的代价上带来改善。
'JSONIFY_PRETTYPRINT_REGULAR': True,
'JSONIFY_MIMETYPE': 'application/json',
'TEMPLATES_AUTO_RELOAD': None,
}
修改配置的方式大约是两种
- 直接对app.config进行修改
app.config["DEBUG"] = True
- 使用类的方式导入
首先要有一个settings.py的文件
class FlaskSetting:
DEBUG = True
SECRET_KEY = "dqwrqrasd"
然后我们在Flask的启动文件中就可以这么写
from flask import Flask
app = Flask(__name__) # type:Flask
app.config.from_object("settings.FlaskSetting")
它会自动对字符串路径进行处理并导入,相当于importlib
Flask实例化时候参数配置
static_folder = 'static', # 静态文件目录的路径 默认当前项目中的static目录
static_host = None, # 远程静态文件所用的Host地址,默认为空
static_url_path = None, # 静态文件目录的url路径 默认不写是与static_folder同名,远程静态文件时复用
# host_matching是否开启host主机位匹配,是要与static_host一起使用,如果配置了static_host, 则必须赋值为True
# 这里要说明一下,@app.route("/",host="localhost:5000") 就必须要这样写
# host="localhost:5000" 如果主机头不是 localhost:5000 则无法通过当前的路由
host_matching = False, # 如果不是特别需要的话,慎用,否则所有的route 都需要host=""的参数
subdomain_matching = False, # 理论上来说是用来限制SERVER_NAME子域名的,但是目前还没有感觉出来区别在哪里
template_folder = 'templates' # template模板目录, 默认当前项目中的 templates 目录
instance_path = None, # 指向另一个Flask实例的路径
instance_relative_config = False # 是否加载另一个实例的配置
root_path = None # 主模块所在的目录的绝对路径,默认项目目录
8 蓝图(Blueprint)
Flask的蓝图,你可以看成是一个个小的组件,分别能够实现部分功能,在我的主运行文件中只需要将这些蓝图全部注册进来,就可以将这些分散的功能组合成一个整体,这非常符合实际开发需求,十个人可以同时开发不同的功能,最后直接统一注册整合即可拼成“真正的伟大蓝图”。蓝图内的用法跟Flask差不多。
9.Flask特殊装饰器
@app.template_folder() 类偏函数方法
@app.template_global() 全局函数
@errorhandler(404) 相当于Django的error页面
@app.befor_request 类似Django中间件的process_request
@app.after_request 类似Django中间件的process_response
10.session
Flask中的Session非常的奇怪,他会将你的SessionID存放在客户端的Cookie中,使用起来也非常的奇怪。
- Flask 中 session 是需要 secret_key 的(前提条件必须满足!)
from flask import session
app = Flask(__name__)
app.secret_key = "session_key"
secret_key 实际上是用来加密字符串的,如果在实例化的app中没有 secret_key 那么开启session一定会抛异常的
- session 要这样用
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
if request.form["username"] == USER["username"] and request.form["password"] == USER["password"]:
session["user"] = USER["username"]
return redirect("/index")
return render_template("login.html", msg="用户名密码错误")
return render_template("login.html", msg=None) # 如果前端Jinja2模板中使用了msg,这里就算是传递None也要出现msg,防止报错!
session[“user”] = USER[“username”] 这样用就代表这个请求带上来的session中保存了一个user=name,后续就可以直接在session.get(“user”)中拿到设置的值
- 怎么用 session 进行验证呢?
@app.route("/books")
def books():
if session.get("user"):
return render_template("books.html", student=BOOK_DICT)
return redirect("/login")
11.Flask中的CBV
from flask import Flask, views, request, render_template
app = Flask(__name__)
class Login(views.MethodView):
def get(self):
return render_template("login.html")
def post(self):
print(111)
return "hello"
app.add_url_rule("/login", endpoint=None, view_func=Login.as_view(name="login"))
if __name__ == '__main__':
app.run("127.0.0.1", 9527, debug=True)