Python作为一个动态类型语言,支持Subtyping和Duck Typing,一些Lint工具可以对Python代码静态进行类型 检查来提高程序质量,还有工具可以支持类型标注和检查,并编译代码以提升性能。Python 3.5又引入了 Type Hints机制来提供统一的类型标注协议。

__class__ and isinstance

Python中,判断一个对象是否是某个类的实例的做法:

  • 判断直接实例使用a.__class__ is A
  • 判断间接实例使用isinstance(a, A)

__class__可以用来获取一个对象的类对象,is用于比较是否是内存中的同一个对象。isinstance判断 给出的对象是否是给出的类的直接或间接实例类型。举例:

In []: class A: pass
In []: class B(A): pass
In []: b = B()

In []: b.__class__ is B
Out[]: True
In []: b.__class__ is A
Out[]: False

In []: isinstance(b, B)
Out[]: True
In []: isinstance(b, A)
Out[]: True

Duck Typing

Duck Typing,鸭子类型,是动态类型的一种实现风格,指的是一个对象的语义不取决于其继承 自特定的类或实现特定的接口,而是关注这个对象有哪些可以被调用的方法和可以被访问的属性。 当被调用的方法或被访问的属性不存在时,会产生一个运行时错误。Python使用了Duck Typing 来实现多态(Run Time Polymorphism)的能力。示例:

class A:
    def f(): pass
    def g(): pass
class B:
    def g(): pass
    def h(): pass

def func(duck):
    g()

Duck Typing放弃了任何类型检查,完全没有语言本身的机制来检查,这种方式非常灵活, 完全依靠程序员来通过注释、文档、测试来强制约束。除了Python之外,Java(通过reflection)、 Javascript,Common Lisp等编程语言也支持Duck Typing。

Type Hints

PEP 482、PEP 483和PEP 484这三篇Proposal描述了Python 3.5引入的Type Hints机制。已经有mypy、 Reticulated Python和numba等实现了一定程度的静态类型检查,PyCharm这类IDE也通过类型推导 和检查机制来静态检查代码中的错误以及给出改进建议。其他的一些动态类型语言如PHP、TypeScript 等也引入了Type Hints来进行运行时的类型推导和检查。

值得注意的是,Type Hints只是提供了一个统一的类型标注的协议,而并没有提供类型检查机制。

def greeting(name: str) -> str:
    return 1

if __name__ == '__main__':
    print(greeting(1))

很明显这段代码中函数的实际返回值类型标注不符,且调用函数时参数类型也与标注不符,但仍然 能正常运行。

早在2006年,PEP 3107 Function Annotations就已经允许对函数的参数和返回值进行类型和注释 的标注。PEP 3107已经提供了Type Hints所需要的语法,PEP 484提供了一个额外的模块typing,提供 了CallableGenericSequenceTypeVar等GenericMeta和TypingMeta来构造一些更复杂的组合类型, 对于TypingMeta类型的构造器还提供了Covariance和Contravariance选项。typing同时 以的形式发布,可以通过pip3 install typing来安装, 用于支持Python 3.5以前版本的Python。typing模块还提供了typing.TYPE_CHECKING这一常量来 对不会被解释执行的代码执行类型检查,在类型检查阶段或静态分析阶段,type.TYPE_CHECKING的 值为True。PEP 484还定义了Type Comments,由于在Python中并没有First-class syntax用来支持 Type Annotation,对于某些比较复杂的表达式,可以用来Type Comments来进行类型标注:

x = []   # type: List[float]
x, y, z = [], [], []  # type: List[int], List[int], List[str]
x, y, z = [], [], []  # type: (List[int], List[int], List[str])
x = [
   1,
   2,
]  # type: List[int]

在可以预见的未来,CPython本身不会引入原生的类型推导和检查的支持,PEP 484中有 这样一段话:

Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention.

Type Hints的另一个可能的用途是用来辅助实现Function/Method Overloading,提供Multiple Dispatch的能力,目前typing.overload仅能用于stub files。PEP 484中表示,Overloading Syntax与Multiple Dispatch由于而这目的和用途不同,应该各自独立实现。overloading.py 以库的形式通过Decorator的形式实现了基本的Function/Method Overloading的支持,并且 支持使用PEP 484定义的类型标注协议以及typing里的构造器定义的复合类型。

想要真正做静态类型推导检查,仍然需要使用mypy 之类的工具。mypy已经支持PEP 484,mypy发现类型错误是的错误信息也非常友善,在实际项目 中使用mypy非常有利于提升项目质量。

Gradual Typing

类型决定了一个程序中一个符号的取值范围。Indiana大学的Jeremy Siek提出的Gradual Typing 可以作为一种弥补的方案。Gradual Typing也是Python的Type Hints以及mypy等类型检查工具的理论基础。

Gradual typing allows one to annotate only part of a program, thus leverage desirable aspects of both dynamic and static typing.

具体来说,如果变量a_value的类型和标识符a_variable一致,那么就可以将a_value赋值给a_variable。 对于两个不同的类型,“一致(consistant)”包含三种含义:

  • 如果类型t1是类型t2的子类型,则t1t2一致。
  • Any类型与其他任何类型一致。
  • 任何类型都可以视作Any类型的子类型,也就意味着任何类型都与Any类型一致。

具体举例,定义两个类型:

class A: ...
class B(A): ...

定义一个A类型的变量a,则a具有类型A,由于B类型是A类型的子类型,与A类型一致,因此 标识符a可以被赋一个B类型的值:

a = A() # type: A
a = B()

反过来,定义一个B类型的变量b,不能给标识符b赋一个A类型的值:

b = B() # type: B
b = A() # error.

Any类型可以视为类型继承图的根节点,因此,可以给Any类型的标识符赋任何类型的值:

c = ... # Any
c = A()
c = B()