Flask入门

Flask入门

常见python-web框架:

django:大而全的web框架,自己内置了很多app,第三方适配的模块也多,但由于过于全,在开启小项目时,略显臃肿。

flask:小而精的python-web框架,甚至可以在一个py文件中完成web最基础的功能,而完成更丰富的功能则需要借助第三方模块。

web.py:是一个小巧灵活的Python框架,它简单而且功能强大(国内几乎没有用的)

常见的异步web框架:

fastapi:python的异步web框架,不少公司在用,专门做前后端分离用于写接口

https://fastapi.tiangolo.com/zh/

sanic:python的异步web框架,供支持异步高并发请求的 web 服务

https://sanic.dev/zh/

tornado:异步框架,用的比较少了:

https://www.tornadoweb.org/en/stable/

这里需要强调所谓同步框架和异步框架,是有本质上的区别的:

同步框架并不代表没有使用异步,而是对于处理请求到返回响应的过程是同步的,一个线程只能处理一个请求,请求在响应前一直占据此线程。同步框架项目也会有异步并发,如django就可以同时处理多个请求(虽然并发量很小)是通过网关服务器实现的。

异步框架则可以灵活的调度线程,当处理请求的过程中出现了IO等阻塞操作,就会将当前线程重新分配用于处理其他请求,可以非常显著的提高cpu的利用率和并发量。

简单来说:

  • 同步框架的一个线程只能处理一个请求
  • 异步框架的一个线程可以处理多个请求

flask简介

Flask是一个基于Python开发并且依赖jinja2模板和Werkzeug WSGI服务的一个微型框架。

jinja2是支持模板语法的模块,可以用于在html文档中用特殊的模板语法进行插值渲染,主要用于前后端混合的项目。

Werkzeug WSGI 符合wsgi协议的web服务器,与django使用的wsgiref是不同的web服务器。

Hello World

from flask import Flask

app = Flask(__name__)  # 初始化app

@app.route('/')  # 注册路由
def index():  # 视图函数
    return 'hello world'

if __name__ == '__main__':
    app.run()  # 启动项目

前后端混合项目演示

这个小项目用了古老的表单标签(使用form标签可以发送post请求的特性)。

html模板文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form method="post">
    <input type="text" name="num1"> +
    <input type="text" name="num2"> = {{summary}}
    <input type="submit" value="计算"> {{error}}
</form>
</body>
</html>

py文件(项目后端)

from flask import Flask, request, render_template

app = Flask(__name__)

# 根路径
@app.route('/', methods=['GET', 'POST'])
def index():
    # flask不需要传入request对象,使用全局的request
    if request.method == 'GET':
        return render_template('login.html')  # 返回模板
    else:
		if num1.isdigit() and num2.isdigt():
            return render_template('sum.html',summary=int(num1)+int(num2))
        else:
            return render_template('login.html', error='输入的不是纯数字')  # 注意跟django的render区分,要模板渲染的数据,直接key=value传即可

首先浏览器按照路由发送get请求拿到html文件,在页面中,输入两个数字,点击提交,则会向本地址(相同路由)发送post请求,再进入post的分支,携带插值变量渲染html模板返回前端。

flask基础

  1. 注册路由可以通过装饰器app.route(路径,methods=[请求方式,'get','post']),加装在某个函数上,这个函数就是视图函数了,路径中可以使用转换器动态匹配路由

  2. 视图函数必须返回响应内容,基础的有:

    • render_template 按模板返回
    • redirect 重定向到其他路由
    • 字符串 与django的HttpResponse类似
    • jsonify 返回json格式字符串(内部可以填入列表,字典等)
  3. 请求的request对象,是全局的,直接导入使用即可,在不同视图函数中不会混乱

  4. session 全局的,直接导入使用即可

    要使用session前一定要指定秘钥app.secret_key = '越复杂越好'

  5. 模板渲染使用的jinjia2,比django的模板语法更强大,可以加括号传参调用,还可以使用中括号取值等。

flask配置方式

