Python类型注解(Typing Hint) 编程详解
本文介绍 Python 类型注解 type hint的作用与用法。主要内容 : 类型提示的基本语法,使用mypy工具检查代码中类型的示是否确; 基本类型的类型提示,使用typeing 标准库对list, tuple, dict等集合类型的类型提示,以及复合型的集合类型的类型提示,类型别名的使用, 在函数定义,类定义中使用泛型 generic type,并以 FastAPI 示例 如何使用类型提示。
Python类型提示使用教程--目录
- 类型提示,对应当前的python 3.12 中 Typing Hint英文词语(官方文档有时也称类型注解(type annotation)。正如 hint 的英文本义,Typing Hint 只是对当前变量类型的提示,并非强制类型申明,
- 类型提示与类型检查,是 Python3.5 后各版本都非常重视的功能, 👍。Type Hint 对于提升代码质量与可读性非常有帮助,越来越多的库开始支持 Type Hint, 如 FastAPI, Numpy, Pandas, Pytorch 等。
1、什么是 Python 类型注解?
1.1 Python动态类型的优缺点
使用静态类型的编程语言,例如 C/C++、Java,必须预先声明变量、函数参数和返回值类型。编译器在编译和运行之前会强制检查代码的类型定义是否符合要求。运行时不能随意改变类型,当然需要的话,C++指针变量可改变指向对象的类型,但必须显式地用cast()函数来实现。
而 Python 解释器使用动态类型,函数的变量、参数和返回值可以是任何类型。此外,在程序运行时,允许不加申明更改变量类型。Python 解释器根据上下文来动态推断变量类型。如下面这个add()函数,输入参数,可以是任意类型。
def add(x,y)
return x+y
print(add(10+20))
print(add(3.14+5.10))
print(add(10+20.33))
print(add("hello","world"))
如果换成C++, 虽然可用函数模板来实现,显然不如python 简洁与灵活。
template<typename T>
T add(T x, T y) {
return x + y;
}
// 如果输入参数为整数
int a = 3;
int b = 4;
int result = add(a, b); // result 等于 7
//如果输入参数为浮点数
double c = 3.14;
double d = 2.71;
double result = add(c, d); // result 等于 5.85
//如果x, y 类型不同,必须先转换成同类型,才能调用。
//如果是字符串,必须重写方法
换成 java 的泛型来实现,可读性还不如C++ 。
public class AddFunction {
public static <T extends Number> T add(T x, T y) {
return (T) x.doubleValue() + y.doubleValue();
}
public static void main(String[] args) {
Integer a = 3;
Integer b = 4;
int resultInt = add(a, b); // resultInt 等于 7
Double c = 3.14;
Double d = 2.71;
double resultDouble = add(c, d); // resultDouble 等于 5.85
}
}
从上面例子可以看出,动态类型的优点:使编程变得容易,代码可读性更佳。这也是Zen of Python(Python之禅)
所倡导的:简单比复杂好,复杂比错综复杂好
。但也有代价,因为灵活,容易出现因前后理解不一致而造成的错误, 如经常遇到的1个典型问题:传入SQL的数据与数据库期望的不一致而导致SQL操作失败。Java语法最死板,若成功编译后,反而不容易出错。
1.2 Python3 类型注解的基本语法
Python3.5 引入了类型注解(typing hint), 可以同时利用静态和动态类型二者优点。 语法上有些类似于 typescript 的类型注解,但python 的类型注解使用更加方便,强烈建议在项目开发中应用此功能, 可以帮助规避很多代码中的变量使用错误。
下面用常规方式,定义一个简单的函数,该函数接受一个字符串并返回另一个字符串:
def say_hi(name):
return f'Hi {name}'
greeting = say_hi('John')
print(greeting)
给函数参数、返回值添加类型注解的语法为:
(parameter: type) # 函数参数类型注解
-> type # 返回值类型注解
例如,下面演示如何对函数的参数和返回值使用类型注解:
def say_hi(name: str) -> str:
return f'Hi {name}'
greeting = say_hi('John')
print((greeting)
输出:
Hi John
在此新语法中,name参数的类型为:str
.
并且 -> str 表示函数的返回值也是str
除了int, str 类型之外,还可以使用其他内置类型,例如str
、int
、float
、bool
、bytes
等。
需要注意的是,Python 解释器完全忽略了类型注解。如果将数字传递给函数,程序将运行,而不会出现任何警告或错误:say_hi()
def say_hi(name: str) -> str:
return f'Hi {name}'
greeting = say_hi(123)
print(greeting)
输出:
Hi 123
1.3 典型类型注解代码示例
# Type hint for a function that takes a list of integers and returns a list of strings
def process_numbers(numbers: List[int]) -> List[str]:
return [str(num) for num in numbers]
# Type hint for a function that takes a dictionary with string keys and integer values
def calculate_total(data: Dict[str, int]) -> int:
return sum(data.values())
# Type hint for a function that takes a datetime object and returns a formatted string
def format_date(date: datetime) -> str:
return date.strftime("%Y-%m-%d")
# Type hint for a function that takes a Union of two types as input
def process_data(data: Union[List[int], List[str]]) -> List[str]:
if isinstance(data, list):
return [str(item) for item in data]
else:
return []
# Type hint for a function that returns a generator object
def generate_numbers() -> Generator[int, None, None]:
for i in range(10):
yield i
# Type hint for a class method that returns an instance of the class itself
class MyClass:
def __init__(self, value: int):
self.value = value
def double_value(self) -> "MyClass":
return MyClass(self.value * 2)
在这些示例中,可以看到如何使用类型注解来指定预期类型的函数参数和返回值。它们有助于澄清代码的意图,并为静态类型检查工具和IDE类型检查插件提供信息,以提供更好的代码建议并捕获潜在的错误。
2、类型检查工具mypy
mypy 工具是Python代码类型检查工具,用于检查类型注解语法是否符合要求。
安装mypy
Python 没有官方的静态类型检查器工具。目前,最流行的第三方工具是 Mypy。有了这个工具,python就可以提前检查到代码中的类型使用错误了,是不是有点像静态语言了。参考 javascript --> typescript的发展轨迹,个人认为,python应该重视类型检查工具的作用。
使用以下命令进行安装:
pip instal mypy
使用mypy
安装后,您可以使用它来在运行程序之前使用以下命令检查类型:mypy
mypy app.py
它将显示以下消息:
app.py:5: error: Argument 1 to "say_hi" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)
该错误指示 的参数是 ,而预期类型是 say_hi``int``str
如果将参数改回字符串并再次运行,它将显示一条成功消息:mypy
Success: no issues found in 1 source file
如果使用 Vscode IDE, 可以将mypy 设置为python代码的静态类型检查器
打开 Vscode 的全局设置文件 settings.json.
{
"python.linting.mypyEnabled": true,
"python.linting.mypyArgs": [
"--follow-imports=silent",
"--show-column-numbers",
"--allow-untyped-defs",
"--allow-subclassing-any",
"--allow-untyped-calls",
"--strict"
]
}
开关选项说明
- –strict 表示严格模式
- –allow-untyped-defs: 对于未做类型注解的函数,不显示错误
- –allow-subclassing-any: 允许对Any类型的值进行子类化。一些框架可以使用Mypy抛出这些错误,因此如果您有这些错误,请添加这一行。
- –allow-untyped-calls: 允许您在代码中调用无类型注解函数
重启Vscode 后,设置即生效。 团队项目可采用此方式,相当于强行要求在python中进行类型检查,帮助提高代码质量。
3、类型注解使得变量类型保持一致
定义变量时,可以添加类型注解:
name: str = 'John'
变量的类型是str。如果将非字符串的值分配给变量,静态类型检查器将发出错误。例如
name: str = 'Hello'
name = 100
mypy检查后报错,提示“将int类型值赋给了str变量”
app.py:2: error: Incompatible types in assignment (expression has type "int", variable has type "str")
Found 1 error in 1 file (checked 1 source file)
4、允许联合类型
除了前面提到的,可使用int, str, bool, float 等进行类型注解。 如果允许1个变量接受2种类型以上的输入值,如何实现?
如前面提到的加法函数,add()
def add(x, y):
return x + y
但你想限制参数x,y为整数或浮点数类型,而非单一类型。可以使用typing模块的 Union类注解。
4.1 Union 联合类型注解
首先,从typing模块导入:Union
from typing import Union
其次,使用 创建包含int 和 float 的联合类型:Union[int, float]
def add(x: Union[int, float], y: Union[int, float]) -> Union[int, float]:
return x + y
以下是完整的源代码:
from typing import Union
def add(x: Union[int, float], y: Union[int, float]) -> Union[int, float]:
return x + y
从 Python 3.10 开始,您可以使用 X | Y 用于创建联合类型,例如:
def add(x: int | float, y: int | float) -> int | float:
return x + y
4.2 使用 | 符号对联合类型注解
Python3.11 以后,支持 | 注解联合类型, 更加简便
def add(x: int|float, y: int|float) -> int|float:
return x + y
4.3 Optional 类型
arg: Optional[int]
相当于 Union[int, None], 即变量可能为None.
from typing import Optional, Mapping
message: Optional[str] = "hello python"
data: Mapping[str,Optional[str]] = {'name': 'Jack', 'hobbies': 'Hiking' }
5、类型别名
Python 允许您为类型分配别名,并将别名用于类型注解。例如:
from typing import Union
number = Union[int, float]
def add(x: number, y: number) -> number:
return x + y
在此示例中,我们为Union[int, float]
分配一个别名number,并在 add()函数中使用该别名。
6、简单容器类型的类型注解
虽然可将变量直接标注为 list, tuple,set,如果希望进一步指定集合中的元素类型,需要使用Typing 模块的 LIst, Tuple, Set,Dict, Sequence等封装类用于提示。
Typing 类型名 | Python内置类型 |
---|---|
List | list |
Tuple | tuple |
Dict | dict |
Set | set |
Sequence | 用于表示 list, tuple 类型 |
Mapping | 用于表示字典,set 类型 |
ByteString | bytes, bytearray, 以及 memoryview 等二进制类型. |
注意 typing 模块类型首字母为大写。
标注 list 类型变量
用typing模板的List 或者Sequence, 注意 list 类型注解,只接收1个类型参数。
from typing import List, Sequence
ratings: List[int] = [1, 2, 3]
data: Sequence = [1,2,3] # 用sequence 来代替 List, Tuple.
也可以写成
rating: list[int] = []
标注Tuple元组类型变量
Tuple 类型标注可以接受任意个参数,按元素位置设置类型
# 元素类型为int
x: tuple[int] = (5,)
# 第1个元素为 int, 第2个元素为str
y: tuple[int, str] = (5, "foo")
标注字典类型变量
使用 typing模板的 Mapping 来标注,Mapping有两个参数,第1个参数为key的类型,第2个参数为value的类型。
from typing import Mapping, Sequence
x: Mapping[str, str | int] = {}
x['name']=3.113 # 会提示错误
Mapping[str, str | int] 表示key为str类型, value 类型为 str 或者 int .
7、复杂容器类型的类型注解
复杂容器类型,是指元素也是容器类型,如 [ (‘Jack’, 100), (‘Steve’, 300), …] , 列表类型的元素为 tuple,
data_a: List[Tuple[str, int]] = [("Bob", 1), ("Jim", 2), ("Steven", 53)]
再看1个复杂点的类型,
data_b: List[Tuple[Tuple[int, int], str]] = [
((10, 20), "red"),
((40, 30), "green"),
((32, 45), "yellow")
]
显然,不太容易理解, 这类情形下,可通过 type alias 类型别名 来标注类型注解, 增加可读性
Position = Tuple[int, int]
# type Position = Tuple[int, int] # 在V3.12, 前面加type
Pixel = Tuple[Position, str]
data_b: List[Pixel] = [
((10, 20), "red"),
((40, 30), "green"),
((32, 45), "yellow")
]
8、特殊Typing类型
8.1 无类型 None
如果函数未显式返回值,则可以使用 None 键入 hint 返回值。例如:
def log(message: str) -> None:
print(message)
8.2 函数对象做参数的类型注解
如果参数为1个函数对象,类型注解的形式如下:
Callable[[int], str]
对应于函数对象的参数与返回值 (int) -> str.
from typing import Callable
def foo(x: int, callback: Callable[[int],str]) -> str:
return callback(x)
9、Generic Type(也称泛型)
如果在代码中采用了Type Hints 规范,但又不想失去调用函数时可输入任意类型参数的便利,可将不确定类型的变量申明为 generic type
9.1 函数定义中使用 generic type
Step1, 先定义1个 type varialbe 类型变量,
使用TypeVar() 方法来定义类型变量 , 主要用法:
T = TypeVar('T') # 可以是任意类型
S = TypeVar('S', bound=str) # 必须是 str 类型
A = TypeVar('A', bound=str|bytes) # 必须是 str 或 bytes
TypeVar() 第1个参数为名字, 可以任意取。类型变量的作用是告知 type checker , 这是1个generic type variable.
from typing import Sequence, Mapping, TypeVar
T = TypeVar('T') # 将 T 做为类型变量
Step-2, 函数定义时,可将函数参数、函数体内变量、返回值,申明为泛型
def doubleit(n: T) -> T:
return n*n
Step-3, 输入不同类型参数验证
x: int = 100
print(doubleit(x)) # 输入int类型
y: float = 33.33 # 输出 float 类型
print(doubleit(y))
output
10000
1110.8889
9.2 定义多个类型变量
有时,函数有多个输入参数,且类型不相同,可定义多个类型变量
X = TypeVar('X')
Y = TypeVar('Y')
def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y:
try:
return mapping[key]
except KeyError:
return default
# 测试泛型函数
rows: Mapping[str, str | int] = {
'product': 'tv',
'price': 5900,
'model': 'XT601',
'size': 75,
'screen': 'IPS LED',
}
print(lookup_name(rows, 'size', 50))
9.3 在 class类定义中使用 generic type
typing提供了1个Generic 类做为使用generic type 泛型的class 的基类,
class MyClass( **typing.Generic[T]** ):
from typing import TypeVar, Generic
T = TypeVar('T')
class MyClass(Generic[T]):
data_a: T
def __init__(self, data: T) -> None:
self.data_a = data
def display(self) -> None:
print(self.data_a)
print("Generic type in class defiition")
ma = MyClass(100)
ma.display()
mb = MyClass("it is a CAT")
mb.display()
10、使用 Typing Hint 的 FastAPI 示例
FastAPI 是最快的 Python Web 开发框架之一,其原因除了采用异步执行方式,类型注解也是1个提升速度的因素。
FastAPI 框架要求使用type hint 类型注解,更容易管理代码质量。 下面是1个简单的FastAPI 例子:
from typing import Any
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
tags: list[str] = []
@app.post("/items/", response_model=Item)
async def create_item(item: Item) -> Any:
return item
@app.get("/items/", response_model=list[Item])
async def read_items() -> Any:
return [
{"name": "Portal Gun", "price": 42.0},
{"name": "Plumbus", "price": 32.0},
]
11、总结
- 对变量与函数使用类型注解,可以提高代码质量,
- 对于容器类型的注解,可以使用标准库 typing 模块的相应容器类型,可以使用类型别名来提升可读性。
- 使用 mypy工具来帮助查检查代码类型注解错误。
- 通过generic type 泛型增加函数与类定义的灵活性。
- 也可以使用 class类, dataclass 做类型注解。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)