Flask&&人工智能AI --2
参考博客:
https://www.cnblogs.com/xiao987334176/p/9598606.html
昨日作业讲解
昨天的作业就是,有3个视图函数,分别是/login,/student_list,/student_detail。写一个装饰器,除了/login以外,其他视图函数都要登录才行!
使用session判断!
原始代码
from flask import Flask,render_template,sessions,request,redirect app = Flask(__name__) USER = {'username': 'xiao', 'password': "123"} @app.route("/login",methods=["POST","GET"]) def login(): if request.method == "GET": # 前端模板中使用了msg,这里就算是传递空,也要出现msg return render_template("login.html", msg="") if request.form["username"] == USER["username"] and request.form["password"] == USER["password"]: return redirect("/student_list") return render_template("login.html", msg="用户名密码错误") @app.route("/student_list") def student_list(): return "学生列表" @app.route("/student_detail") def student_detail(): return "学生详情" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
使用装饰器
from flask import Flask,render_template,session,request,redirect app = Flask(__name__) # 使用session,必须设置secret_key app.secret_key = "123asdzxc" USER = {'username': 'xiao', 'password': "123"} def auth(func): def inner(*args,**kwargs): if session.get("user"): return func(*args,**kwargs) else: return redirect("/login") return inner @app.route("/login",methods=["POST","GET"]) def login(): if request.method == "GET": # 前端模板中使用了msg,这里就算是传递空,也要出现msg return render_template("login.html", msg="") username = request.form["username"] # 获取POST请求时,FormData中的参数 # password = request.form.get("password") if request.form["username"] == USER["username"] and request.form["password"] == USER["password"]: # 设置session session["user"] = username return redirect("/student_list") return render_template("login.html", msg="用户名密码错误") @app.route("/student_list",endpoint="student_list") @auth def student_list(): return "学生列表" @app.route("/student_detail",endpoint="student_detail") @auth def student_detail(): return "学生详情" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
重启flask,直接访问student_list
http://127.0.0.1:5000/student_list
打开浏览器工具,查看网络。它会跳转至登录页面!
输入正确的用户名和密码
提交之后,跳转页面
注意:使用装饰器的视图函数,必须要定义endpoint参数。
因为使用装饰器之后,视图函数都是inner,所以flask无法区分,路由到底指向哪一个视图函数。
启动flask之后,会直接报错。endpoint参数,是给url起别名,唯一标识。可以做url反向解析!
还有一种方法,使用@functools.wraps装饰器,保留原函数信息,比如函数名。
但是不推荐使用。因为定义视图函数,本身就应该定义endpoint参数
一、装饰器的坑以及解决方法
1、使用装饰器装饰两个视图函数,代码如下
from flask import Flask, redirect, render_template, request, session app = Flask(__name__) app.secret_key = "wanglili" # 装饰器函数 def outer(func): def inner(*args, **kwargs): if session.get("user"): # 验证session ret = func(*args, **kwargs) return ret else: return redirect("/login") return inner @app.route("/") @outer # inner=outer(index) def index(): return render_template("index.html") @app.route("/course") @outer def course(): return render_template("course.html") @app.route("/login", methods=["POST", "GET"]) def login(): if request.method == "GET": return render_template("login.html") if request.form.get("username") == "wll"and request.form.get("password") == "123": session["user"] = request.form.get("username") # 写入session return redirect("/") return render_template("login.html") app.run(debug=True)
启动程序有如下错误:
我们还发现当装饰一个视图函数时可以正常运行,而装饰两个或两个以上视图函数则会报以上错误。
2、解决方式
1)方式一:使用functools模块
import functools def outer(func): @functools.wraps(func) # 保留原函数func的信息 def inner(*args, **kwargs): if session.get("user"): ret = func(*args, **kwargs) return ret else: return redirect("/login") return inner
2)方式二:使用flask提供的endpoint参数
@app.route("/", endpoint='index') @outer def index(): return render_template("index.html") @app.route("/course", endpoint='course') @outer def course(): return render_template("course.html")
二、Flask的路由系统
Flask中的路由系统其实我们并不陌生了,从一开始到现在都一直在应用
@app.route("/",methods = ["POST","GET"])
为什么要这么用?其中的工作原理我们知道多少吗?
@app.route() 装饰器中的参数
1、@app.route()装饰器中的参数
如果不明白装饰器 点击这里
methods
methods : 当前 url 地址,允许访问的请求方式
from flask import Flask,request app = Flask(__name__) @app.route("/info", methods=["GET", "POST"]) def student_info(): stu_id = int(request.args["id"]) return f"hello kitty {stu_id}" # Python3.6的新特性 f"{变量名}" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
访问url,注意:要带上参数id,否则报错
http://127.0.0.1:5000/info?id=1
效果如下:
endpoint
endpoint:反向url地址,默认为视图函数名 (url_for)
from flask import Flask,request,url_for app = Flask(__name__) @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 kitty {stu_id}" # Python3.6的新特性 f"{变量名}" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
刷新页面,效果同上!查看Pycharm控制台输出:
/info
注意:这并不是完整的url。如果要给前端妹子,返回一个完整的URL怎么办呢?
url_for
url_for:用于反向生成url,也可以附带一些参数,比如想要完整的URL,可以设置_external为Ture:
from flask import Flask,request,url_for app = Flask(__name__) @app.route("/info", methods=["GET", "POST"],endpoint="r_info") def student_info(): stu_id = int(request.args["id"]) print(url_for("r_info", _external=True)) return f"hello kitty {stu_id}" # Python3.6的新特性 f"{变量名}" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
刷新页面,效果同上!查看Pycharm控制台输出:
http://127.0.0.1:5000/info
但是还不够,参数没有啊?怎么办?再加上url参数。
注意:由于id是动态参数,所以后台获取时,要和实际的参数匹配。这里是id
from flask import Flask,request,url_for app = Flask(__name__) @app.route("/info", methods=["GET", "POST"],endpoint="r_info") def student_info(): stu_id = int(request.args["id"]) print(url_for("r_info", _external=True,id=stu_id)) return f"hello kitty {stu_id}" # Python3.6的新特性 f"{变量名}" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
刷新页面,效果同上!查看Pycharm控制台输出:
http://127.0.0.1:5000/info?id=1
这下,就非常完美了!
defaults
defaults : 视图函数的参数默认值{"nid":100}
注意:视图函数必须要设置形参nid,否则报错!
from flask import Flask,request,url_for app = Flask(__name__) @app.route("/info", methods=["GET", "POST"],endpoint="r_info",defaults={"nid": 100}) def student_info(nid): # stu_id = int(request.args["nid"]) print(url_for("r_info", _external=True)) print(nid) return f"hello kitty {nid}" # Python3.6的新特性 f"{变量名}" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
访问url: http://127.0.0.1:5000/info
效果如下:
strict_slashes
strict_slashes : url地址结尾符"/"的控制 False : 无论结尾 "/" 是否存在均可以访问 , True : 结尾必须不能是 "/"
from flask import Flask,request,url_for app = Flask(__name__) @app.route("/info", strict_slashes=True) def student_info(): return "hello kitty info" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
末尾不带 "/"
末尾带 "/"
也就是说:strict_slashes = True ,表示开启路由严格匹配模式!即使末尾多了一个"/",也不允许访问!
将参数改为False
from flask import Flask,request,url_for app = Flask(__name__) @app.route("/info", strict_slashes=False) def student_info(): return "hello kitty info" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
刷新页面,又可以访问了
如果多加一个s,会报404
总结:strict_slashes 用来控制末尾是否有"/",为Ture,带 "/"不允许!
为False,不管你带不带"/",都可以访问!
redirect_to
redirect_to : url地址重定向
from flask import Flask,request,url_for app = Flask(__name__) @app.route("/info", redirect_to="/infos") def student_info(): return "hello kitty info" @app.route("/infos") def student_infos(): return "Hello Tom infos" if __name__ == '__main__': app.run("0.0.0.0", 5000, debug=True)
访问url: http://127.0.0.1:5000/info,会发生重定向
查看浏览器工具,查看网络。它经历了2次请求!
它有什么应用场景呢?
比如你的网站域名是xx.com,后来你的网页改版了,换了新的域名qqxx.com。但是老客户还不知道你的新域名啊!怎么办呢?用重定向就可以了!
subdomain
subdomain : 子域名前缀 subdomian="qq" 这样写可以得到 qq.xx.com 前提是app.config["SERVER_NAME"] = "xx.com:5000"
from flask import Flask,request,url_for app = Flask(__name__) # 一定一定一定要写端口!!!!!! app.config["SERVER_NAME"] = "xx.com:5000" @app.route("/",endpoint="r_info",subdomain="qq") def student_info(): print(url_for("r_info", _external=True,)) return "hello kitty info" if __name__ == '__main__': # 监听端口为5000,注意:要和SERVER_NAME匹配! app.run("0.0.0.0", 5000, debug=True)
注意:app.config["SERVER_NAME"] = "xx.com:5000"。
一定要加端口!一定要加端口!一定要加端口!重要的事情说三遍!!!
就是因为没有加端口,快要放弃了!google了一把,终于找到原因了!
这里是window 10访问。必须要修改本机的Hosts文件,至于怎么修改,为啥没有权限,请自行百度!
修改Hosts文件,添加一条记录
127.0.0.1 qq.xx.com
打开cmd窗口,ping qq.xx.com,请确保返回地址是127.0.0.1
打开浏览器访问url,注意:只能是qq.xx.com访问。不能是xx.com!访问时,一定要带上端口!
http://qq.xx.com:5000/
效果如下:
关于路由目前就说这么多,之后的课程中会有关于Flask路由系统的源码剖析,再详细说明Flask路由系统的工作原理
动态参数路由
<int:参数名> 参数转换为整形
<int:nid> 就是在url后定义一个参数接收
但是这种动态参数路由,在url_for的时候,一定要将动态参数名+参数值添加进去,否则会抛出参数错误的异常
from flask import Flask,request,url_for app = Flask(__name__) @app.route("/info/<int:nid>", endpoint="r_info") def student_info(nid): print(url_for("r_info", _external=True,nid=nid)) return f"hello kitty {nid}" # Python3.6的新特性 f"{变量名}" if __name__ == '__main__': # 监听端口为5000 app.run("0.0.0.0", 5000, debug=True)
访问url,一定要带上参数,而且参数必须是数字!
查看Pycharm控制台输出:
12 <class 'int'> http://127.0.0.1:5000/info/12
如果是字符串,会报错
<string:参数名> 参数转换为字符串
from flask import Flask,request,url_for app = Flask(__name__) @app.route("/info/<string:nid>", endpoint="r_info") def student_info(nid): print(nid,type(nid)) print(url_for("r_info", _external=True,nid=nid)) return f"hello kitty {nid}" # Python3.6的新特性 f"{变量名}" if __name__ == '__main__': # 监听端口为5000 app.run("0.0.0.0", 5000, debug=True)
刷新页面,就可以访问了
查看Pycharm控制台输出:
ask <class 'str'> http://127.0.0.1:5000/info/ask
参数是数字也是正常的
三、flask实例化配置
app = Flask(__name__) # Flask的实例化对象app
实例化对象时的参数如下:
template_folder="temp" 默认模板存放目录 templates static_folder="static" 默认静态文件存放目录 static static_url_path="/static" 访问静态文件路由地址,默认是"/"+static_folder static_host=None 指定静态文件服务器地址 host_matching = False 若不是特别需要时,慎用,否则所有的route 都需要host=""的参数 subdomain_matching = False 理论上来说是用来限制SERVER_NAME子域名的,但是目前还没有感觉出来区别在哪里 instance_path = None 指向另一个Flask实例的路径 instance_relative_config = False 是否加载另一个实例的配置 root_path = None 主模块所在的目录的绝对路径,默认项目目录
四、flask对象配置
Flask对象即Flask的实例化对象app,Flask的对象配置就是在 app.config 中添加键值对,例如,app.config["SECRET_KEY"]="/wrwfgs",但是你存进去的键必须是config中应该存在的,如果不存在的话,它会默认无用,所以我们来看一下config中有哪些key以及对应的作用?
'DEBUG': False, # 是否开启Debug模式 'TESTING': False, # 是否开启测试模式 'SECRET_KEY': None # 在启用Flask内置Session的时候/开启flash,一定要有它 'PERMANENT_SESSION_LIFETIME': 31, # days , Session的生命周期(天)默认31天 'SESSION_COOKIE_NAME': 'session', # 在cookies中存放session加密字符串的名字 '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 异常。 # 因为在调试中,你希望准确地找出异常的原因,这个设置用于在这些情形下调试。 'EXPLAIN_TEMPLATE_LOADING': False, # 如果这个值被设置为True,你只会得到常规的回溯。 '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,
以上这些key都可以被改写,当然它们也有默认值,如果没有特殊情况,不要改写它的默认值。
除了单独修改某一项的配置外,还可以通过类的方式导入配置:
首先定义一个flask_settings.py,内容如下:
class FlaskDebug(object): # debug模式下的类 DEBUG=True SECRET_KEY="DEBUGmoshidesecret_key" PERMANENT_SESSION_LIFETIME = 7 SESSION_COOKIE_NAME = "debug_session"
然后再flask的启动文件中使用,如下:
from flask import Flask app = Flask(__name__) app.config.from_object(flask_settings.FlaskDebug) # 配置debug模式
五、初识flask蓝图(Blueprint)
作为初学flask的我们,可以将蓝图理解为没有run方法的Flask对象,接下来学习如何创建以及配置蓝图。
首先创建一个目录flask_project/app01,在app01目录中创建一个views.py文件,内容如下:
from flask import Blueprint # 从flask中导入蓝图 bp1 = Blueprint("bp1", __name__) # 实例化一个蓝图对象 @bp1.route('/stuList') # 添加路由并定义视图函数 def stulist(): return "student list"
在flask_project目录中创建manage.py文件,内容如下:
from flask import Flask from app01 import views # 导入刚写好的蓝图所在模块 app = Flask(__name__) app.register_blueprint(views.bp1) # 在Flask对象中注册蓝图对象,重要 if __name__ == '__main__': app.run(debug=True)
此时,运行manage.py文件启动服务,然后访问http://127.0.0.1:5000/stuList,即可看到返回结果。
上述蓝图的视图函数返回了一个HttpResponse对象,那么是否可以返回模板页面呢?当然可以,实例化蓝图(Blueprint)对象时,也可以配置蓝图的模板目录(template_folder参数)和静态文件目录(static_folder参数),也就说蓝图的对象配置与Flask的对象配置方法一样,在此不过多介绍。
需要特别说明的是蓝图可以指定url前缀(url_prefix参数),方法如下:
方法一:实例化蓝图对象时指定url前缀
bp1 = Blueprint("bp1", __name__, url_prefix='/app01')
方法二:在注册蓝图时指定url前缀
app.register_blueprint(views.bp1, url_prefix='/app01')
此时,浏览器要访问地址http://127.0.0.1:5000/app01/stuList
六、flask特殊装饰器
1、flask的中间件
1)@app.before_request # 请求进入视图函数之前,类似于django中间件的request_process
2)@app.after_request # 响应返回客户端之前,类似于django中间件的response_process
manage.py代码如下:
from flask import Flask app = Flask(__name__) @app.before_request def be1(): print('我是be1') @app.before_request def be2(): print('我是be2') @app.before_request def be3(): print('我是be3') @app.after_request def af1(response): print('我是af1') return response @app.after_request def af2(response): print('我是af2') return response @app.after_request def af3(response): print('我是af3') return response @app.route('/index') def index(): print('我是视图函数') return 'ok' if __name__ == '__main__': app.run(debug=True)
启动项目,访问http://127.0.0.1:5000/index,打印结果如下:
我是be1
我是be2
我是be3
我是视图函数
我是af3
我是af2
我是af1
分析:@app.after_request自上而下依次执行,@app.after_request自下而上依次执行。与django的中间件类似,@app.after_request装饰的函数不写return或者returnNone表示放行,顺利通过,否则拦截请求,返回响应。@app.after_request装饰的函数一定要有返回值,且必须有参数接收response对象。
修改be1函数为如下代码:
@app.before_request def be2(): print('我是be2') return 'error'
执行结果如下:
我是be1 我是af3 我是af2 我是af1
总结:
正常情况下流程:be1 - be2 - be3 - af3 - af2 - af1
异常(be1函数发生异常)情况下流程:be1 - af3 - af2 - af1
2、重定义错误页面返回信息
@app.errorhandler(404)# 参数是错误代码 def error404(error_info):# 注意,一定要加参数接收错误信息 print(error_info) # 404 Not Found: The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again. return '页面不存在'# 可以返回 三剑客 + 小儿子
当访问时发生404错误时,会看到该函数返回的内容。