flask有多种配置方式,在此罗列一下:

  1. flask对象app=Flask()中,可以app.debugapp.secret_key,配置debug和秘钥

    app.config['配置键'] = 配置值可以设置其他配置项

  2. 使用py文件导入(类似于django的settings.py)

    app.config.from_pyfile("settings.py")
    

    类似于django项目中的策略,但在flask中还有更好的方案。

  3. (常用)导入类配置项

    ## settings.py
    class ConfigBase:
        DEBUG=False
        一些配置项。。。
    
    class DevConfig(ConfigBase):
    	DEBUG=True   # 开发环境中debug为true
        mysql=测试库   # 开发时用测试库
        
    class ProdConfig(ConfigBase):
    	mysql=上线库  # 实际上线用不一样的库配置
        
        
    ## 导入配置
    app.config.from_object('settings.DevConfig')
    
  4. 其他:

    # 通过环境变量配置
    app.config.from_envvar("环境变量名称")
    # 通过json文件配置
    app.config.from_json("json文件名称")
    # 通过字典格式配置
    app.config.from_mapping({'DEBUG': True})
    

    字典格式配置在一些大型公司中会用到,因为会建立大的配置中心,多台机器可能都使用这个配置中心的配置,启动时先朝配置中心发送一个请求,它会返回一个字典(验证通过时),方便我们做集群化部署等。

对于项目的配置,我们可以定义为,供给给项目启动后自动加载的不再变化的量。而这些配置可以是flask的内置配置字段,用于支持flask的web服务,也可以是其他模块的配置字段,如redis、mysql的连接地址等。

路由系统

写法及原理

flask配置路由的基础写法是装饰器:

@app.route('/')
def index():
    pass

也就是将index这个普通函数替换成了带路由的视图函数。按照有参装饰器的语法糖,index被替换为app.route('/')(index),查看route的源码,即被替换成了decorator(index)这个闭函数。

其内部最核心的就只执行了self.add_url_rule(rule, endpoint, f, **options),而self在这个语境中是flask的对象app。

所以flask路由的本质是app对象的add_url_rule完成路由的注册

add_url_rule的参数

rule 匹配的规则(路径,可能含转换器)
view_func  视图函数,即被装饰的函数
defaults = None  视图函数可能会需要一些额外的参数,通过defaults = {'k': 'v'}提供
 endpoint = None  路径的别名,名称,用于反向解析URL,反向解析用到一个函数url_for('endpoint名称')
 methods = None  允许的请求方式,如:["GET", "POST"]

strict_slashes = None  对URL最后的 / 符号是否严格要求
redirect_to = None  访问这个路由相当于重定向到redirect_to

转换器

路由中可能包含一些视图函数所需的参数,通过转换器对路由的某一部分进行动态匹配并传参给视图。

使用方式:

@app.route('/student/<int:pk>')

@app.route('/media/<path:path>')

转换器的格式为<转换器类型:匹配内容的变量名>

转换器类型有:

转换器 说明
default UnicodeConverter
string UnicodeConverter
any AnyConverter
path PathConverter
int IntegerConverter
float FloatConverter
uuid UUIDConverter

flask的CBV

# 基于类的视图,写法

from flask import Flask,request
from flask.views import View, MethodView  # View是MethodView的基类

app = Flask(__name__)

app.debug = True


# 视图类,继承MethodView,类中写跟请求方式同名的方法即可
class IndexView(MethodView):
    def get(self):
        print(request.method)
        return 'get 请求'

    def post(self):
        print(request.method)
        return 'post 请求'


app.add_url_rule('/index', endpoint='index', view_func=IndexView.as_view('index'))
if __name__ == '__main__':
    app.run()

与视图函数比较,只是app.add_url_rule添加路由时形参view_func传入的内容为类.as_view('别名'),视图类由于继承了MethodView,所以是执行了其内部的as_view方法,最终其实返回的还是一个函数的内存地址。

源码解析

as_view

def as_view(cls, name, *class_args, **class_kwargs):
    def view(**kwargs: t.Any) -> ft.ResponseReturnValue:
        # 本质是在执行self.dispatch_request,只是用了异步
        return current_app.ensure_sync(self.dispatch_request)(**kwargs)
    return view

dispatch_request

 def dispatch_request(self, **kwargs):
	# self是视图类的对象,这里反射拿到请求方式对应的视图函数
    meth = getattr(self, request.method.lower(), None)  # 如拿到self.get
    # 用异步执行meth()
    return current_app.ensure_sync(meth)(**kwargs)

add_url_rule的endpoint=None

-@app.route('/index')--》没有传endpoint
-endpoint 就是None---》调用了app.add_url_rule,传入了None
# 路径如果不传别名,别名就是函数名
if endpoint  None:
	endpoint = _endpoint_from_view_func(view_func)  # type: ignore
    -_endpoint_from_view_func -->return view_func.__name__

为什么要设置endpoint参数?

