FastAPI第一天
这个专栏开始之前,我想先说说在这个专栏我想记录的到底是什么?或者说我们一起学习什么东西?
作为一个喜欢并且经常弄一些机器学习、深度学习的学生,在平时我也喜欢学习一些web开发相关的知识,特别是一些火热/新的web框架,我都想上手试一试,并且把一些项目部署到web。简单点说,我希望将一些机器学习的项目上线,当然我希望使用到的技术尽量是新的,可以给人一些帮助的。所以我放弃了之前的Flask而是和大家一起学习新的python框架---FastAPI来帮助我搭建整个项目。
我希望在这个专栏一起学习框架的使用,将一些简单的项目和框架一步步结合。其实经过我目前的大致学习,我对于后端部分其实不太担心,但是对于前端部分我由于各种因素导致Vue学习时断时续,确实学的不好,所以肯定会有不好的地方请见谅。
1.环境配置
在说环境配置之前,我想先说说一些最基础的东西。FastAPI作为一个异步Python web开发框架,在性能上是远超以前的Flask或者Django的(不使用gevent情况下),但是实际上目前Python的异步开源环境是否完善我们还不得而知,当我们使用FastAPI搭配第三方包时,如果第三方的包仍然是串行,那还是会影响整个项目的效率,因此使用这个框架与否还是要先思考整个项目的环境是否适配。
另一个重要的点就是FastAPI由于异步,所以必然会用到Python中新的async/await关键字,所以项目的Python版本也有一定要求,建议Python 3.7+
,同时为了更简单了解有关协程这些新特性,建议先去官网看看基本用法,因为下面我直接跳过基础用法开始讲框架。
正式开始搭建环境,命令行输入
pip install fastapi[all]
注意一个地方,如果是mac,可能会出现not match,这个时候运行命令换成
pip install fastapi\[all\]
运行这个命令我们就不需要单独再去安装uvicorn或者python-multipart……
2.“Hello World”
对于程序猿不管做什么,只要环境配置好,都想先运行一次Hello World,确实令人心潮澎湃。所以话不多说,参照官网来吧。
from fastapi import FastAPI app=FastAPI() @app.get("/") async def index(): return {"message":"Hello world!"} 复制代码
在命令行运行uvicorn demo1:app --reload
,demo1是我的py文件名,app是FastAPI()的实例化对象,--reload热启动,可以保证我们修改源码之后不用重启服务就能刷新后在前端网页看到更改结果。
这个其实和Flask十分相似,但是最令人激动的是自带接口文档功能。访问/docs就可以看到如下图
不仅能看到接口参数,还可以做一些简单测试,实在是太方便了。
再来看看细节问题,app是一个实例化对象,但是FastAPI到底是什么呢?
继承自Starlette,难怪说基于。并且在FastAPI中我们还得习惯使用Python之前很少用的类型声明,在参数后面: 类型
,并且从源码中可以看到openapi_url,也就是说FastAPI将我们定义的API经过OpenAPI标准转化为了模式,也就是一种抽象,从而支持到内置的两种交互式文档。
一般第一天的内容差不多就是环境配置和一个基础例子,但是为了更快开始项目得早点过完FastAPI基础内容,所以下面继续。
3.参数问题
之前我提到过RESTful API,这就得和路由参数挂钩,这也就是这一部分重要的原因,还是先从简单的例子开始
from fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id): return {"item_id": item_id}
这个item_id可以填入任何数据,但是我上面说过,使用FastAPI就得习惯在Python中加入类型定义,如果我在上面代码加入定义,修改为下面这样
from fastapi import FastAPI app = FastAPI() @app.get("/items/{item_id}") async def read_item(item_id:int): return {"item_id": item_id} 复制代码
仅仅将item_id定义为int类型,再执行一次试试
依然可以执行,但是我们要知道路由中的111其实是字符串类型,也就是说只要我们定义了参数类型之后,FastAPI会帮助我们自动进行类型转换。但是如果无法转换呢?
这个时候报错也很明显,值不是一个integer,也不能转换为整型,那就只能报错了。
所以加入参数类型定义最大的好处就是方便排错,并且防止了很多异常的产生,并且还可以使框架自动帮助我们做一些类型转换,这里的数据校验都是Pydantic隐式完成的。
当然,我们还要注意顺序问题,在我们定义多个路由时,要避免前一个路由会干扰后一个路由,直接看看官网给出的例子
从源码来看也很容易看出来
每一个后面的route直接append进去,所以如果前面的route会影响后面的route,那么后面的route参数匹配就会失效出错。
对于包含路径的路由参数,我们可以添加path声明,表示匹配任意的路径
from fastapi import FastAPI app = FastAPI() @app.get("/files/{file_path:path}") async def read_file(file_path: str): return {"file_path": file_path} 复制代码
4.查询参数
当我们的参数不属于路由参数,而是其他函数参数那就是查询参数,比如下面这个
search很明显是一个路由参数,代表着查询搜索功能;但是?后面接的q=python&就属于查询参数,表示搜索的关键字等等信息。
让我们想想这个问题,假设我们已经实现了search的后端功能,但是这个查询参数我们怎么解决。
- 首先这个查询参数是可选的,我们可以搜索关键字,也可以不输入关键字
- 查询参数默认值为空
那代码可以这样写
from typing import Optional from fastapi import FastAPI app=FastAPI() @app.get("/search") def search(q:Optional[str]=None): if q: return {f"搜索关键字{q}的网页"} return {'当前网页'} 复制代码
这只是一个简单事例,其中重要的就是typing中的Optional实现可选参数。当然我们也可以设置必须查询参数,只需要不设置默认值就行,也就不用添加Optional。