1.第一个flask程序
from flask import Flask
"""
Flask这个类是项目的核心,以后很多操作都是基于这个类的对象
注册URL等等,都是基于这个类
"""
app = Flask(__name__)
"""
用Flask类创建一个对象,传递参数__name__
这里__name__参数的作用:
1.可以规定模板和静态文件的查找路径
2.以后flask的一些插件如果报错,那么可以通过__name__找到具体的错误位置
"""
# @app.route是一个装饰器,里面传入URL,那么会将URL映射到所装饰的函数上面
# 当访问"/"的时候,会执行所装饰的函数,将函数的返回值渲染到页面
# 个人觉得:flask设计的非常优雅,路由、视图函数之间的关系非常直观明了。
# 额外插一嘴,个人觉得flask团队写框架真的是贼喜欢用装饰器,比如click,一个处理命令行参数的模块,也是大量使用装饰器
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
"""
app.run方法,也就是启动这个服务
关于app.run,实际上是flask提供的一个测试应用服务器,仅在测试使用,不推荐在部署时使用
调用app.run实际上调用了run_simple
from werkzeug.serving import run_simple
try:
run_simple(host, port, self, **options)
finally:
# reset the first request information if the development server
# reset normally. This makes it possible to restart the server
# without reloader and that stuff from an interactive shell.
self._got_first_request = False
那么它的原理是什么呢?就类似于一个while死循环,里面有一个listen方法,不断地监听
"""
app.run()
执行成功,如果我们不想监听5000端口,该怎么办呢?很简单,直接在run方法里面指定相应的参数即可
app.run(port=8888)
由于pycharm有点问题,所以是终端启动的
可以看到监听的端口已经改了,并且flask也提示我们这是一个用于开发项目的服务器,不要在生产环境上使用
2.debug模式详解
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
"""
那么问题来了,如果代码出现了错误该怎么办呢?
比如这里1/0
"""
1 / 0
return 'Hello World!'
if __name__ == '__main__':
app.run(port=8888)
可以看到,在访问的时候,只是告诉我们服务器内部出现了错误,但是却并没有告诉我们哪里出错了
if __name__ == '__main__':
app.run(port=8888, debug=True)
如果加上debug=True的话,那么结果就不同了
此时当我再次访问页面的时候
就会显示详细的报错信息
那么为什么要开启debug模式呢
如果开启了debug模式,那么在代码中如果抛出了异常,在浏览器中可看到具体的错误信息和错误代码位置。方便开发者调试
如果开启了debug模式,那么以后修改了flask代码,无序手动重启,会自动重启,类似于Django
配置debug的四种方式
可以通过app.run(debug=True)来设置
直接通过app.debug = True来设置
app = Flask(__name__) app.debug = True
app下面有一个config属性,相当于app的配置文件,是一个字典。可以通过app.config.update(DEBUG=True)或者app.config.update({"DEBUG": True})或者app.config["DEBUG"]=True来设置,注意通过config来设置的话,那么这里的DEBUG要大写
app = Flask(__name__) app.config["DEBUG"] = True ''' 或者app.config.update("DEBUG"=True) 或者app.config.update({"DEBUG": True}) '''
通过配置文件的形式设置,将DEBUG=True写在一个配置文件里面,比如config.py,然后导入config,通过app.config.from_object(config)设置debug模式
3.配置文件的两种方式
使用
app.config.from_object
的方式来加载配置文件import config app.config.from_object(config)
使用
app.config.from_pyfile
的方式来加载配置文件app.config.from_pyfile("config.py") """ 而且app.config.from_pyfile,不仅可以加载py文件,其他文件也可以。 app.config.from_pyfile("config.py", silent=False),还可以有一个silent参数。 如果为False,表示如果文件没找到就报错,为True表示如果没找到就什么也不做,程序继续往下走。 """
那么flask是如何加载的呢?我们把看一下源码,把注释去掉之后长这样
def from_object(self, obj):
if isinstance(obj, string_types):
obj = import_string(obj)
for key in dir(obj):
if key.isupper():
self[key] = getattr(obj, key)
"""
可以看到flask是通过反射机制来加载的。
dir是python内置的方法,如果key都是大写字母的话,那么注册到config里面去,所以我们通过配置文件指定的时候要大写
"""
4.url中传递参数
from flask import Flask
app = Flask(__name__)
"""
只需要访问localhost:8888即可
"""
@app.route('/')
def hello_world():
return 'Hello World!'
# 访问localhost:8888/satori即可,访问该路由会自动执行该路由对应的装饰器所装饰的函数
# 从而返回"<h1>古明地觉</h1>"
# 再提一句,flask的api设计的真优雅
@app.route("/satori")
def satori():
return "<h1>古明地觉</h1>"
# 如何制定参数呢?
# 在tornado中,可以通过(),在flask中要通过<>,并且要指定名字
@app.route("/mashiro/<age>/<anime>")
def mashiro(anime, age):
"""
路由中指定了参数,那么在视图函数中也要定义相同个数的同名参数
比如这里路由中指定了<age><anime>,那么视图函数中也要有age和anime这两个参数,名字、个数都必须一样,否则报错
那么当我访问"localhost:8888/mashiro/18/樱花庄"的时候,age=18,anime="樱花庄"
因此在执行视图函数的时候,18就会赋值给age,"樱花庄"就会赋值给anime
当然这里视图函数的参数顺序是无所谓的,因为我们指定了变量名
等价于:
localhost:8888/mashiro/18/樱花庄 --映射--> /mashiro/<age>/<anime>
===>age=18, anime="樱花庄"
def mashiro(anime, age):
return age, anime
mashiro(age=18, anime="樱花庄")
因此视图函数里的参数顺序不重要,因为路由里的参数和视图里的参数保持一致,传参使用类似于关键字传参的方式
"""
return f"age={age}, anime={anime}"
if __name__ == '__main__':
app.run(port=8888, debug=True)
访问localhost:8888
访问localhost:8888/satori
访问localhost:8888/mashiro/18/樱花庄
限制用户输入的类型
但是现在问题又来了,如果我想在指定参数的时候,对参数的格式进行限制怎么办?比如有这样的一个路由"/96猫@/<length>/cm",这里的length显然要求类型是一个int,如果输入的不是int,则匹配失败。
在tornado中可以使用r"/96猫@/\d+/cm",但是在flask中要怎么做呢?
直接使用表示类型的关键字即可,<type:var>即可,比如这里就可以写成"/96猫@/<int:length>/cm"
注意int:length,:左右两边不可以有空格
from flask import Flask
app = Flask(__name__)
@app.route('/96猫@/<int:length>/cm')
def neko(length):
return f"96猫@{length}cm"
if __name__ == '__main__':
app.run(port=8888, debug=True)
那么关于类型的限制可以有哪些呢?
string
:是string,不是str。这是默认的数据类型,就是说我们不加类型,那么默认是string,可以接受任何没有" /"的文本int
:接收整型float
:浮点型path
:和string类似,但是可以接收\和/uuid
:接收uuid字符串any
:可以指定多种路径
from flask import Flask
app = Flask(__name__)
@app.route('/<path:adj>')
def path(adj):
return f"{adj}"
@app.route("/<uuid:arg>")
def uuid(arg):
return f"{arg}"
"""
这里的any要说一下,上面"/<uuid:arg>"表示我们的路由必须是"localhost:8888/uuid类型"
而这里的"/<any(foo,bar):func>"表示我们输入的必须是"localhost:8888/foo"或者"localhost:8888/bar"
"""
@app.route("/<any(foo,bar):func>")
def any(func):
if func == "foo":
return "foo"
else:
return "bar"
if __name__ == '__main__':
app.run(port=8888, debug=True)
获取用户输入的参数
如果我们想要拿到用户输入的参数怎么办呢?
tornado的话,直接使用self.get_argument,在flask中也是类似的
首先from flask import request,然后又request.arg.get()即可
from flask import Flask, request
app = Flask(__name__)
@app.route('/args')
def get_argument():
# 所有的参数和值都会以键值对的形式存储在request.args里
# 这个request.args是一个ImmutableMultiDict,说明可以使用dict的api
# 关于request后面会详细介绍
return f"a={request.args.get('a')}, b={request.args.get('b')}, c={request.args.get('c')}"
if __name__ == '__main__':
app.run(port=8888, debug=True)
5.url_for
这个url_for是个什么东西,如果熟悉tornado,那么肯定知道在tornado中,有一个反向解析,self.reverse_url,就是在定义路由的时候指定一个name,然后通过self.reverse_url(name),可以拿到对应的路由。
在flask中,url_for也是与其类似的
from flask import Flask
from flask import url_for
"""
函数的原型:def url_for(endpoint, **values):
这里的endpoint是什么?首先我们在浏览器上面输入的请求最终都是由我们的视图函数来执行的
因此endpoint就是我们的函数名,传参的时候,以字符串的形式。当然我们也可以手动指定endpoint
下面举个栗子:
"""
app = Flask(__name__)
@app.route('/hello')
def hello():
return "<h1>hello world</h1>"
@app.route('/get')
def get_url():
# 通过url_for,传入endpoint,可以拿到对应的路由
link = url_for("hello")
return f"<a href='{link}'>还是去看hello world吧</a>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
当然我们也可以手动指定endpoint
结果是一样的
但是如果我们通过url_for获取的路由需要参数呢?
from flask import Flask
from flask import url_for
app = Flask(__name__)
@app.route('/hello/<adj>', endpoint="蛤蛤蛤蛤蛤嗝")
def hello(adj):
return f"<h1>hello {adj} world</h1>"
@app.route('/你认为世界残忍吗/<any(yes,no):select>')
def get_url(select):
"""
我们到底要hello一个什么样的world呢?我们要为world指定一个形容词。
这个形容词是根据用户的输入来判断的
"""
if select == "yes":
adj = "cruel"
else:
adj = "beautiful"
# 形容词有了,但是我们要怎么传递呢
# 直接传递即可,endpoint对应的路由需要几个参数,我们就传几个,当然最好使用关键字参数
link = url_for("蛤蛤蛤蛤蛤嗝", adj=adj)
return f"<a href='{link}'>去看看吧,世界其实是很{adj}的</a>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
输入'localhost:8888/你认为世界残忍吗/yes'
输入'localhost:8888/你认为世界残忍吗/no'
问题又来了,就是这样,问题总是像递归一样,层层出现
首先如果我们参数指定不够,肯定是报错的,但是如果我们多指定了参数会怎么样呢?
from flask import Flask
from flask import url_for
app = Flask(__name__)
@app.route('/hello/<adj>', endpoint="蛤蛤蛤蛤蛤嗝")
def hello(adj):
return f"<h1>hello {adj} world</h1>"
@app.route('/many_arguments')
def get_url():
link = url_for("蛤蛤蛤蛤蛤嗝", adj="cruel", name="satori", age="16", where="东方地灵殿")
return f"<a href='{link}'>我多指定了参数,看看link是什么?{link}</a>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
url_for总结:
第一个参数应该是视图函数名对应的字符串,如果我们手动指定了endpoint,那么就是我们指定的。后面的参数则是endpoint对应的路由中指定的参数,会按照关键字参数的形式传递给相应的url。少指定是不可以的,但如果多指定,会变成查询字符串拼接到url的末尾。
6.自定义url转换器
之前我们使用了int,path等等定义url,但如果我们想要自己指定url的格式该怎么办呢?
首先我们要知道在werkzeug.routing里面有一个BaseConverter,我们把模块的注释去掉拿出来看看
'''
可以看到其他所有的Converter都是集成这个BaseConverter
'''
class BaseConverter(object):
"""Base class for all converters."""
regex = '[^/]+'
weight = 100
def __init__(self, map):
self.map = map
def to_python(self, value):
return value
def to_url(self, value):
return url_quote(value, charset=self.map.charset)
class UnicodeConverter(BaseConverter):
def __init__(self, map, minlength=1, maxlength=None, length=None):
BaseConverter.__init__(self, map)
if length is not None:
length = '{%d}' % int(length)
else:
if maxlength is None:
maxlength = ''
else:
maxlength = int(maxlength)
length = '{%s,%s}' % (
int(minlength),
maxlength
)
self.regex = '[^/]' + length
class AnyConverter(BaseConverter):
def __init__(self, map, *items):
BaseConverter.__init__(self, map)
self.regex = '(?:%s)' % '|'.join([re.escape(x) for x in items])
class PathConverter(BaseConverter):
regex = '[^/].*?'
weight = 200
DEFAULT_CONVERTERS = {
'default': UnicodeConverter, # 因此不加转换器默认是string类型
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
因此我们也可以按照相同的方式,定义一个类,继承自BaseConverter
from flask import Flask
from werkzeug.routing import BaseConverter
app = Flask(__name__)
class InfoConverter(BaseConverter):
'''
我们定义一个转换器,要求输入身高体重。
格式:xxxcm@xxxkg
'''
# 定义一个正则
regex = r"\d{2,3}cm@\d{1,3}kg"
# 然后要讲转换器进行注册, 这样才能找得到
# app.url_map.converters是一个字典
'''
self.converters = self.default_converters.copy()
default_converters = ImmutableDict(DEFAULT_CONVERTERS)
DEFAULT_CONVERTERS = {
'default': UnicodeConverter,
'string': UnicodeConverter,
'any': AnyConverter,
'path': PathConverter,
'int': IntegerConverter,
'float': FloatConverter,
'uuid': UUIDConverter,
}
'''
# 直接添加进去即可, 然后就可以直接使用了。info便也成了我们的转换器
app.url_map.converters["info"] = InfoConverter
# 因此info要满足InfoConverter里面定义的正则,满足regex,r"\d{2,3}cm@\d{1,3}kg"
@app.route(r"/satori/<info:info>")
def satori(info):
return f"success,info = {info}"
if __name__ == '__main__':
app.run(host="localhost", port=8888)
自定义url转换器的方式
1.实现一个类,类继承自BaseConverter
2.在自定义的类中,重写regex,也就是变量的正则表达式
3.将自定义的类映射到app.url_map.converters里面
to_python
这个to_python是什么呢?首先to_python是定义在转换器里面的一个方法
from flask import Flask
from werkzeug.routing import BaseConverter
app = Flask(__name__)
class ListConverter(BaseConverter):
# 同样需要继承BaseConverter
def to_python(self, value):
# 会将to_python的返回值传递到视图函数中作为参数
return value.split("+")
app.url_map.converters["list"] = ListConverter
@app.route(r"/mashiro/<list:value>")
def mashiro(value):
'''
当我们在浏览器中输入之后,会得到value对应的值。
一般情况下,输入的值会自动传给视图函数中的value,但是我们用list转换器。
那么会先将值传到to_python,然后将to_python的返回值传给视图函数中的value。
因此转换器可以有两种:第一种是输入的格式有限制,但是不会改变,从浏览器中得到的什么就往视图函数里面传什么
第二种是输入的格式没有限制,但是会将得到的内容进行转化,然后再传给视图函数。
to_python显然是第二种
'''
return f"value = {value}"
if __name__ == '__main__':
app.run(port=8888, debug=True)
"a+b"这个字符串,传递给to_python中的value,然后value进行split("+"),然后将返回值传给视图函数中的value
to_url
还有一个to_url,这个主要是和url_for搭配使用
from flask import Flask, url_for
from werkzeug.routing import BaseConverter
app = Flask(__name__)
class ListConverter(BaseConverter):
def to_url(self, value):
return "hello"
app.url_map.converters["list"] = ListConverter
@app.route(r"/mashiro/<list:value>")
def mashiro(value):
return f"猜猜看value是什么?{value}"
@app.route(r"/koishi")
def koishi():
link = url_for("mashiro", value="随+便+指+定+一+个")
return f"<a href='{link}'>link</a>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
from flask import Flask, url_for
from werkzeug.routing import BaseConverter
app = Flask(__name__)
class ListConverter(BaseConverter):
def to_url(self, value):
return "".join(value.split("+"))
app.url_map.converters["list"] = ListConverter
@app.route(r"/mashiro/<list:value>")
def mashiro(value):
return f"猜猜看value是什么?{value}"
@app.route(r"/koishi")
def koishi():
link = url_for("mashiro", value="随+便+指+定+一+个")
"""
在使用url_for的时候,如果没有转换器,那么会自动将参数赋值给路由的url中的value
也就是说,如果value没有list装饰的话,那么得到的url应该是"/mashiro/随+便+指+定+一+个"
但是这个value被list装饰了,那么这个url_for中指定的value就不会传给路由的url(或者视图函数)中的value了
而是会传给to_url中的value,然后将to_url的返回值传递给路由的url(视图函数)中的value
而to_url的返回值是return "".join(value.split("+")),将url_for中的value带进去
结果是"随便指定一个",所以得到的路由(或者说路由中的url)就是"/mashiro/随便指定一个"
"""
return f"<a href='{link}'>link</a>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
to_python和to_url总结
to_python:当用户传来参数时,不会传递给路由(视图函数),而是传递给to_python中的value,然后将to_python的返回值传递给路由中的url
to_url:当使用url_for指定参数时,也不会传递给endpoint对应的路由(视图函数),而是传递给to_url中的value,然后将to_url的返回值传递给路由中的url
7.post请求
我们之前的请求都是get请求,那么如何进行post请求呢?
from flask import Flask
app = Flask(__name__)
"""
不指定methods那么默认是get请求,如果指定请求为post的话
直接加上methods=["POST"]即可如果既想get又想post
指定methods=["GET", "POST"]
"""
@app.route("/post", methods=["POST"])
def post():
return "success"
if __name__ == '__main__':
app.run(port=8888, debug=True)
我们使用requests来试一下
import requests
res = requests.post("http://localhost:8888/post")
print(res.text) # success
res = requests.get("http://localhost:8888/post")
print(res.text)
"""
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>405 Method Not Allowed</title>
<h1>Method Not Allowed</h1>
<p>The method is not allowed for the requested URL.</p>
"""
模拟表单提交
post.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/info" method="post">
姓名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="submit" value="提交">
</form>
</body>
</html>
from flask import Flask
app = Flask(__name__)
'''
我们来进行一个表单提交
flask和tornado不一样,我们如果不指定methods,那么默认只是GET
当我们输入url,会显示一个表单页面,这没问题。一旦当我们点击提交,就是POST请求。
而默认只能GET,所以就报错了。因此必须要显示的指明请求种类,加上methods=["GET", "POST"],表示两种请求都支持
'''
@app.route('/info', methods=["GET", "POST"])
def info():
# 用于获取参数
from flask import request
# 用于渲染模板
from flask import render_template
if request.method == "GET":
'''
模板渲染默认使用的是jinja2,而且在没设置模板路径的情况下,
要把html文件放在templates文件夹里面,不要和py文件放在一起。
因为是flask使用jinja2去找,所以要指定位置,不指定的话,默认是到templates这个文件夹下面找
'''
return render_template("post.html")
# 否则执行post请求
else:
'''
request.args.get,是获取get请求(url)里面的参数
request.form.get,是获取post请求(form表单)里面的参数
'''
name = request.form.get("username")
passwd = request.form.get("password")
return f"username is {name}, password is {passwd}"
if __name__ == '__main__':
app.run(port=8888, debug=True)
8.重定向
重定向分为永久性重定向和暂时性重定向,在页面的体现就是浏览器会从一个页面跳转到另一个页面。比如用户访问了一个需要权限的页面,但是该用户当前并没有登录,因此我们应该给他重定向到登录页面。
永久性重定向:http的状态码是301,多用于旧网址被废弃了要转移到新网址确保用户可以访问,最经典的就是京东网站,输入www.jingdong.com,会被重定向到www.jd.com,因为www.jingdong.com这个网址已经废弃了,所以这种情况应该被永久重定向。
暂时性重定向:http状态码是302,表示页面的暂时性跳转。比如访问一个需要权限的网址,如果当前用户没有登录,应该重定向到登录页面。这种情况应该使用暂时性重定向
from flask import Flask, redirect, url_for, request
app = Flask(__name__)
@app.route("/login")
def login():
return "欢迎登陆"
@app.route("/index")
def index():
return '<h2 style="text-align: center">欢迎来到古明地觉的避难小屋<h2>'
@app.route("/info")
def info():
name = request.args.get("name")
password = request.args.get("password")
if name == "satori" and password == "komeijisatori":
# code为301,永久性重定向。302,暂时性重定向。默认为302
return redirect(url_for("index"), code=302)
else:
return redirect(url_for("login"))
if __name__ == '__main__':
app.run(port=8888, debug=True)
输入localhost:8888/info?name=satori&password=komeijisatori
输入其他的
9.响应(response)
from flask import Flask
from flask import Response
"""
其实这个Response是位于werkzeug.wrappers下,但是我们通过flask也能导入
其实说白了flask+werkzeug+SQLAlchemy+jinja2是一个整体,werkzeug和jinja2都在flask下的__init__中被导入了进来
所有我们from flask import Response 和 from werkzeug.wrappers import Response是一样的
"""
app = Flask(__name__)
@app.route(r"/index")
def index():
'''
其实return返回只能返回一个Response对象,
这里返回一个hello world等价于Response("hello world", status=200, mimetype="text/html")
会自动帮我们做一个转换
'''
return "hello world"
@app.route(r"/index1")
def index1():
'''
也可以返回一个元组,response,status,headers(一个列表或者字典,作为额外的消息头)
'''
return "hello index", 200, {"X-NAME": "satori"}
if __name__ == '__main__':
app.run(port=8888, debug=True)
虽然可以省略Response,但我们还是推荐使用的,为什么呢?因为可以设置cookie,headers等等
from flask import Flask
from flask import Response
app = Flask(__name__)
@app.route(r"/index1")
def index1():
res = Response("hello index")
# 设置cookie
res.set_cookie("foo", "bar")
"""
刚才说了, 除了返回一个Response对象,还可以带上一个status和一个headers,总共返回三个值
"""
return res, 200, {"X-NAME": "satori"}
if __name__ == '__main__':
app.run(port=8888, debug=True)
10.flash(闪现)
flash,在flask都翻译为闪现,在A页面进行闪现,跳转到B页面可以获取相应的信息。
from flask import Flask, flash, get_flashed_messages
"""
闪现是通过session实现的,后面会介绍
先把数据存在session里面,只要不删除,会一直都在
获取的时候,通过session.pop()获取
并且flask要求对app设置一个secret_key才可以使用闪现
"""
from flask import redirect, url_for
app = Flask(__name__)
import secrets
app.secret_key = secrets.token_hex()
@app.route(r"/index")
def index():
flash("这是一个普通的闪现,唰~~~")
flash("这是一个指定类别的闪现,唰~~~", category="fafafa")
return redirect(url_for("index1"))
@app.route(r"/index1")
def index1():
# 获取闪现的内容
# 闪现没有分类时
print(get_flashed_messages())
# 闪现分类时
print(get_flashed_messages(category_filter=["fafafa"]))
return "hahaha"
if __name__ == '__main__':
app.run(port=8888, debug=True)
访问localhost:8888/index跳转到localhost:8888/index1
11.定制错误页面
如果用户访问一个不存在的路由,那么我们可以跳转到错误页面,而不是返回错误信息。
from flask import Flask
app = Flask(__name__)
@app.route(r"/index")
def index():
return "hello world"
@app.errorhandler(404)
def error(err):
return "您要找的页面去火星了"
if __name__ == '__main__':
app.run(port=8888, debug=True)
12.用户认证
现在有一个问题,我想根据用户传来的url,对用户进行认证。
from flask import Flask, request
app = Flask(__name__)
@app.route(r"/index")
def index():
name = request.args.get("name")
password = request.args.get("password")
if name == "satori" and password == "123456":
return "<h1>欢迎来到古明地觉的避难小屋</h1>"
else:
return "<h1>fuck off</h1>"
@app.route(r"/index1")
def index1():
name = request.args.get("name")
password = request.args.get("password")
if name == "satori" and password == "123456":
return "<h1>欢迎来到古明地觉的避难小屋</h1>"
else:
return "<h1>fuck off</h1>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
这样做显然是可以的,但是很麻烦,因为代码重复度大,因此我们可以将代码抽离出来
from flask import Flask, request
import functools
app = Flask(__name__)
def auth(func):
"""
为什么要有这一行@functools.wraps(func),我们要使用这个装饰器对视图函数进行装饰
一旦装饰,那么当访问/index和/index1的时候,本质上执行的都是inner
这样的话两个路由对应的视图函数名就都叫inner,由于是在闭包中,所以是两个函数
尽管是两个函数,但是函数名都叫inner,而在flask中这是不被允许的
因为我们加上@functools.wraps(func),表示装饰的时候将原函数的信息也拷贝过去
为什么函数名相同不被允许呢?
其实在flask中,endpoint和视图函数是在一个字典中
如果不加@functools.wraps(func)
那么当对index函数装饰完毕,注册路由时候,存在一个endpoint="inner",对应函数为inner
为什么endpoint不是index,因为在注册路由的时候,函数已经被装饰了。下面是部分源码
if view_func is not None: # 同理auth函数对index1装饰完毕、注册时候,存在endpoint="inner",也有一个函数inner
# 会根据当前的endpoint去self.view_functions(函数映射)里面找
# 看看当前endpoint是否有函数对应
# 如果没有,证明是新的endpoint,那么直接注册到self.view_functions里面去
old_func = self.view_functions.get(endpoint)
# 如果有,那么将函数获取出来。
# 如果发现是两个不同的函数,说明一个endpoint对应了两个函数,所以会报错。
if old_func is not None and old_func != view_func:
raise AssertionError(
"View function mapping is overwriting an "
"existing endpoint function: %s" % endpoint
)
# 因此我们要加上装饰器,表示将原函数的信息也拷贝过去
# 这样即使装饰完毕,由于@functools.wraps(func)的特征
# 第一个endpoint仍是index,第二个endpoint仍是index1
# 这样就不会出现endpoint、视图函数之间冲突的情况。
# 因为即使是两个不同的函数,但是由于都叫inner,因此导致endpoint也都叫inner
# 所以加上@functools.wraps(func),使得endpoint还是原来自己的函数名称,这样就不会造成冲突了
self.view_functions[endpoint] = view_func
"""
@functools.wraps(func)
def inner(*args, **kwargs):
name = request.args.get("name")
password = request.args.get("password")
if name == "satori" and password == "123456":
return func(*args, **kwargs)
else:
return "<h1>fuck off</h1>"
return inner
"""
@auth要写在下面,因为@app.route要对应一个函数,@auth写在上面没有意义。
"""
@app.route(r"/index")
@auth
def index():
return "<h1>欢迎来到古明地觉的避难小屋</h1>"
@app.route(r"/index1")
@auth
def index1():
return "<h1>欢迎来到古明地觉的避难小屋</h1>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
13.钩子函数
类似于Django中的中间件,在每一次请求到来的时候,都会执行。
常用的钩子函数:
app.before_request:每一次请求到来都会先执行
app.after_request:每一次请求结束之后会执行
app.before_first_request:第一次请求过来的时候会先执行,只会执行一次
没有app.after_first_request
app.context_processor:上下文处理器,保证每一个页面都会有相同的信息。不好解释,可以看例子
app.error_handler():自定义错误,这个已经介绍过了
from flask import Flask, request
app = Flask(__name__)
"""
@app.before_request,表示每一次请求到来的时候,先要执行的函数.
比如我们访问/index,那么在执行/index执行的视图函数之前,会先执行before_request
如果这个函数没有返回或者返回None的时候,那么会继续往下走,如果返回了,就不会再执行相应请求的视图函数了
可以看到这就类似于Django之间的中间件
请求 -----> before_request(没有返回值) -----> 请求对应的视图函数
请求 -----> before_request(有返回值)
|
直接返回<--------|
可以看到,可以实现一个拦截器的效果,或者进行一些初始化的操作。比如我们上面用户认证就可以不适用装饰器而使用这种方法
"""
@app.before_request
def before_request():
name = request.args.get("name")
password = request.args.get("password")
if name == "satori" and password == "123456":
pass
else:
return "<h1>fuck off </h1>"
@app.route("/index")
def index():
return "<h1>hello world</h1>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
from flask import Flask
app = Flask(__name__)
@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对象,记住一定要返回
# 当然页面显示的内容肯定不是这个response,还是我们视图函数里面返回的内容
return response
@app.route("/index")
def index():
return "<h1>hello world</h1>"
if __name__ == '__main__':
app.run(port=8888, debug=True)
连续请求三次
当然三次都是返回这个结果
加上钩子函数我们好像写了3个版本,第一个是直接在每一个视图函数里面都进行认证,第二个是通过装饰器,第三个则是通过钩子函数。对于第一个版本,几乎不用。第二个装饰器版本,则是有应用场景的,如果很多函数,我们只对一小部分进行处理,那么可以使用装饰器。如果是对全部进行批量处理,那么则可以使用钩子函数
还有一个app.context_processor钩子函数,没有介绍,这是什么呢?
test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
{{ name }}:{{ gender }}
</body>
</html>
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/satori")
def satori():
"""
这是模板语法,后面会介绍
我们在html中定义了{{name}}和{{gender}}
那么在渲染模板的时候要传进去
"""
return render_template("test.html", name="古明地觉", gender="女")
@app.route("/koishi")
def koishi():
return render_template("test.html", name="古明地恋", gender="女")
@app.route("/scarlet")
def scarlet():
return render_template("test.html", name="芙兰朵露斯卡雷特", gender="女")
if __name__ == '__main__':
app.run(port=8888, debug=True)
但是我们发现gender是重复的,也就是说我们每次都要重新传一次,那么有没有简单的办法呢?这个时候app.context_processor钩子函数就派上用场了
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/satori")
def satori():
"""
这是模板语法,后面会介绍
我们在html中定义了{{name}}和{{gender}}
那么在渲染模板的时候要传进去
"""
return render_template("test.html", name="古明地觉")
@app.route("/koishi")
def koishi():
return render_template("test.html", name="古明地恋")
@app.route("/scarlet")
def scarlet():
return render_template("test.html", name="芙兰朵露斯卡雷特")
"""
此时在渲染模板的时候,会自动将字典打开,变成关键字参数传进去
那么在渲染模板的时候,就不需要传入gender了
"""
@app.context_processor
def context_processor():
return {"gender": "女"}
if __name__ == '__main__':
app.run(port=8888, debug=True)
访问页面,返回的结果和之前是一样的,不再演示