python3类型注释

python是一门动态语言,若编码不规范会出现很多运行时错误。为了提高代码可读性,减少运行时bug,python提供了类型注释可以对函数参数,返回值,变量等进行标注。通过静态检查就可以发现一些常见的问题。类型注释的代码在python运行时并不会触发,所以没有性能影响。

1.类型注释的好处,看如下这个例子。
image

这两种方法在python3.5之后都是正确的写法,一点问题都没有。但是看左边的代码就会遇到的问题,因为python是动态语言,函数参数name的类型只有在运行时才知道。如果我们这样调用左边的函数gretting(["world"]),这很明显字符串不能和列表相加,这在运行时就会发生异常。
如果我们使用右边的写法通过静态检查器就可以提前检查出错误。右边提示了参数name的类型为str,函数的返回值为str。

通过mypy对代码进行类型检查,可以看到参数传递错误被检查出来了。
image

2.python提供的类型注释
python从3.5版本开始将类型注释并入了标准库中。可通过如下方式引用
from typing import List, Dict

  • 对变量进行注释

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    In [1]: x: int = 1                            # 表示变量x为int类型                                                                                     
    In [2]: x: float = 0.1 # 表示变量x为float类型
    In [3]: x: bool = True # 表示变量x为bool类型
    In [4]: x: str = "test" # 表示变量x为str类型
    In [5]: x: bytes = b"test" # 表示变量x为bytes类型


    In [8]: from typing import List,Dict,Sequence,Tuple,Set
    In [9]: x: Dict[str, float] = {'field': 2.0} # x是一个字典key为str类型,值为float类型
    In [10]: x: Tuple[int, str, float] = (3, "yes", 7.5) # x是一个元组,元素类型可以为int, str, float
    In [11]: x: List[int] = [1] # x是一个列表,元素类型为int
    In [12]: x: Set[int] = {6, 7} # x是一个集合,元素类型为int

    In [16]: x: Union[int,str] = 1 # x类型可以为int或者str

    In [17]: x: Union[int, None] = 1 # x类型可以为int或者None,等同于Optional[int]

    In [18]: x: Optional[int] = 1
  • 函数注释

    1
    2
    def gretting(name: str) -> str:
    return "Hello " + name
1
2
# 对一个可调用的函数进行注释
x: Callable[[int, float], float] = f # x是一个可调用的函数,有两个参数,类型分别为int,float,返回值为float类型
  • 类变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Car:
    seats: ClassVar[int] = 4 # seats是一个类变量,类型为int


    def f(x: 'A') -> None: # OK # 表示函数f的参数x的类型为A的实例
    ...
    class A:
    ...

    x: A = A() # 表示x的类型为A的实例,若想表示x的类型为类对象,请看下面
    x: Type[A] = A

typing模块中的协议类

协议 方法
Iterable[T] def __iter__(self) -> Iterator[T]
Iterator[T] def __next__(self) -> T def __iter__(self) -> Iterator[T]
Sized def __len__(self) -> int
Container[T] def __contains__(self, x: object) -> bool
Collection[T] def __len__(self) -> int def __iter__(self) -> Iterator[T] def __contains__(self, x: object) -> bool
Awaitable[T] def __await__(self) -> Generator[Any, None, T]
AsyncIterable[T] def __aiter__(self) -> AsyncIterator[T]
AsyncIterator[T] def __anext__(self) -> Awaitable[T] def __aiter__(self) -> AsyncIterator[T]
ContextManager[T] def __enter__(self) -> T def __exit__(self,exc_type: Optional[Type[BaseException]],exc_value: Optional[BaseException],traceback: Optional[TracebackType]) -> Optional[bool]
AsyncContextManager[T] def __aenter__(self) -> Awaitable[T] def __aexit__(self,exc_type:Optional[Type[BaseException]],exc_value: Optional[BaseException],traceback: Optional[TracebackType]) -> Awaitable[Optional[bool]]
  • cast 将静态类型值强制转换为子类型,并不是像其它静态语言真正的进行类型转换,在运行时并不生效。

    1
    2
    3
    4
    5
    from typing import cast, List

    o: object = [1]
    x = cast(List[int], o) # OK
    y = cast(List[str], o) # OK (cast performs no actual runtime check)
  • Generics

python内置的collection classes都是generic classes。Generic types拥有一个或多个类型参数。如Dict[int, str] List[int]

自定义一个原生类:

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

T = TypeVar('T')

class Stack(Generic[T]):
def __init__(self) -> None:
# Create an empty list with items of type T
self.items: List[T] = []

def push(self, item: T) -> None:
self.items.append(item)

def pop(self) -> T:
return self.items.pop()

def empty(self) -> bool:
return not self.items

现在Stack类可以用于表示拥有任务类型的stack,如Stack[int],Stack[str],Stack[Tuple[int, str]]等。
可以像使用内置容器类型一样使用类Stack

1
2
3
4
5
# Construct an empty Stack[int] instance
stack = Stack[int]()
stack.push(2)
stack.pop()
stack.push('x') # Type error

  • Variance of generic types
    关于它们之间的子类型关系,存在三种主要类型的通用类型:invariant(不变的),covariant(协变的),contravariant(逆变的)
1
2
3
4
5
class Animal:
...

class Cat(Animal):
...

如上,Animal有子类型Cat,Cat可能可以使用Animal中的某些方法,这个可能取决于类型是covariant或者contravariant

如果Cat可以使用它的超类型Animal中的一些方法,这种情况被称为convariant;如果超类型Animal可以使用子类型Cat中的某些方法,则被称为contravariant
如果convariantcontravariant都不成立,则被称作invariant,即Cat不能使用Animal中的方法,Animal 也不能使用Cat中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from typing import TypeVar, Generic, Iterable, Iterator

List_co = TypeVar('List_co', contravariant=True)


class ImmutableList(Generic[List_co]):
def __init__(self, items: Iterable[List_co]) -> None:
...

def __iter__(self) -> Iterator[List_co]:
...


class Employee:
...


class Manager(Employee):
...


def list_employees(employees: ImmutableList[Employee]) -> None:
for employee in employees:
print(employee)


managers = ImmutableList([Manager()])
list_employees(managers)

contravariant=True时,使用命令mypy co.py,出现如下错误:
image

默认情况下mypy认为所有用户自定义的原生类型都是invariant的。

请试下当convariant=True时又是怎样的结果


Ref:
1.pep484
2.pep526
3.mypy
4.https://blog.magrathealabs.com/pythons-covariance-and-contravariance-b422c63f57ac