Python类型编程

明月AI 野生AI架构师

Python是一种弱类型的解释性语言,以前在使用的时候往往也只是当作弱类型来使用。在开发小型系统的时候,弱类型也没什么问题,但是系统稍微复杂点,维护就可能会变成一个大坑。

不过自从Python3.6之后,已经加入了类型提示的功能,现在主流的版本也是3.8了,是时候该上马类型编程了。


一个简单的例子


先上一碟小菜:

def hello(name: str="world") -> str:    msg = f"hello {name}"    return msg
print(hello("deeao"))

不过这真的只是一个类型提示,随便传入一个值都不会进行参数校验:

print(hello(30))print(hello([30]))

这也不会报错,这特性也挺让人失望的。


更复杂的类型


简单的类型,我们有bool,int,float,str等,也有复杂一点的tuple,list,set,dict等,不过这些几个复杂一点的类型的定义并不是太清晰,例如list,只知道是一个列表,却没有定义列表元素的类型。我们需要借助typing来定义更加清晰的数据结构:

from typing import Dict, List, Tuple, Set, Optional
# 定义向量类型,元素为float的列表Vector = List[float]# key和value的类型都是strConnectionOptions = Dict[str, str]# 包含str和int两种类型的列表Address = Tuple[str, int]Server = Tuple[Address, ConnectionOptions]
def say_hi(name: Optional[str] = None): if name is not None: print(f"Hey {name}!") else: print("Hello World")

官方文档:docs.python.org/zh-cn/3/library/typing.html


特殊类型与特殊形式


  • typing.Tuple

  • 元组类型; Tuple[X, Y] 是二项元组类型,第一个元素的类型是 X,第二个元素的类型是 Y。空元组的类型可写为 Tuple[()]

    例:Tuple[T1, T2] 是二项元组,类型变量分别为 T1 和 T2。Tuple[int, float, str] 是由整数、浮点数、字符串组成的三项元组。

    可用省略号字面量指定同质变长元组,例如,Tuple[int, ...] 。Tuple 与 Tuple[Any, ...] 等价,也与 tuple 等价。

  • typing.Union

  • 联合类型;Union[X, Y] 的意思是,非 X 即 Y。

    可用 Union[int, str] 等形式定义联合类型。具体如下:

    • 参数必须是某种类型,且至少有一个。

    • 联合类型之联合类型会被展平,例如:

      Union[Union[int, str], float] == Union[int, str, float]
    • 单参数之联合类型就是该参数自身,例如:

      Union[int] == int  # The constructor actually returns int
    • 冗余的参数会被跳过,例如:

      Union[int, str, int] == Union[int, str]
    • 比较联合类型,不涉及参数顺序,例如:

      Union[int, str] == Union[str, int]
    • 联合类型不能作为子类,也不能实例化。

    • 不支持 Union[X][Y] 这种写法。

    • Optional[X] 是 Union[X, None] 的缩写。

  • typing.Any

  • 不受限的特殊类型。

    • 所有类型都与 Any 兼容。

    • Any 与所有类型都兼容。

  • typing.NoReturn

  • 标记没有返回值的函数的特殊类型。例如:

    from typing import NoReturn

    def stop() -> NoReturn:
    raise RuntimeError('no way')
  • class typing.NamedTuple

  • collections.namedtuple() 的类型版本。

    用法:

    class Employee(NamedTuple):
    name: str
    id: int

    相当于:

    Employee = collections.namedtuple('Employee', ['name', 'id'])

    为字段提供默认值,要在类体内赋值:

    class Employee(NamedTuple):
    name: str
    id: int = 3

    employee = Employee('Guido')
    assert employee.id == 3

    带默认值的字段必须在不带默认值的字段后面。

  • typing.NewType(nametp)

  • 用于为类型检查器标明不同类型的辅助函数,详见 NewType。在运行时,它返回一个返回其参数的函数。用法如下:

    UserId = NewType('UserId', int)
    first_user = UserId(1)
  • class typing.TypedDict(dict)

  • 把类型提示添加至字典的特殊构造器。在运行时,它是纯 dict

    TypedDict 声明一个字典类型,该类型预期所有实例都具有一组键集,其中,每个键都与对应类型的值关联。运行时不检查此预期,而是由类型检查器强制执行。用法如下:

    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')

    用于内省的类型信息可通过 Point2D.__annotations__ 和 Point2D.__total__ 访问。


FastAPI中的应用


FastAPI中的接口参数是有类型校验的,我猜应该是使用装饰器内使用反射实现的。


关于Python类型的小结


Python虽然有了类型提示,可依然是弱类型,仅仅是作为提示作用(不知道以后的版本会不会继续增强),跟强类型还是差了很远。

不过聊胜于无吧,能写出清晰的代码也是大功一件,而且各种编辑器的代码提示也得依赖类型提示来实现代码补全。

使用python开发系统,应该强制使用清晰的变量类型定义,即使小系统,也应该这样。(有些系统可能开始开发的时候,是个小系统,但是开发过程中,代码越来越多,就变成了一个复杂系统)

文章推荐