0%

python | 类型标注

在自己维护了一个几万行的 python 项目后,才发现类型标注可以拯救你维护时候的抓狂。

python 作为一个动态语言,只有在运行的时候才知道该变量到底是什么类型,虽然,python 写起来非常快,但是,后期维护起来,不知道变量怎么取值是真的痛苦。

python 3.5 版本开始将 Typing 作为标准库引入。

参考资料

Type aliases

类型别名是通过将类型分配给别名来定义的。

1
2
3
4
5
6
7
8
9
10
11
from typing import List

Vector = List[float]


def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]


new_vector = scale(2.0, [1.0, -4.2, 5.4])
print(new_vector)

NewType

使用 NewType 辅助类来创建不同的类型。静态类型检查器会将新类型视为原始类型的子类,这有助于捕获逻辑错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from typing import NewType

UserId = NewType('UserId', int) # 实际上UserID就是一个int类型,可以对其像int一样正常操作
some_id = UserId(524313)
print(some_id) # 524313


def get_user_name(user_id: UserId) -> str:
return str(user_id)


user_a = get_user_name(UserId(42351))
print(user_a) # 42351

# 可以对UserId类型的变量执行所有int操作,但结果始终为int类型
output = UserId(23413) + UserId(54341)
print(output) # 77754

Any

是一种特殊的类型。静态类型检查器会将每种类型都视为与 Any 兼容,同样,Any也与所有类型兼容。可以对Any类型的值执行任何操作或方法调用,并将其分配给任何变量。将Any类型的值分配给更精确的类型(more precise type)时,不会执行类型检查。所有没有返回类型或参数类型的函数都将隐式地默认使用Any

Anyobject的区别:与Any类似,每个类型都是object的子类型。然而,与Any不同,反过来就不成立了:object不是其它类型的子类型。

使用object,说明值能以类型安全的方式转为任何类型;使用Any,说明值是动态类型。

1
2
3
4
5
6
7
a: Any = None
a = [] # OK
a = 2 # OK

s: str = ""
s = a # OK
print(s) # 2

NoReturn

特殊类型,标记一个函数没有返回值

1
2
3
4
def stop() -> NoReturn:
raise RuntimeError('no way')

stop()

Union

联合类型,Union[X, Y]等价于X|Y,意味着XY。使用形如Union[int, str]的形式来定义一个联合体

  • 参数必须是某种类型,且至少有一个
  • 联合类型的联合类型会被展开(flattened)
  • 仅有一个参数的联合类型就是该参数自身
  • 冗余的参数会被跳过(skipped)
  • 在比较联合类型的时候,参数顺序会被忽略(ignored)
  • 不能继承或者实例化一个联合类型
  • 不支持Union[X][Y]这种写法。
1
2
3
4
5
6
7
8
# 联合类型的联合类型会被展开(flattened)
Union[Union[int, str], float] == Union[int, str, float]
# 仅有一个参数的联合类型就是该参数自身
Union[int] == int
# 冗余的参数会被跳过(skipped)
Union[int, str, int] == Union[int, str] # == int | str
# 在比较联合类型的时候,参数顺序会被忽略(ignored)
Union[int, str] == Union[str, int]

Optional

可选类型,Optional[X]等价于X|None,或Union[X, None]。可选类型与含默认值的可选参数不同,含默认值的可选参数不需要在类型注解(type annotation)上添加Optional限定符,因为它仅是可选的。显式应用None值时,不管该参数是否可选,Optional都适用。

1
2
3
4
# 可选类型与含默认值的可选参数不同,含默认值的可选参数不需要在类型注解(type annotation)上添加Optional限定符,因为它仅是可选的
def foo(arg: int = 0) -> None: ...
# 显式应用None值时,不管该参数是否可选,Optional都适用
def foo(arg: Optional[int] = None) -> None: ...

Callable

可调用类型,下标语法(subscription syntax)必须始终与两个值一起使用:参数列表和返回类型。参数列表必须是类型列表或省略号;返回类型必须是单一类型。如:Callable[[int], str]

没有指示可选参数或关键字参数的语法,这种函数类型很少用作回调类型。Callable[…, ReturnType]可用于类型提示一个可调用的接受任意数量的参数并返回ReturnType。一个普通的Callable等价于Callable[…, Any]

将其它可调用对象作为参数的可调用对象可能表明它们的参数类型使用typing.ParamSpec相互依赖。此外,如果该可调用对象从其它可调用对象中添加或删除参数,则可以使用typing.Concatenate运算符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def other_function(x: int, y: float) -> float:
print(f"x: {x}, y: {y}")
return y

def any_function(func: Callable[[int, float], float], x: int, y: float) -> float:
return func(x, y)

any_function(other_function, x=10, y=20)
any_function(other_function, x="abc", y=20) # 注:此条语句可以正常执行,类型提示(type hints)对运行实际上是没有影响的

