在真正开始fastapi教程之前,我们需要补充一些内容
一,python 类型提示
Python 变量的类型由分配给它的值决定,每次分配时它都可能动态变化。
(一)为什么Python语言设计之初没有设计类型定义
Python在设计之初没有设计类型定义(也就是静态类型),主要是因为以下几个原因:
- 动态类型系统的灵活性
Python是一种动态类型的语言,这意味着变量的类型在运行时确定,而不是在编译时确定。这种设计具有以下优点:- 灵活性:开发者可以在不同的上下文中重新使用相同的变量,而不需要担心类型转换。这使得代码更简洁和可读。
- 简洁性:无需显式声明类型可以减少代码量,降低编码的复杂度,从而提高开发速度。
- 快速原型开发:动态类型系统非常适合快速原型开发和迭代,因为开发者不需要在代码的早期阶段花费时间在类型声明和类型检查上。
- 面向对象和脚本语言的特点
Python是一种面向对象和脚本语言,其设计目标之一是简化编程任务,特别是在快速开发和自动化脚本方面。动态类型系统与这种目标非常契合:- 灵活的对象模型:Python的对象模型允许开发者在运行时修改对象,这在静态类型系统中是不容易实现的。
易于使用:作为脚本语言,Python希望降低学习曲线和使用门槛,让非专业程序员也能轻松上手。
- 灵活的对象模型:Python的对象模型允许开发者在运行时修改对象,这在静态类型系统中是不容易实现的。
- 哲学和设计理念
Python的设计哲学强调简洁、明确和易读。其“Python之禅”(The Zen of Python)包含了以下几条与类型系统相关的理念:- 明确优于隐晦(Explicit is better than implicit):虽然动态类型系统中类型是隐含的,但其简洁性和灵活性通常更符合快速开发和易于阅读的要求。
- 拒绝过度设计(You aren’t gonna need it):在许多应用场景中,Python开发者不需要显式的类型声明,动态类型系统已经足够处理大多数任务。
- 类型检查器和运行时错误
在Python的发展早期,运行时类型检查和错误处理已经足够强大,可以捕获大多数类型错误。Python依赖于良好的测试和调试工具来确保代码质量,而不是依赖于编译时的类型检查。
(二)为什么后来Python又引入类型提示(type hints)
- 大型项目需求:随着Python在大型、复杂项目中的应用增多,开发者需要更好的工具来管理和维护代码。类型提示可以提高代码的可读性和可维护性。
- 错误预防:类型提示可以帮助在运行前捕获某些类型相关的错误,减少运行时错误。
- 代码文档:类型提示作为一种自文档化的方式,可以清晰地表明函数参数和返回值的预期类型。
- IDE支持:类型提示使得集成开发环境(IDE)能够提供更准确的代码补全和错误检查。
- 性能优化:虽然Python本身不会使用类型提示来优化代码,但一些第三方工具可以利用类型信息来生成优化的代码。
- 静态分析:类型提示使得静态代码分析工具能够更有效地工作,提高代码质量。
Python的类型提示被设计为可选功能,允许开发者在需要时逐步添加类型信息而不是强制所有代码都使用类型,不会破坏现有的Python代码,不影响Python作为动态语言的本质。主要用于开发工具和文档目的,而不是运行时的类型检查。这种方法平衡了动态语言的灵活性和静态类型检查的好处。
因为Python的类型提示的引入是一个不断补充与完善的过程,这里就以python3.11.2为例,简单说明如何使用类型提示
(三),如何使用类型提示
1,基本类型提示
对于基本的内置类型,如 int、float、str 和 bool,可以直接在函数定义中使用类型提示。
def add(x: int, y: int) -> int:
return x + y
def greet(name: str) -> str:
return f"Hello, {name}!"
# 示例
result = add(5, 3)
greeting = greet("Alice")
print(result) # 输出: 8
print(greeting) # 输出: Hello, Alice!
在这个示例中,add 函数接受两个整数参数,并返回一个整数。greet 函数接受一个字符串参数,并返回一个字符串。
2,容器类型
对于原生的容器 list, dict, tuple, set,从 Python 3.9 开始直接支持使用原生的容器类型进行类型提示,而不需要从 typing 模块中导入。
def process_list(values: list[int]) -> list[int]:
return [value * 2 for value in values]
def process_dict(data: dict[str, int]) -> int:
return sum(data.values())
def process_tuple(items: tuple[int, str]) -> str:
return f"Number: {items[0]}, Text: {items[1]}"
def process_set(items: set[int]) -> set[int]:
return {item * 2 for item in items}
list_result = process_list([1, 2, 3])
dict_result = process_dict({"a": 1, "b": 2})
tuple_result = process_tuple((10, "hello"))
set_result = process_set({1, 2, 3})
print(list_result) # 输出: [2, 4, 6]
print(dict_result) # 输出: 3
print(tuple_result) # 输出: Number: 10, Text: hello
print(set_result) # 输出: {2, 4, 6}
这些函数分别处理列表、字典、元组和集合,每个函数都带有相应的类型提示。
3,可选类型和联合类型
有些变量或函数返回值可以是某种类型或 None
,可以使用 Optional
:
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
return None if user_id == 0 else "User"
在上述示例中,find_user 函数的返回值可以是 str
类型或 None
。
当变量或参数可以是多种类型中的一种时,可以使用 Union
:
from typing import Union
def process_data(data: Union[int, str]) -> str:
if isinstance(data, int):
return f"Number: {data}"
else:
return f"String: {data}"
在上述示例中,process_data 函数的参数 data 可以是 int
类型或 str
类型。
Python 3.10 引入了新的语法 |
来表示联合类型,可以替代 Union:
def process_data(data: int | str) -> str:
if isinstance(data, int):
return f"Number: {data}"
else:
return f"String: {data}"
4,自定义类型
使用 NewType 可以定义新的类型,基于现有类型但在类型检查时被视为不同类型。
from typing import NewType
UserId = NewType('UserId', int)
ProductId = NewType('ProductId', int)
def get_user_name(user_id: UserId) -> str:
return "Alice"
def get_product_name(product_id: ProductId) -> str:
return "ProductA"
user_id = UserId(123)
product_id = ProductId(456)
user_name = get_user_name(user_id)
product_name = get_product_name(product_id)
print(user_name) # 输出: Alice
print(product_name) # 输出: ProductA
在这个示例中,UserId 和 ProductId 是基于 int 类型创建的新类型。user_id 被赋值为 UserId(123),product_id 被赋值为 ProductId(456)。
尽管 UserId 和 ProductId 在类型检查时被视为独有类型,但在运行时,它们实际上仍然是 int 类型。
print(user_id) # 输出: 123
print(type(user_id)) # 输出: <class 'int'>
5,任意类型提示
在 Python 类型提示系统中,Any
类型表示任意类型,即可以是任何类型的值。使用 Any
可以让你在类型检查时表示某个变量可以是任何类型,而不受类型检查的限制。
from typing import Any
def log_message(message: Any) -> None:
print(message)
result1 = process_data(42)
result2 = process_data("hello")
result3 = process_data([1, 2, 3])
print(result1) # 输出: Processed data: 42
print(result2) # 输出: Processed data: hello
print(result3) # 输出: Processed data: [1, 2, 3]
在上述示例中,log_message 函数的参数 message 可以是任意类型。
Any
的适用场景
- 与动态类型交互:当你需要与一些动态类型系统(如 JSON 数据、未类型化的数据库数据、外部 API 返回的数据)交互时,可以使用 Any。
- 渐进式类型检查:在逐步为代码添加类型注释时,可以临时使用 Any,然后逐步替换为更具体的类型。
- 通用容器:在实现一些通用的容器或数据结构时,使用 Any 可以使代码更加灵活。
为了确保代码的类型安全性和可读性,应该尽量避免过度使用 Any
类型,优先考虑具体类型。
6,泛型
泛型(Generic)是编程中的一个重要概念,它允许编写可以处理多种类型的代码,而不需要在代码中显式指定这些类型。
在 Python 中,泛型主要通过 TypeVar
和 Generic
来实现。
-
TypeVar 用于定义一个类型变量
from typing import TypeVar T = TypeVar('T') # 定义一个类型变量 T def identity(value: T) -> T: return value print(identity(1)) # 1 print(identity('hello')) # hello print(identity([1, 2, 3])) # [1, 2, 3] print(identity({'name': 'Alice', 'age': 20})) # {'name': 'Alice', 'age': 20}
-
Generic 用于定义一个泛型类或泛型函数
from typing import TypeVar, Generic T = TypeVar('T') class Box(Generic[T]): def __init__(self, content: T) -> None: self.content = content def get_content(self) -> T: return self.content int_box = Box(123) str_box = Box("Hello") print(int_box.get_content()) # 输出: 123 print(str_box.get_content()) # 输出: Hello
你可以定义多个类型变量,以处理更加复杂的泛型需求:
from typing import TypeVar, Generic
T = TypeVar('T')
U = TypeVar('U')
class Pair(Generic[T, U]):
def __init__(self, first: T, second: U) -> None:
self.first = first
self.second = second
def get_first(self) -> T:
return self.first
def get_second(self) -> U:
return self.second
pair = Pair(1, "apple")
print(pair.get_first()) # 输出: 1
print(pair.get_second()) # 输出: apple
你可以对类型变量添加约束,限制它们可以是某些特定类型的子类:
from typing import TypeVar
T = TypeVar('T', bound='Comparable')
class Comparable:
def compare(self, other: 'Comparable') -> int:
pass
class Number(Comparable):
def __init__(self, value: int) -> None:
self.value = value
def compare(self, other: 'Number') -> int:
return self.value - other.value
n1 = Number(10)
n2 = Number(20)
print(n1.compare(n2)) # 输出: -10
二,异步
(一)CPU密集型任务和IO密集型任务
-
CPU密集型任务主要消耗计算机的处理器资源。这类任务需要进行大量的计算,而很少等待I/O操作。
a) 科学计算:如复杂的数学模型计算、气象模拟
b) 图形渲染:3D动画渲染、视频编码
c) 密码破解:尝试大量可能的密钥组合
d) 机器学习算法:训练深度学习模型
e) 大数据分析:处理和分析大量数据集# 判断一个数是否是素数 def is_prime(n): if n < 2: return False for i in range(2, int(n ** 0.5) + 1): if n % i == 0: return False return True # 计算素数的个数 def count_primes(start, end): return sum(1 for n in range(start, end) if is_prime(n)) # 这是一个CPU密集型操作 result = count_primes(2, 1000000) print(f"Number of primes: {result}")
-
IO密集型任务主要涉及输入/输出操作,如读写文件、网络通信等。这类任务花费大量时间等待I/O操作完成,而非执行计算。
a) 文件操作:读写大文件、日志处理
b) 网络请求:web爬虫、API调用
c) 数据库操作:查询、插入、更新大量记录
d) 消息队列处理:接收和发送大量消息
e) 文本处理:解析大型XML或JSON文件import aiohttp import asyncio # 定义一个异步函数,用于获取指定URL的内容 async def fetch_url(session, url): async with session.get(url) as response: return await response.text() # 定义一个主函数,用于发起多个异步请求 async def main(): urls = [ "http://example.com", "http://example.org", "http://example.net", ] * 100 # 重复请求以模拟大量IO操作 async with aiohttp.ClientSession() as session: tasks = [fetch_url(session, url) for url in urls] results = await asyncio.gather(*tasks) print(f"Fetched {len(results)} pages") asyncio.run(main())
优化策略差异.
CPU密集型任务:
- 使用多进程来利用多核CPU
- 优化算法和数据结构
- 使用编译型语言或优化的库(如NumPy)
- 考虑GPU加速(如CUDA)
IO密集型任务:
- 使用异步编程(如asyncio)
- 多线程处理
- 使用非阻塞I/O
- 实现缓存机制
- 优化数据库查询
(二)并发与并行
1,并发是指多个任务在重叠的时间段内启动、运行和完成的能力。它不一定意味着多个任务同时执行,而是指任务可以在重叠的时间段内进行切换。
2,并行是指多个任务或多个任务的部分在同一时刻在不同的处理单元上同时执行。
并发vs并行的关键区别:
-
执行方式:
并发:任务可能在重叠的时间段内交替执行
并行:任务在同一时刻真正同时执行 -
硬件要求:
并发:可以在单核处理器上实现
并行:需要多核处理器或多台机器 -
主要目标:
并发:提高程序的响应性和资源利用率
并行:提高处理速度和吞吐量 -
实现复杂度:
并发:通常更复杂,需要处理同步和资源共享问题
并行:实现可能更直接,但需要考虑任务分解和结果合并 -
适用场景:
并发:I/O密集型任务,如网络服务器
并行:CPU密集型任务,如科学计算
(三)同步与异步
-
在同步操作中,任务按照顺序执行,每个任务开始之前必须等待前一个任务完成。
- 传统的文件读写操作
- 同步 HTTP 请求
- 数据库查询操作
import time def task(name, duration): print(f"Starting {name}") time.sleep(duration) # 模拟耗时操作 print(f"Finished {name}") def main(): task("Task 1", 2) task("Task 2", 3) task("Task 3", 1) start = time.time() main() end = time.time() print(f"Total time: {end - start:.2f} seconds")
-
在异步操作中,任务可以独立启动,不需要等待其他任务完成。程序可以在等待某个操作完成时继续执行其他任务。
- 异步文件 I/O
- 非阻塞网络操作
- 事件驱动的 GUI 程序
- 异步数据库查询
import asyncio import time async def task(name, duration): print(f"Starting {name}") await asyncio.sleep(duration) # 模拟异步操作 print(f"Finished {name}") async def main(): tasks = [ task("Task 1", 2), task("Task 2", 3), task("Task 3", 1) ] await asyncio.gather(*tasks) start = time.time() asyncio.run(main()) end = time.time() print(f"Total time: {end - start:.2f} seconds")
(四)python 异步编程
Python在异步编程方面的发展是一个渐进的过程,反映了语言和社区需求的演变。让我们按时间顺序回顾Python异步编程的主要里程碑,并举例说明每个阶段的特点。
1,传统的多线程和多进程 (Python早期)
Python最初通过threading和multiprocessing模块支持并发。
import threading
def worker(num):
print(f'Worker: {num}')
threads = []
for i in range(5):
# 创建线程
t = threading.Thread(target=worker, args=(i,)) # 目标函数,传入函数的参数
# 就绪线程
threads.append(t)
# 启动线程
t.start()
for t in threads:
t.join()
2,基于生成器的协程 (Python 2.5, 2006年)
引入yield语句,为后续的协程实现奠定基础。
- yield 用于定义生成器函数。
- 当函数中包含 yield 语句时,调用该函数会返回一个生成器对象。
- 每次调用生成器的 next() 方法(或使用 for 循环)时,函数会执行到 yield 语句,返回 yield 后的值,并暂停执行。
- 下次调用 next() 时,函数会从上次暂停的地方继续执行。
def infinite_sequence():
num = 0
while True:
yield num
num += 1
gen = infinite_sequence()
for i in range(5):
print(next(gen)) # 输出: 0, 1, 2, 3, 4
协程可以暂停执行并稍后恢复,还可以接收外部发送的值。
def simple_coroutine():
print("-> coroutine started")
x = yield
print('-> coroutine received:', x)
my_coro = simple_coroutine()
next(my_coro) # 启动协程
my_coro.send(42) # 发送值到协程
yield
的引入大大增强了 Python 处理迭代和异步编程的能力,为后续的异步编程模型(如 asyncio)奠定了基础。它允许开发者以一种更自然、更高效的方式处理序列和数据流,成为 Python 中实现生成器和协程的核心机制。
3,Twisted和Tornado框架 (2002年和2009年)
这些第三方框架引入了基于事件循环的异步编程模型。
import tornado.ioloop
import tornado.web
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("Hello, World!")
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.current().start()
4,yield from语法 (Python 3.3, 2012年)
简化了生成器的使用,为asyncio库铺平道路。
def generator1():
yield from range(3)
def generator2():
yield from generator1()
for item in generator2():
print(item)
5,asyncio库 (Python 3.4, 2014年)
引入了标准库级别的异步编程支持。
import asyncio
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1, 'hello')
await say_after(2, 'world')
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
6,async/await语法 (Python 3.5, 2015年)
引入了更直观的异步编程语法。
import asyncio
async def fetch_data():
print('start fetching')
await asyncio.sleep(2)
print('done fetching')
return {'data': 1}
async def print_numbers():
for i in range(10):
print(i)
await asyncio.sleep(0.25)
async def main():
task1 = asyncio.create_task(fetch_data())
task2 = asyncio.create_task(print_numbers())
value = await task1
print(value)
await task2
asyncio.run(main())
7,异步上下文管理器和异步迭代器 (Python 3.6, 2016年)
进一步扩展了异步编程的能力。
import asyncio
class AsyncContextManager:
async def __aenter__(self):
print('entering context')
await asyncio.sleep(1)
return self
async def __aexit__(self, exc_type, exc, tb):
print('exiting context')
await asyncio.sleep(1)
async def main():
async with AsyncContextManager() as manager:
print('inside context')
asyncio.run(main())
8,asyncio改进 (Python 3.7+, 2018年至今)
包括添加asyncio.run(),改进事件循环实现,以及其他性能优化。
import asyncio
async def main():
print('Hello')
await asyncio.sleep(1)
print('World')
# 简化的运行方式
asyncio.run(main())
9,异步压缩语法 (Python 3.9, 2020年)
引入了异步列表、集合和字典推导式。
import asyncio
async def double(x):
await asyncio.sleep(0.1) # 模拟异步操作
return x * 2
async def main():
result = [await double(i) for i in range(5)]
print(result)
asyncio.run(main())
这个演进过程展示了Python如何从基本的多线程支持发展到全面的异步编程模型。
每一步都使异步编程变得更加强大和易用,同时保持了Python简洁清晰的语法风格。
现代Python的异步能力使其能够高效处理大量并发I/O操作,特别适合网络应用、Web开发等场景。
三,关于 REST 架构
(一)API
Web API(应用程序编程接口)是一种允许不同软件系统之间通过网络进行通信和数据交换的接口。它定义了一组可以通过HTTP协议访问的端点(endpoints),使得客户端应用能够与服务器端资源进行交互。
Web API 是一种基于网络的接口,允许应用程序、服务或系统之间进行通信和数据交换。
1,设计它的主要目的是:
- 提供数据访问和操作的标准化方法
- 实现系统间的集成和互操作性
- 支持分布式应用程序的开发
2,关键特征:
- 基于HTTP:大多数Web API使用HTTP协议进行通信
- 数据格式:通常使用JSON或XML格式传输数据
- 无状态:每个请求都应包含处理该请求所需的所有信息
- 可扩展:设计良好的API可以轻松扩展和版本控制
3,Web API通常使用以下HTTP方法来执行不同的操作:
- GET:获取资源
- POST:创建新资源
- PUT:更新现有资源
- DELETE:删除资源
- PATCH:部分更新资源
4,API设计风格:
- RESTful API:遵循REST(表述性状态转移)架构原则
- GraphQL:允许客户端精确指定所需的数据
- SOAP:使用XML进行消息传递的协议
5,Web API 安全性是一个重要考虑因素,常见的安全措施包括:
- 身份验证:确保只有授权用户可以访问API
- 授权:控制用户可以执行的操作
- HTTPS:加密数据传输
- 速率限制:防止API滥用
6,随着API的发展,版本控制变得非常重要。常见的版本控制策略包括:
- URL版本控制:例如 /api/v1/users
- 请求头版本控制:使用自定义请求头指定版本
7,API测试是确保API可靠性和一致性的关键。常见的测试类型包括:
- 单元测试
- 集成测试
- 负载测试
- 安全测试
(二)RESTful风格
RESTful(Representational State Transfer)是一种用于设计网络应用程序的架构风格,特别是用于设计Web服务。它由Roy Fielding在2000年的博士论文中提出。RESTful API 遵循 REST 架构的原则,旨在创建可扩展、易于理解和维护的 Web 服务。
多年来,REST架构已经成为构建 API 的事实标准。尽管还有其他方法可用于开发 API。
1,资源(Resources)
在 RESTful 架构中,一切都被视为资源。
- 资源可以是任何可被命名的事物,如用户、文章、评论等。
- 每个资源都有一个唯一的标识符(通常是 URL)。
/users (用户集合)
/users/123 (ID 为 123 的特定用户)
/articles/456 (ID 为 456 的特定文章)
2,表现层(Representations)
资源的表现形式是资源数据的特定表示方式。
- 常见的表现形式包括 JSON、XML、HTML 等。
- 客户端可以请求特定的表现形式(通过 HTTP 头部)。
3,状态转移(State Transfer)
客户端通过对资源的表现形式进行操作来改变资源状态。
- 服务器不保存客户端状态(无状态)。
4,统一接口(Uniform Interface)
RESTful API 应该有一个统一的接口,包括以下约束:
- 资源识别:使用 URI 唯一标识资源
- 通过表现层操作资源:客户端使用表现层来修改或获取资源
- 自描述消息:每个消息包含足够的信息来描述如何处理消息
- 超媒体作为应用状态引擎(HATEOAS):客户端通过服务器提供的链接来动态发现可用的操作
5,HTTP 方法
GET /api/users # 获取所有用户
GET /api/users/123 # 获取 ID 为 123 的用户
POST /api/users # 创建新用户
PUT /api/users/123 # 更新 ID 为 123 的用户
DELETE /api/users/123 # 删除 ID 为 123 的用户
GET /api/users/123/posts # 获取用户 123 的所有帖子
6,无状态性(Stateless)
- 每个请求都包含处理该请求所需的所有信息。
- 服务器不应存储与客户端会话相关的任何状态。
7,可缓存性(Cacheable)
响应应该明确标明是否可以被缓存。
8,客户端-服务器架构
客户端和服务器是分离的,允许它们独立演化。
9,分层系统(Layered System)
允许架构由多个层次组成,每个层次只知道与它直接交互的层。
10,注意事项
- 正确使用 HTTP 状态码
- 版本控制(例如 /api/v1/users)
- 适当的错误处理和消息
- 考虑安全性(如身份验证和授权)
- 遵循一致的命名约定
四,HTTP 方法
统一接口原则指出,请求消息必须自给自足,应包含处理请求所需的一切:
- 资源的 URI
- 对其采取的操作
- 完成操作需要的一些额外数据
请求中的操作部分由 HTTP 方法表示。
最常用的 HTTP 方法是 POST、GET、PUT 和 DELETE。这些方法对应于服务器资源上的创建、读取、更新和删除操作。这些操作通常以缩写形式 CRUD 为人所知。
除了上述方法外,HTTP 请求类型还可以是其他一些类型(如 PATCH、HEAD、OPTIONS 等)。然而,它们不太常见,尤其是在 REST 架构的背景下。
因此,我们可以推断,客户端发送 POST、GET、PUT 或 DELETE 类型的 HTTP 请求,以对服务器上的某个资源执行 CRUD 操作。此请求被按照前面解释的约束设计的 REST API 拦截,并转发到服务器进行处理。反过来,响应将返回给客户端以供其进一步使用。
HTTP 中常用请求方法的使用场景和限制:
- GET 方法
使用场景:
- 获取资源信息
- 执行无副作用的操作
- 检索数据,如搜索结果
限制:
- 不应用于修改服务器上的数据
- URL 长度有限制,不适合传输大量数据
- 数据暴露在 URL 中,不适合传输敏感信息
- POST 方法
使用场景:
- 创建新资源
- 提交表单数据
- 上传文件
- 执行可能改变服务器状态的操作
限制:
- 非幂等,多次相同请求可能产生多个资源
- 不适合用于纯粹的数据检索操作
- PUT 方法
使用场景:
- 更新已存在的资源
- 替换整个资源
限制:
- 需要知道资源的确切位置(URL)
- 通常要求客户端提供完整的资源表示
- DELETE 方法
使用场景:
- 删除指定的资源
限制:
- 操作不可逆,需要谨慎使用
- 有些系统可能只标记为删除而不是真正删除
- PATCH 方法
使用场景:
- 部分更新资源
限制:
- 不是所有服务器都支持
- 需要特定的数据格式来指示更新内容
- HEAD 方法
使用场景:
- 获取与 GET 请求相同的响应头,但没有响应体
- 检查资源是否存在
- 获取资源元数据
限制:
- 不返回响应体,仅用于获取头信息
- OPTIONS 方法
使用场景:
- 获取服务器支持的 HTTP 方法
- 用于 CORS 预检请求
限制:
- 主要用于信息获取,不用于修改资源
- TRACE 方法
使用场景:
- 诊断,用于回显服务器收到的请求
限制:
- 可能暴露敏感信息,通常在生产环境中禁用
- 主要用于调试目的
通用限制:
- 安全性:GET、HEAD、OPTIONS 通常被认为是安全的,而其他方法可能改变服务器状态。
- 幂等性:GET、HEAD、PUT、DELETE、OPTIONS、TRACE 是幂等的,而 POST 和 PATCH 不是。
- 缓存:GET 请求通常可以被缓存,而其他方法通常不被缓存。
- 对于所有方法,服务器都可能因为权限、资源不存在等原因拒绝请求。
在实际应用中,选择合适的 HTTP 方法需要考虑操作的语义、安全性要求、幂等性需求以及 RESTful 设计原则。
五,fastapi 的依赖项
(一)Starlette
Starlette 是 FastAPI 的核心基础框架,提供了 Web 应用的基本功能。
1,Starlette 简介
Starlette 的主要特点:
轻量级和高性能:基于 ASGI,支持异步处理。
简洁的 API:易于学习和使用。
WebSocket 支持:轻松处理实时通信。
中间件支持:可以方便地添加横切关注点。
后台任务:支持异步后台任务处理。
静态文件服务:内置静态文件处理。
模板支持:集成模板渲染功能。
测试客户端:便于进行集成测试。
完全类型注解:提供良好的 IDE 支持。
兼容性:可以与其他 ASGI 框架和服务器(如 Uvicorn)无缝集成。
FastAPI 构建在 Starlette 之上,继承了其高性能特性和许多核心功能。FastAPI 扩展了 Starlette,增加了数据验证、序列化和文档生成等特性。
2,Starlette 简单教程
(1)基本路由
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.responses import JSONResponse
import uvicorn
async def homepage(request):
return JSONResponse({"message": "Welcome to Starlette!"})
routes = [
Route("/", homepage)
]
app = Starlette(routes=routes)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
(2)中间件
import uvicorn
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.responses import JSONResponse
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
async def homepage(request):
return JSONResponse({"message": "Welcome to Starlette!"})
routes = [
Route("/", homepage)
]
middleware = [
Middleware(CORSMiddleware, allow_origins=['*'])
]
app = Starlette(routes=routes, middleware=middleware)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
(3)WebSocket 支持
import uvicorn
from starlette.applications import Starlette
from starlette.routing import Route, WebSocketRoute
from starlette.websockets import WebSocket
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message received: {data}")
routes = [
WebSocketRoute("/ws", websocket_endpoint)
]
app = Starlette(routes=routes)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
(4)异步请求和后台任务
import asyncio
import uvicorn
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from starlette.background import BackgroundTasks
async def send_email(email: str, message: str):
# 模拟发送邮件的异步操作
await asyncio.sleep(5)
print(f"Email sent to {email}: {message}")
async def email_signup(request):
data = await request.json()
tasks = BackgroundTasks()
tasks.add_task(send_email, data['email'], "Thanks for signing up!")
return JSONResponse(
{"message": "Signup successful, email will be sent shortly."},
background=tasks
)
routes = [
(Route("/signup", email_signup, methods=["POST"]))
]
app = Starlette(debug=True, routes=routes)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
(5)静态文件服务
import uvicorn
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.responses import JSONResponse
from starlette.staticfiles import StaticFiles
async def homepage(request):
return JSONResponse({"message": "Welcome to Starlette!"})
routes = [
Route("/", homepage)
]
app = Starlette(routes=routes)
app.mount("/static", StaticFiles(directory="static"), name="static")
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
(6)模板渲染
import uvicorn
from starlette.applications import Starlette
from starlette.routing import Route
from starlette.templating import Jinja2Templates
templates = Jinja2Templates(directory="templates")
async def template_view(request):
return templates.TemplateResponse("index.html", {"request": request, "name": "Starlette"})
routes = [
Route("/template", template_view)
]
app = Starlette(routes=routes)
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
(7)测试客户端
from starlette.testclient import TestClient
def test_homepage():
client = TestClient(app)
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Welcome to Starlette!"}
(二)Pydantic
1,Pydantic 简介
Pydantic 是 FastAPI 用于数据验证和设置管理的库。
Pydantic 的主要特点:
- 基于类型注解的验证:利用 Python 的类型提示进行数据验证。
- 自动类型转换:自动将输入数据转换为指定的类型。
- 嵌套模型支持:可以定义复杂的嵌套数据结构。
- 自定义验证器:可以添加自定义的验证逻辑。
- 易于使用的 API:简洁的语法,易于集成到现有项目中。
- 高性能:核心验证逻辑用 Rust 实现,性能出色。
- 与 IDE 集成:提供良好的类型提示和自动完成支持。
- JSON Schema 生成:可以自动生成 JSON Schema。
Pydantic 通过结合 Python 的类型注解和强大的验证功能,使得数据处理变得简单而可靠。
在 FastAPI 中:
- 定义请求和响应模型
- 自动验证请求数据
- 生成 API 文档(结合 OpenAPI)
- 类型检查和自动完成支持
2,Pydantic 简单教程
(1)基本模型定义
from pydantic import BaseModel, Field
from typing import List, Optional
# 创建用户模型
class User(BaseModel):
id: int
name: str
email: str
age: Optional[int] = None
tags: List[str] = []
score: float = Field(..., ge=0, le=100)
# 创建用户实例
user = User(id=1, name="Alice", email="alice@example.com", score=85.5)
print(user)
(2)数据验证
# 2,数据验证
try:
invalid_user = User(id="not an int", name="Bob", email="invalid", score=101)
except ValueError as e:
print(e)
# 输出多个验证错误
(3)类型转换
# 3,数据转换
user = User(id="123", name="Charlie", email="charlie@example.com", score="90.5")
print(user.id, type(user.id)) # 输出: 123 <class 'int'>
print(user.score, type(user.score)) # 输出: 90.5 <class 'float'>
(4)嵌套模型
# 4,嵌套模型
class Address(BaseModel):
street: str
city: str
class AdvancedUser(BaseModel):
user: User
address: Address
advanced_user = AdvancedUser(
user=User(id=2, name="David", email="david@example.com", score=75),
address=Address(street="123 Main St", city="Anytown")
)
print(advanced_user)
(5)导出数据
# 5,导出数据
user = User(id="123", name="Charlie", email="charlie@example.com", score="90.5")
print(user.dict()) # 转换为字典
print(user.json()) # 转换为 JSON 字符串
(6)自定义验证器
# 6,自定义验证器
class ValidatedUser(BaseModel):
name: str
password: str
@field_validator('password')
def password_strength(cls, v):
if len(v) < 8:
raise ValueError('密码必须至少8个字符')
return v
# 使用自定义验证器
try:
user = ValidatedUser(name="Eve", password="weak")
except ValueError as e:
print(e) # 输出: 密码必须至少8个字符
(三)Uvicorn
1,WSGI 与 ASGI
WSGI(Web Server Gateway Interface)是一种用于 Python 应用和 Web 服务器之间的接口标准。它定义了一个简单的通用接口,使得 Python Web 应用能够在各种 Web 服务器上运行,促进了应用和服务器之间的兼容性和互操作性。
ASGI 是 WSGI 的异步版本,为异步 Web 服务器和应用程序提供了一个标准接口。
特点:
- 异步处理:可以同时处理多个请求。
- 支持 WebSocket、HTTP/2 等长连接协议。
- 向后兼容 WSGI。
- 专为 Python 3 设计。
- 支持生命周期协议。
+----------------------+ +-----------------+ +---------------------+
| | | | | |
| Client (Browser) +----------------> Web Server +-------------------> WSGI/ASGI Server |
| | HTTP Request| (Nginx) | HTTP Request | (Gunicorn, uWSGI, |
| | | Handles Static | | Uvicorn, Daphne, |
| | | Content & | | etc.) |
+----------------------+ | Proxy Pass | | |
+-----------------+ +---------------------+
|
+-----------------+--------------------+
| |
v v
+-------------------+ +---------------------+
| | | |
| WSGI Servers | | ASGI Servers |
| | | |
| - Gunicorn | | - Uvicorn |
| - uWSGI | | - Daphne |
| | | |
+-------------------+ +---------------------+
| |
v v
+-------------------+ +---------------------+
| | | |
| Python Web | | Python Web |
| Frameworks | | Frameworks |
| (WSGI-based) | | (ASGI-based) |
| | | |
| - Django | | - FastAPI |
| - Flask | | - Starlette |
| - Pyramid | | - Quart |
| | | |
+-------------------+ +---------------------+
| |
v v
+-------------------+ +---------------------+
| | | |
| Application | | Application |
| Logic | | Logic |
| | | |
+-------------------+ +---------------------+
WSGI
ASGI Documentation
PEP 3333 – Python Web Server Gateway Interface v1.0.1
Python/WSGI应用快速入门
花了两个星期,我终于把 WSGI 整明白了
2,Uvicorn
Uvicorn 是一个快速的 ASGI 服务器,用于运行 FastAPI 应用。
主要特性:
- 支持 HTTP/1.1 和 WebSockets
- 支持异步框架(如 FastAPI 和 Starlette)
- 使用 uvloop 和 httptools 实现高性能
- 自动重载支持(开发模式)
- 支持 SSL/TLS
在 FastAPI 中:
- 作为推荐的生产级 ASGI 服务器运行 FastAPI 应用
- 在开发过程中提供快速的重载功能
这种组合允许 FastAPI 提供高性能、易用性和强大的类型检查,同时保持代码简洁和直观。每个组件都专注于特定的任务,共同构成了 FastAPI 的强大生态系统。