在PEP 483中提出了使用Type Hints来表明数据类型。
子类型
子类型的定义:
如果满足下列2个条件,则second_type是first_type的子类型
- second_type的任意值属于first_type的值的几何
- first_type的任意函数属于second_type的函数集合
可以看出,一个类型包含的值越多,函数越少,则在类型树上越接近根部。
一个例子:int
类型是float
类型的子类型,int
类型支持位运算,float
不支持。而float
支持的函数,int
类型都支持。
另外一个例子,List[int]
则不是 List[float]
的子类型. 第一个条件满足,但是第二个条件,append函数只能向List[float]
增加实数,不能向List[int]
增加实数。
有2种声明subtype的方式:
- nominal subtyping, 类型树是基于class 树的,例如
class UserId(int):
pass
UserId则是int类型的子类型
- structural subtyping, 子类型关系是通过声明来推断的
gradual typing
gradual type允许用户指定部分变量,在动态类型和静态类型中进行了平衡。
在此定义了一种新的关系,与...一致(is-consistent-with),这种关系类似于"是...子类型",除了当涉及到Any
类型时,不具有传递性。
当a的类型与b的类型一致,那么可以将a的值赋给b。这种一致性关系可以用以下三个规则定义:
- 如果类型
t1
是类型t2
的子类型,那么称t1
与t2
一致。(反过来不行,不能说t2
与t1
一致) - 类型
Any
与所有类型一致。(但是Any
不是任何类型的子类型) - 任何类型与
Any
一致。(但是任何类型不是Any
的子类型)
Any
可以看作是一个有所有值和所有函数的类型。结合子类型的定义,Any
即作为类型树的顶端(有所有类型)也是类型树的底端(有所有函数)。当使用Any
类型时,一般表示任何类型都可以接受,这时候就蜕化到了动态类型,使得静态类型检查器不再报错。
Types和Classes
类是动态,运行时概念,
类型由building blocks构建,被静态类型检查器使用。
每个类都是一个类型。
类和类型的区别可以总结为以下一般准则:
- 类型不能实例化,否则会产生
TypeError
错误。(Generic的非抽象子类可以) - 类型不能被继承,除非是Generic和其子类
3.类型如果出现在isinstance
issubclass
中,会产生TypeError
错误。(除非是非参数的generics)
building blocks
Any
-
Union[t1,t2,...]
指定的类型是t1,t2...
的子类型 -
Optional[t1]
是Union[t1,None]
的简写 -
Tuple[t1,t2,...tn]
元组,其中的变量类型依次为t1,t2,...,tn
- 空元组使用
Tuple[()]
- 可变的同类型元组可以使用
Tuple[t1,...]
使用三个点表示
- 空元组使用
-
Callable[[t1,t2,...tn],tr]
函数,参数类型分别为t1,t2,...,tn
,返回值为tr
。无法为可选参数或关键字参数声明类型,但是可以声明不检查参数列表:Callable[...,tr]
,是的,用三个点表示。
变量注释语法
PEP 526 python3.6
primes: List[int] = []
captain: str # Note: no initial value!
class Starship:
stats: ClassVar[Dict[str, int]] = {}
这样写不是强制的,python仍然是动态语言。这样只是为了便于读程序和使用第三方库。
ClassVar
是typing
模块的一个特殊的类,为了向类型检查器表示该变量是class 变量,不需要在实例中赋值。需要注意,在ClassVar
中,不能嵌套任何类型变量。
一般约定,对于实例变量,应该在__init__
或其他函数中进行注解。
from typing import Generic, TypeVar
T = TypeVar('T')
class Box(Generic[T]):
def __init__(self, content):
self.content: T = content
函数注释语法
def greeting(name: str) -> str:
return 'Hello ' + name
参数name
的类型是str,返回值类型也是str。
可以接受的注释(Annotations)
可以是内建类,抽象积累,types模块中的类型,用户自定义类。
还可以是None,Any,Union,Tuple,Callable,和从typing中导出的类(例如Sequence和Dict),类型变量(type variables)和类型别名(type aliases)
类型别名aliases
类型别名可以通过简单的变量赋值的形式实现
Url = str
def retry(url: Url, retry_count: int) -> None: ...
类型别名最好首字母大写。
任何在注释中接受的类型都可以在别名中使用。
from typing import TypeVar, Iterable, Tuple
T = TypeVar('T', int, float, complex)
Vector = Iterable[Tuple[T, T]]
def inproduct(v: Vector[T]) -> T:
return sum(x*y for x, y in v)
def dilate(v: Vector[T], scale: T) -> Vector[T]:
return ((x * scale, y * scale) for x, y in v)
vec = [] # type: Vector[float]
Generic types
类似于函数的语法,函数接受value返回value, generic type 构造器接受type,构造type。
generic function, 接受type variables
def take_first(seq: Sequence[T]) -> T: # a generic function
return seq[0]
accumulator = 0 # type: int
accumulator += take_first([1, 2, 3]) # Safe, T deduced to be int
accumulator += take_first((2.7, 3.5)) # Unsafe
类型变量,type variables
X=TypeVar('X')
声明了类型变量X,名字必须和变量名一样,默认情况下,类型变量可以是任何类型。
而Y=TypeVar('Y',t1,t2,...)
则指定了类型,类型Union[t1,t2]
S = TypeVar('S', str, bytes)
def longest(first: S, second: S) -> S:
return first if len(first) >= len(second) else second
result = longest('a', 'abc') # The inferred type for result is str
result = longest('a', b'abc') # Fails static type check
在这个例子中,两个参数的类型必须是一样的。
但是如果没有指定,则可以是任意类型。