def get_addr(csdn: str, github: str, port: int=403) -> str:
return f"csdn: {csdn}, github: {github}, port: {port}"

def get_addr() -> Callable[[str, str, int], str]:
return get_addr

Literal

字面类型,可用于向类型检查器指示相应的变量或函数参数与提供的字面量(或多个字面量之一)等效的值。Literal[…]不能创建子类。在运行时,允许将任意值作为Literal[…]的类型参数,但类型检查器可能会对此加以限制。

1
2
3
4
5
6
7
def validate_simple(data: Any) -> Literal[True]: ... # always returns True

MODE = Literal["r", "rb", "w", "wb"]
def open_helper(file: str, mode: MODE) -> str:
return file + ":" + mode

print(open_helper("/some/path", "r")) # /some/path:r

TypeVar

容器中,对象的类型信息不能以Generic(泛型)方式静态推断,因此,抽象基类扩展支持下标(subscription),用于表示容器元素的预期类型。可以使用typing模块中的TypeVar新工厂实现Generic参数化

  • 用户定义的类可以定义为Generic
  • Generic类型支持多个类型变量,不过,类型变量可能会受到限制
  • Generic类型变量的参数都必须是不同的
  • Generic支持多重继承
  • Generic类继承时,可以修复一些类型变量
  • 使用Generic类而不指定类型参数时,每个位置的类型都预设为Any
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
T = TypeVar('T') # Declare type variable, Can be anything
# 泛型类型支持多个类型变量,不过,类型变量可能会受到限制
S = TypeVar('S', int, str) # Must be int or str

class StrangePair(Generic[T, S]): ...

# 泛型类型变量的参数都必须是不同的
#class Pair(Generic[T, T]): ... # TypeError: Parameters to Generic[...] must all be unique

length = "5.5"
Length = TypeVar("Length", int, float, None) # Length可以使用int, float或None来表示
def get_length() -> Length:
return length

print(get_length()) # 5.5

TypedDict

把类型提示(type hints)添加到字典的特殊构造(special construct)。在运行时,它是一个普通的dictTypedDict声明一个字典类型,该类型期望它的所有实例都有一组固定的keys,其中每个key都与对应类型的值关联。这种期望不会在运行时检查,而只会由类型检查器强制执行。默认情况下,所有的keys都必须出现在一个TypedDict中,可以通过指定总体(totality)来重写它。

1
2
3
4
5
6
7
8
9
class Point2D(TypedDict):
x: int
y: int
label: str

a: Point2D = {'x': 1, 'y': 2, 'label': 'good'} # OK
b: Point2D = {'z': 3, 'label': 'bad'} # Fails type check

assert Point2D(x=1, y=2, label='first') == dict(x=1, y=2, label='first')

Dict

dict的泛型(generic)版本,用于注解(annotate)返回类型。注解参数时,最好使用抽象集合类型(abstract collection type),例如MappingDictdict之间没有真正的区别,但是Dict是泛型类型,它允许你指定keyvalue的类型,使其更加灵活。

1
2
def count_words(text: str) -> Dict[str, int]: ...
x: Dict[str, int] = {"beijing", 1}; print(x) # {"beijing", 1}

Mapping

collections.abc.Mapping的泛型(generic)版本。

1
2
3
4
5
6
7
def get_position_in_index(word_list: Mapping[str, int], word: str) -> int:
return word_list[word]

def func(m: Mapping[int, str]) -> List[int]:
return list(m.keys())

print(func({0: "no", 1: "yes"})) # [0, 1]

List

list的泛型(generic)版本,用于注解(annotate)返回类型。注解参数时,最好使用抽象集合类型(abstract collection type),例如SequenceIterable

1
2
3
4
5
6
7
8
9
10
11
T = TypeVar('T', int, float)

def vec2(x: T, y: T) -> List[T]:
return [x, y]

print(vec2(3, 2)) # [3, 2]

def keep_positives(vector: Sequence[T]) -> List[T]:
return [item for item in vector if item > 0]

x: List[int] = [1, 2, 3]; print(x) # [1, 2, 3]

Tuple

元组类型,Tuple[X, Y]是二项的元组类型,第一个元素的类型是X,第二个元素的类型是Y。空元组的类型可以写为Tuple[()]。如Tuple[int, float, str]是由整数、浮点数、字符串组成的三项元组。可用字面省略号(literal ellipsis)指定可变长元组,如Tuple[int, …]

1
2
3
4
# 指定所有元素的类型
x: Tuple[int, float, str] = (1, 2.1, "beijing"); print(x) # (1, 2.1, "beijing")
# 可变长度的元组
y: Tuple[int, ...] = (1, 2.1, "beijing"); print(y) # (1, 2.1, "beijing")

Set

builtins.set的泛型版本,用于注解返回类型。注解参数时,最好使用抽象集合类型,例如AbstractSet

1
x: Set[int] = {1, 2, 3}; print(x) # {1, 2, 3}
请我喝杯咖啡吧~