在真正开始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]):