1. 介绍
类型标注(Type Hinting)是 Python 3.5 引入的一项功能,它允许你在代码中指定变量、函数参数和返回值的预期类型。类型标注不会在运行时强制执行类型检查(除非你使用像 mypy
这样的静态类型检查工具),但它有以下几个重要的作用:
- 提高代码可读性: 类型标注使代码更加清晰易懂,减少了阅读代码时需要猜测变量类型的成本。
- 提高代码可维护性: 类型标注有助于及早发现类型错误,减少运行时错误,并方便代码重构。
- 支持静态类型检查: 使用
mypy
等工具可以对代码进行静态类型检查,在代码运行前发现潜在的类型错误。 - 改进 IDE 和编辑器的支持: 许多 IDE 和编辑器利用类型标注提供更好的代码补全、错误提示和代码导航功能。
2. 基本语法
2.1 变量标注
name: str = "Alice"
age: int = 30
height: float = 1.75
is_student: bool = True
names: list[str] = ["Alice", "Bob", "Charlie"]
ages: tuple[int, int, int] = (20, 25, 30)
person: dict[str, str | int] = {"name": "Alice", "age": 30} # Python 3.9+
from typing import Dict, Union # Python 3.7, 3.8
person2: Dict[str, Union[str, int]] = {"name": "Alice", "age": 30}
变量名: 类型 = 值
的形式。- 可以使用内置类型(
str
、int
、float
、bool
、list
、tuple
、dict
、set
等)。 - Python 3.9+ 支持使用
list[str]
、dict[str, int]
等更简洁的类型标注方式。 - Python 3.7, 3.8 需要从
typing
模块导入List
,Dict
,Union
等类型。 Union[str, int]
表示可以是str
或int
类型。
2.2 函数返回值标注
def greet(name: str) -> str:
return f"Hello, {name}!"
def add(x: int, y: int) -> int:
return x + y
def process_data(data: list[int]) -> tuple[int, float]:
total = sum(data)
average = total / len(data) if data else 0
return total, average
2.3 可选类型 (Optional) 联合类型(Union)
from typing import Optional
def get_name(user_id: int) -> Optional[str]:
# ... 可能会返回 None
if user_id == 1:
return "Alice"
else:
return None
Optional[T]
等价于Union[T, None]
,表示可以是T
类型或None,也等价于 T | None
。
2.4 任意类型
from typing import Any
def process(data: Any) -> Any:
# data 可以是任何类型
return data
Any
表示可以是任何类型,通常用于处理未知类型或需要灵活性的情况。应尽量避免使用Any
,但这会弱化类型检查,它会失去类型检查的优势。
2.5 不可变标注
Final:用于标注不可修改的变量或方法:
MAX_SIZE: Final = 9000 # 这个值不应被修改
MAX_SIZE += 1 # 类型检查器将报告错误
class Connection:
TIMEOUT: Final[int] = 10
class FastConnector(Connection):
TIMEOUT = 1 # 类型检查器将报告错误
Literal:用于标注特定的值:
type Mode = Literal['r', 'rb', 'w', 'wb']
def open_helper(file: str, mode: Mode) -> str:
...
open_helper('/some/path', 'r') # 通过类型检查
open_helper('/other/path', 'typo') # 类型检查错误
2.6 自定义异常类型标注
当函数返回特定异常类型时,可以通过 NoReturn
标注:
from typing import NoReturn
def exit_with_error() -> NoReturn:
raise SystemExit("Error occurred")
3. 类型别名
from typing import List
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
用 TypeAlias
给复杂的类型起一个更简单的名字,提高代码可读性。
4. 标注可调用对象(Callable 类型)
from typing import Callable
def apply_function(func: Callable[[int, int], int], x: int, y: int) -> int:
return func(x, y)
def multiply(x: int, y: int) -> int:
return x * y
result = apply_function(multiply, 5, 10) # result is 50
Callable[[int, int], int]
表示一个接受两个int
类型参数并返回int
类型的函数。
5. 泛型
泛型是一种强大的工具,它允许你编写可以处理多种类型的代码,而无需为每种类型都编写单独的函数或类。这提高了代码的复用性、可读性和可维护性。
举个例子,假设你想编写一个函数,返回列表中的第一个元素。如果没有泛型,你可能需要为每种类型的列表都编写一个函数:
def first_int(items: list[int]) -> int:
return items[0] if items else None
def first_str(items: list[str]) -> str:
return items[0] if items else None
使用泛型,你可以只编写一个函数:
from typing import TypeVar, List
T = TypeVar('T') # 定义一个类型变量 T
def first(items: List[T]) -> T | None: #注意要处理items为空的情况
return items[0] if items else None
names: List[str] = ["Alice", "Bob"]
first_name = first(names) # first_name 的类型被推断为 str
numbers: List[int] = [1, 2, 3]
first_number = first(numbers) # first_number 的类型被推断为 int
empty_list: List[int] = []
first_in_empty = first(empty_list)
print(type(first_in_empty)) #输出<class 'NoneType'>
在这个例子中,T
就是一个类型变量,它可以代表任何类型。当你调用 first(names)
时,T
就被推断为 str
;当你调用 first(numbers)
时,T
就被推断为 int
。
你还可以给 TypeVar
设置边界,限制它可以代表的类型。例如
T = TypeVar('T') # T 可以代表任何类型
#或
NumberT = TypeVar('NumberT', int, float) # NumberT 只能是 int 或 float 类型