众所周知,Python 是动态类型语言,运行时不需要指定变量类型。这一点是不会改变的,但是2015年9月创始人 Guido van Rossum 在 Python 3.5 引入了一个类型系统,允许开发者指定变量类型。它的主要作用是方便开发,供IDE 和各种开发工具使用,对代码运行不产生影响,运行时会过滤类型信息。 Python的主要卖点之一就是它是动态类型的,这一点也不会改变。而在2014年9月,[Guido van Rossum](https://twitter.com/gvanrossum) (Python [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life)) 创建了一个Python增强提议([PEP-484](https://www.python.org/dev/peps/pep-0484)),为Python添加类型提示(Type Hints)。并在一年后,于2015年9月作为Python3.5.0的一部分发布了。于是对于[存在了二十五年](http://python-history.blogspot.com/2009/01/brief-timeline-of-python.html)的Python,有了一种标准方法向代码中添加类型信息。在这篇博文中,我将探讨如何使用它。 ## 基本使用 ### 基本数据类型 ```python # def 函数名(变量: 类型) -> 返回值类型 def foo(a: int, b: int) -> int: return a + b print(f(1, 2)) ``` ### 自定数据类型 ```python class A: name = "A" def get_name(o: A) -> str: return o.name get_name(A) # ~^~ 错误提示:应为类型 'A',但实际为 'Type[A]' get_name(A()) # 正确写法 ``` 特殊情况: 出现循环依赖的情况可加引号解决此类问题,例如下面举了一个双向链表的例子 ```python class Node: def __init__(self, prev: "Node"): self.prev = prev self.next = None ``` ### 列表和字典 #### 列表 ```python # Python < 3.9 from typing import List def my_sum(lst: List[int]) -> int: total = 0 for i in lst: total += i return total my_sum([0, 1, 2]) my_sum([0, 1, "2"]) # ~~~~~~~~^~ 这里的"2"将会提示报错 ``` ```python # Python >= 3.9 def my_sum(lst: list[int]) -> int: total = 0 for i in lst: total += i return total my_sum([0, 1, 2]) my_sum([0, 1, "2"]) # ~~~~~~~~^~ 这里的"2"将会提示报错 ``` 但如果```my_sum```中传入```tuple```也将会报错,可将类型```List```修改成```Sequence```,而```Sequence```也更常在实际中使用。 ```python from typing import Sequence def my_sum(lst: Sequence[int]) -> int: total = 0 for i in lst: total += i return total # 以下写法均合法 my_sum([0, 1, 2]) my_sum((0, 1, 2)) my_sum(b'0123') my_sum(range(3)) ``` #### 字典 ```python def my_sum(d: dict[str, int]) -> int: total = 0 for i in d.values(): total += i return total my_sum({"a": 1, "b": 2, "c": 3}) my_sum({"a": 1, "b": 2, "c": "3"}) # ~~~~~^^^~ 当传入值类型不符时就会报错 ``` ### 传入值类型不固定 ```python from typing import Union def foo(x: Union[int, None]) -> int: if x is None: return 0 return x f(None) f(0) ``` Python 3.10及以上版本时,```x: Union[int, None]```可简写成```x: int | None```。当传入类型有可能含有```None```或者其他类型时,还可使用```Optional```。例如 ```python from typing import Optional def foo(x: Optional[int]) -> int: if x is None: return 0 return x ``` 显然```Optional```的写法比```Union```的写法也更加清晰 ### 变量的 Type Hints 在实际开发中,可能会想创建一个字符串列表,可以使用如下方法 ```python user: list[str] = [] user.append(1) # ~^~ 将会报错:应为类型 'str' (匹配的泛型类型 '_T'),但实际为 'int' ``` ### 一些其他用法 #### 返回类型```Any``` 在我们没有进行类型标注时,那么类型默认即为```Any```,只是大部分时候我们认为显式是要好于隐式的。 ```python from typing import Any def foo(a: Any) -> Any: return a ``` 但是当函数没有返回值时,不能将函数返回值类型标注为```Any```,因为当函数没有返回值时,函数实际上是返回了```None```,下面就是一个不理想案例 ```python from typing import Any def foo(a: list) -> Any: a.append(1) lst: list = [] i: int = foo(lst) ``` 当函数```foo```类型标注为```Any```时(实际上函数```foo```返回```None```),变量```i```永远不可能从函数```foo```中得到一个```int```,但是函数```foo```类型标注为```Any```,这个时候不会报错,因为IDE认为函数```foo```返回任何东西都有可能。如果我们把函数```foo```的返回类型改为```None```时 ```python from typing import Any def foo(a: list) -> None: a.append(1) lst: list = [] i: int = foo(lst) # ~~~~^^^~ 将会报错:应为类型 'int',但实际为 'None' ``` 上文提到没有进行类型标注时,那么类型默认即为```Any```,这里也亦如此,当函数```foo```的返回类型留空不填时还会出现如上问题,所以当函数不返回值的时候也不应该留空,还是有必要加上返回类型```None```的。 #### 返回类型```NoReturn``` 当然也有函数真的不会返回东西,即他不是正常的运行完这个函数之后没有显示返回而返回```None```,他可能是```raise```了一个```exception```或直接退出了程序,这个时候可用```NoReturn```进行类型标注。 ```python from typing import NoReturn def error() -> NoReturn: raise ValueError ``` #### 返回类型```Callable``` 当我们要求传入的参数是可调用的时,我们可以使用```Callable```进行类型标注,下面以函数装饰器举例 ```python from typing import Callable def my_dec(func: Callable): def wrapper(*args, **kwargs): print("start") ret = func(*args, **kwargs) print("end") return ret return wrapper my_dec(1) # ~^~ 此处报错:Argument 1 to "my_dec" has incompatible type "int"; # expected "Callable[..., Any]" ``` 接下来修改一下这个函数 ```python from typing import Callable def my_dec(func: Callable): def wrapper(a: int, b: int): print(f"args = {a}, {b}") ret = func(a, b) print(f"result = {ret}") return ret return wrapper @my_dec def add(a: int, b: int) -> int: return a + b add(1, 2) ``` 这个函数装饰器的作用就是将两个参数```a```和```b```打印下来并且将最后的结果打印下来,结果如下 ```commandline args = 1, 2 result = 3 ``` 但是如果被装饰的函数只接受一个参数时(没有或不止两个时)就会带来一些问题,如 ```python @my_dec def absolute(a: int) -> int: return abs(a) absolute(1, 2) ``` 现在运行这个代码就会出错 ```commandline args = 1, 2 Traceback (most recent call last): File "example.py", line 19, in absolute(1, 2) File "example.py", line 7, in wrapper ret = func(a, b) TypeError: absolute() takes 1 positional argument but 2 were given ``` 那么现在想让这个函数```func```接受什么参数就返回什么值怎么办呢,就像对待列表和字典一样,做进一步定义 ```python from typing import Callable def my_dec(func: Callable[[int, int], int]): def wrapper(a: int, b: int): print(f"args = {a}, {b}") ret = func(a, b) print(f"result = {ret}") return ret return wrapper # vvvvv~ 报错:Argument 1 to "my_dec" has incompatible type "Callable[[str, str], str]"; @my_dec # expected "Callable[[int, int], int]" def add(a: str, b: str) -> str: return a + b # vvvvv~ 报错:Argument 1 to "my_dec" has incompatible type "Callable[[int], int]"; @my_dec # expected "Callable[[int, int], int]" def absolute(a: int) -> int: return abs(a) absolute(1, 2) ``` #### 返回类型```Literal``` 当我们想规定某个参数传进的值只能是规定的值时也可以使用Type Hint中的```Literal``` ```python from typing import Literal class Person: def __init__(self, name: str, gender: Literal["male", "female"]): self.name = name self.gender = gender a = Person("Li", "woman") # ~~~~~~~^^^^^^^~ 将会报错:应为类型 'Literal["male", "female"]',但实际为 'Literal["woman"]' b = Person("Bob", "male") ``` 但是有时候我们想让一个变量如```g```保存```gender```但是这样导致了一个问题,没错会报错 ```python g = "female" a = Person("Li", g) # ~~~~~~^~ Argument 2 to "Person" has incompatible type "str"; # expected "Literal['male', 'female']" ``` 这个时候我们只要把```g```进行类型标注即可解决问题 ```python g: Literal["male", "female"] = "female" a = Person("Li", g) ``` 但是这样```g```就太过麻烦,有没有办法简写呢?答案是有的,因为Type Hint在Python中也是一个object,也就是说能给他一个变量名 ```python from typing import Literal GenderType = Literal["male", "female"] class Person: def __init__(self, name: str, gender: GenderType): self.name = name self.gender = gender g: GenderType = "female" a = Person("Li", g) ``` #### 建立新返回类型```NewType``` 为避免直接给类型起别名的一下小弊端,Python为我们提供了```NewType```,让我们新建返回类型 ```python from typing import NewType UserId = NewType("UserId", int) AttackPoint = NewType("AttackPoint", int) class Player: def __init__(self, uid: UserId, attack: AttackPoint): self.uid = uid self.attack = attack def update_attack(self, atk: AttackPoint): self.attack = atk ``` 但是这也会带来一个问题,不能直接用```int```给值了,需要显式的将1变成```UserId```,100变成```AttackPoint``` ```python p = Player(1, 100) # ~~~~~~~^~~^^^~ 竟然报错了: # | Argument 1 to "Player" has incompatible type "int"; expected "UserId" # | Argument 2 to "Player" has incompatible type "int"; expected "AttackPoint" # 正确写法 p = Player(UserId(1), AttackPoint(100)) ``` 当然Type Hint在Run Time的时候不会有任何影响,也让我们的程序不会容易出错了,但是这样也让Python变得复杂了 Loading... 众所周知,Python 是动态类型语言,运行时不需要指定变量类型。这一点是不会改变的,但是2015年9月创始人 Guido van Rossum 在 Python 3.5 引入了一个类型系统,允许开发者指定变量类型。它的主要作用是方便开发,供IDE 和各种开发工具使用,对代码运行不产生影响,运行时会过滤类型信息。 Python的主要卖点之一就是它是动态类型的,这一点也不会改变。而在2014年9月,[Guido van Rossum](https://twitter.com/gvanrossum) (Python [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life)) 创建了一个Python增强提议([PEP-484](https://www.python.org/dev/peps/pep-0484)),为Python添加类型提示(Type Hints)。并在一年后,于2015年9月作为Python3.5.0的一部分发布了。于是对于[存在了二十五年](http://python-history.blogspot.com/2009/01/brief-timeline-of-python.html)的Python,有了一种标准方法向代码中添加类型信息。在这篇博文中,我将探讨如何使用它。 ## 基本使用 ### 基本数据类型 ```python # def 函数名(变量: 类型) -> 返回值类型 def foo(a: int, b: int) -> int: return a + b print(f(1, 2)) ``` ### 自定数据类型 ```python class A: name = "A" def get_name(o: A) -> str: return o.name get_name(A) # ~^~ 错误提示:应为类型 'A',但实际为 'Type[A]' get_name(A()) # 正确写法 ``` 特殊情况: 出现循环依赖的情况可加引号解决此类问题,例如下面举了一个双向链表的例子 ```python class Node: def __init__(self, prev: "Node"): self.prev = prev self.next = None ``` ### 列表和字典 #### 列表 ```python # Python < 3.9 from typing import List def my_sum(lst: List[int]) -> int: total = 0 for i in lst: total += i return total my_sum([0, 1, 2]) my_sum([0, 1, "2"]) # ~~~~~~~~^~ 这里的"2"将会提示报错 ``` ```python # Python >= 3.9 def my_sum(lst: list[int]) -> int: total = 0 for i in lst: total += i return total my_sum([0, 1, 2]) my_sum([0, 1, "2"]) # ~~~~~~~~^~ 这里的"2"将会提示报错 ``` 但如果```my_sum```中传入```tuple```也将会报错,可将类型```List```修改成```Sequence```,而```Sequence```也更常在实际中使用。 ```python from typing import Sequence def my_sum(lst: Sequence[int]) -> int: total = 0 for i in lst: total += i return total # 以下写法均合法 my_sum([0, 1, 2]) my_sum((0, 1, 2)) my_sum(b'0123') my_sum(range(3)) ``` #### 字典 ```python def my_sum(d: dict[str, int]) -> int: total = 0 for i in d.values(): total += i return total my_sum({"a": 1, "b": 2, "c": 3}) my_sum({"a": 1, "b": 2, "c": "3"}) # ~~~~~^^^~ 当传入值类型不符时就会报错 ``` ### 传入值类型不固定 ```python from typing import Union def foo(x: Union[int, None]) -> int: if x is None: return 0 return x f(None) f(0) ``` Python 3.10及以上版本时,```x: Union[int, None]```可简写成```x: int | None```。当传入类型有可能含有```None```或者其他类型时,还可使用```Optional```。例如 ```python from typing import Optional def foo(x: Optional[int]) -> int: if x is None: return 0 return x ``` 显然```Optional```的写法比```Union```的写法也更加清晰 ### 变量的 Type Hints 在实际开发中,可能会想创建一个字符串列表,可以使用如下方法 ```python user: list[str] = [] user.append(1) # ~^~ 将会报错:应为类型 'str' (匹配的泛型类型 '_T'),但实际为 'int' ``` ### 一些其他用法 #### 返回类型```Any``` 在我们没有进行类型标注时,那么类型默认即为```Any```,只是大部分时候我们认为显式是要好于隐式的。 ```python from typing import Any def foo(a: Any) -> Any: return a ``` 但是当函数没有返回值时,不能将函数返回值类型标注为```Any```,因为当函数没有返回值时,函数实际上是返回了```None```,下面就是一个不理想案例 ```python from typing import Any def foo(a: list) -> Any: a.append(1) lst: list = [] i: int = foo(lst) ``` 当函数```foo```类型标注为```Any```时(实际上函数```foo```返回```None```),变量```i```永远不可能从函数```foo```中得到一个```int```,但是函数```foo```类型标注为```Any```,这个时候不会报错,因为IDE认为函数```foo```返回任何东西都有可能。如果我们把函数```foo```的返回类型改为```None```时 ```python from typing import Any def foo(a: list) -> None: a.append(1) lst: list = [] i: int = foo(lst) # ~~~~^^^~ 将会报错:应为类型 'int',但实际为 'None' ``` 上文提到没有进行类型标注时,那么类型默认即为```Any```,这里也亦如此,当函数```foo```的返回类型留空不填时还会出现如上问题,所以当函数不返回值的时候也不应该留空,还是有必要加上返回类型```None```的。 #### 返回类型```NoReturn``` 当然也有函数真的不会返回东西,即他不是正常的运行完这个函数之后没有显示返回而返回```None```,他可能是```raise```了一个```exception```或直接退出了程序,这个时候可用```NoReturn```进行类型标注。 ```python from typing import NoReturn def error() -> NoReturn: raise ValueError ``` #### 返回类型```Callable``` 当我们要求传入的参数是可调用的时,我们可以使用```Callable```进行类型标注,下面以函数装饰器举例 ```python from typing import Callable def my_dec(func: Callable): def wrapper(*args, **kwargs): print("start") ret = func(*args, **kwargs) print("end") return ret return wrapper my_dec(1) # ~^~ 此处报错:Argument 1 to "my_dec" has incompatible type "int"; # expected "Callable[..., Any]" ``` 接下来修改一下这个函数 ```python from typing import Callable def my_dec(func: Callable): def wrapper(a: int, b: int): print(f"args = {a}, {b}") ret = func(a, b) print(f"result = {ret}") return ret return wrapper @my_dec def add(a: int, b: int) -> int: return a + b add(1, 2) ``` 这个函数装饰器的作用就是将两个参数```a```和```b```打印下来并且将最后的结果打印下来,结果如下 ```commandline args = 1, 2 result = 3 ``` 但是如果被装饰的函数只接受一个参数时(没有或不止两个时)就会带来一些问题,如 ```python @my_dec def absolute(a: int) -> int: return abs(a) absolute(1, 2) ``` 现在运行这个代码就会出错 ```commandline args = 1, 2 Traceback (most recent call last): File "example.py", line 19, in <module> absolute(1, 2) File "example.py", line 7, in wrapper ret = func(a, b) TypeError: absolute() takes 1 positional argument but 2 were given ``` 那么现在想让这个函数```func```接受什么参数就返回什么值怎么办呢,就像对待列表和字典一样,做进一步定义 ```python from typing import Callable def my_dec(func: Callable[[int, int], int]): def wrapper(a: int, b: int): print(f"args = {a}, {b}") ret = func(a, b) print(f"result = {ret}") return ret return wrapper # vvvvv~ 报错:Argument 1 to "my_dec" has incompatible type "Callable[[str, str], str]"; @my_dec # expected "Callable[[int, int], int]" def add(a: str, b: str) -> str: return a + b # vvvvv~ 报错:Argument 1 to "my_dec" has incompatible type "Callable[[int], int]"; @my_dec # expected "Callable[[int, int], int]" def absolute(a: int) -> int: return abs(a) absolute(1, 2) ``` #### 返回类型```Literal``` 当我们想规定某个参数传进的值只能是规定的值时也可以使用Type Hint中的```Literal``` ```python from typing import Literal class Person: def __init__(self, name: str, gender: Literal["male", "female"]): self.name = name self.gender = gender a = Person("Li", "woman") # ~~~~~~~^^^^^^^~ 将会报错:应为类型 'Literal["male", "female"]',但实际为 'Literal["woman"]' b = Person("Bob", "male") ``` 但是有时候我们想让一个变量如```g```保存```gender```但是这样导致了一个问题,没错会报错 ```python g = "female" a = Person("Li", g) # ~~~~~~^~ Argument 2 to "Person" has incompatible type "str"; # expected "Literal['male', 'female']" ``` 这个时候我们只要把```g```进行类型标注即可解决问题 ```python g: Literal["male", "female"] = "female" a = Person("Li", g) ``` 但是这样```g```就太过麻烦,有没有办法简写呢?答案是有的,因为Type Hint在Python中也是一个object,也就是说能给他一个变量名 ```python from typing import Literal GenderType = Literal["male", "female"] class Person: def __init__(self, name: str, gender: GenderType): self.name = name self.gender = gender g: GenderType = "female" a = Person("Li", g) ``` #### 建立新返回类型```NewType``` 为避免直接给类型起别名的一下小弊端,Python为我们提供了```NewType```,让我们新建返回类型 ```python from typing import NewType UserId = NewType("UserId", int) AttackPoint = NewType("AttackPoint", int) class Player: def __init__(self, uid: UserId, attack: AttackPoint): self.uid = uid self.attack = attack def update_attack(self, atk: AttackPoint): self.attack = atk ``` 但是这也会带来一个问题,不能直接用```int```给值了,需要显式的将1变成```UserId```,100变成```AttackPoint``` ```python p = Player(1, 100) # ~~~~~~~^~~^^^~ 竟然报错了: # | Argument 1 to "Player" has incompatible type "int"; expected "UserId" # | Argument 2 to "Player" has incompatible type "int"; expected "AttackPoint" # 正确写法 p = Player(UserId(1), AttackPoint(100)) ``` 当然Type Hint在Run Time的时候不会有任何影响,也让我们的程序不会容易出错了,但是这样也让Python变得复杂了 最后修改:2025 年 01 月 12 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