因为有时视图函数会先被其他装饰器装饰后再装饰路由,那么传入view_func的函数实际上都是其他装饰器的名字,很可能造成冲突,需要手动传入一个视图名。

as_view必传别名

as_view是一个包函数,其内部是一个闭函数view,add_url_rule会将endpoint都处理成view,那么就报错了,但是如果要求传入name,那么as_view内部会将view的name先替换掉view.__name__ = name,那么不同的视图类就不会冲突了。

MethodView继承View

View中实现了as_view方法,其中执行了self.dispatch_request,这个方法没有在View中实现,所以View是抽象类,它需要子类去实现这个方法,MethodView就实现了dispatch_request方法。

视图类加装饰器

实际上是给所有的类中的函数加装饰器。

class IndexView(MethodView):
    decorators = [auth,]
    def get(self):
        pass
        return 'get 请求'

    def post(self):
        pass
        return 'post 请求'
    
# as_view中从左到右装饰原函数  
if cls.decorators:
    view.__name__ = name
    view.__module__ = cls.__module__
    for decorator in cls.decorators:
        view = decorator(view)

jinjia2模板语法

与django的dtl语法相比,多了以下几条特性:

使用模板

flask项目中默认配置了模板的路径为templates,也可以通过参数template_folder更改

app = Flask(__name__, template_folder='templates', static_folder='static') 

函数可加括号传参

{{ add(4,5) }}

Markup(字符)取消校验

一般文本插值到html中,会被处理变成非html的文本,使用markup后直接按标签渲染。

自定义标签和过滤器

# template_global  在模板中直接使用该过滤器
@app.template_global()
def add(a1, a2):
    return a1 + a2

# template_filter
@app.template_filter()
def db(a1, a2, a3):
    return a1 + a2 + a3

ps:jinjia2这种前后端混合语法已经逐渐不再使用

请求与响应

请求属性

request的属性总结
request.method 提交的方法
request.args get请求提及的数据
request.form post请求提交的数据
request.values post和get提交的数据总和
request.cookies 客户端所带的cookie
request.headers 请求头
request.path 不带域名,请求路径
request.full_path 不带域名,带参数的请求路径
request.url 带域名带参数的请求路径
request.base_url 带域名请求路径
request.url_root 域名 http://127.0.0.1:5000/
request.host_url 域名 http://127.0.0.1:5000/
request.host 127.0.0.1:5000

响应类型

可以直接在视图函数中返回以下四种形式:

  • 字符串
  • redirect
  • render_template
  • jsonify

这些会被后续的程序处理为符合传输协议的response,也可以在视图函数中先通过make_response处理为response,然后我们对其返回的response对象中加响应头、cookie等。

session使用

先设置app.secret_key才能使用session

视图中使用session['键']=值可以存储,使用session.get('键')可以取到对应的值。

实际上session设置的键值被加密放在了cookie中存在浏览器,而请求过来时携带的cookie会被处理成session对象,可以取出键。这个过程被配置在app.session_interface的类中,分别由open_session,save_session方法实现。

闪现

简单理解为只用一次的session。

flash('%s,传入一个'%name),可以设置多次,放在列表中。

flash('超时错误',category="debug") 分类存 (可以按照一定的参数去存)

get_flashed_messages()取出所有的falsh并删除

实际上也是存到了session中。

ps:以上的内容很多都是前后端混合,了解即可

请求扩展

before_request

请求来了进行处理,如果返回的不是None,则会拦截请求并返回响应。

@app.before_request
def before():
    print('我来了111')
    
    
@app.before_request
def before():
    print('我来了222')

多个函数加装此装饰器,则请求来时按注册顺序执行

after_request

响应走时,进行处理,必须返回响应四件套。

@app.after_request
def after(response):
    print('我走了111')
    return response


@app.after_request
def after2(response):
    print('我走了222')
    return response

多个函数加装此装饰器,则请求走时按注册顺序反顺序执行

before_first_request

即将弃用,项目启动后的第一个请求来时做一些处理。

teardown_request

可以拿到视图抛出的异常,在这里做错误日志,而无论视图函数是否出错都会执行,只是e接收到异常与否。

@app.teardown_request
def teardown(e):
    print(e)
    print('执行我了')

errorhandler

监听响应状态码,如果符合监听的状态码,就会走它,return一个response对象,则会返给前端。

@app.errorhandler(404)
def error_404(arg):
    return "404错误了"

@app.errorhandler(500)
def error_500(arg):
    return "500错误了"
posted @ 2023-04-02 22:33  leethon  阅读(38)  评论(0编辑  收藏  举报