python 类型提示和静态类型检查

63 篇文章 0 订阅
53 篇文章 0 订阅

官方文档:https://docs.python.org/3/library/typing.html#module-typing

YouTube:https://www.youtube.com/watch?v=omORtPiMBN4&t=300s&ab_channel=DougMercer

为什么关心类型检查?
  1. 提高代码的清晰度
  2. 知道变量是什么对象以及它有哪些方法
  3. 错误更少,因为可以执行静态分析,允许在运行代码之前捕获类型错误
所以类型提示如何提高代码清晰度?
def impute(x, method, safe):
    ...
    
如果没有查看实现内容,X,method 和 safe 有什么有效值?
def impute(
    x: list[int | None], method: str, safe: bool
) -> list[int]:
    ...
    
X 接受 int 和 Nones 列表。

method 是字符串。

safe 是一个布尔,也许能够启用/禁用安全检查。
所以,文档字符串还需要吗?

毕竟我们可以从文档字符串中获取所有这些信息,在一些源码中可以看到使用文档字符串一样很清晰,那么是文档字符串更好还是类型提示更好(因为它可以被程序分析)?

很好的文档字符串注释:

def impute(x, method, safe):
    """Impute missing values in time series.

    Parameters
    ----------
    x
        List of integers or None values.
    method
        str, specifying imputation method.
    safe
        bool, if True perform safety check.

    Returns
    -------
    List of integers, None values replaced with imputed values.
    ...
基本类型提示
age: int = 42      // 表示一个整数
weight: float = 170.5
first_name: str = "Bob"
last_name: bytes = b"Bobbington"
    
组成:变量名+冒号+类型+
# 对于Python 3.9+,我们使用集合的内置类型

quantities: list[int] = [1, 7, 6, 2, 0]
places_ive_visited: set[str] = {"Maryland", "New York", "Florida"}

# 对于Python <3.9,我们导入来自键入的 typing

from typing import List, Set
quantities: List[int] = [1, 7, 6, 2, 0]
places_ive_visited: Set[str] = {"Maryland", "New York", "Florida"}
# 对于映射,我们需要两个键和值的类型
menu: dict[str, int] = {"ham sandwich": 12, "soup du jour": 4}

# 对于固定大小的元组,我们指定了所有元素的类型
x: tuple[int, str, float] = (3, "yes", 7.5)

# 对于变量大小的元组,我们使用一种类型和省略号
x: tuple[int, ...] = (1, 2, 3)
函数类型提示
# 参考上面的示例
def repeat_str(s: str, n: int) -> str:
    ...

# 可以使用`Callable`键入`repeat_str`的值
from typing import Callable
g: Callable[[str, int], str] = repeat_str
lambda 的类型标注
由于类型注解的语法和 lambda 的语法冲突,因此不能直接对 lambda 做类型注解,但我们可以将 lambda 传给一个变量,通过对这个变量做 lambda,达到相同的目的。以下对 lambda 的几个例子:

from typing import Callable

# is_even 传入 int 返回布尔
is_even: Callable[[int], bool] = lambda x: (x % 2 == 0)
# func 传入两个字符串,返回 int
func: Callable[[str, str], int] = lambda var1, var2: var1.index(var2)
函数的一些高级用法

如何键入多个返回的提示函数?

def func() -> tuple[int, str]:    return 1, "a">>> print(func())(1, 'a')>>> isinstance(func(), tuple)True

如果变量可以是多种类型,该怎么办?

# Python 3.10+x: int | str | None = None# Python <3.10from typing import Unionx: Union[int, str, None] = None# 特殊情况:变量是*某些东西*或没有from typing import Optionaly: Optional[int] = None

注意:如果变量可以是什么或者难以键入的情况怎么办? 谨慎使用

如果变量只应对少数值?

from typing_extensions import Literaldef li(s: str, mode: Literal["x", "xx", "d", "e"]) -> str:    pass result = li(s="acascsac", mode="77")  # Expected type 'Literal["x", "xx", "d", "e"]', got 'Literal["77"]' instead print(result)

如何定义一次类型并多次使用它?

from typing import TypeAliasVector: TypeAlias = list[float]def awful_vector_add(u: Vector, v: Vector) -> Vector:    ...

注意:有些模块只支持 python 版本 >= 3.8, 对于小于 3.8的我建议的方法:

import sysif sys.version_info >= (3, 8):    from typing import Protocolelse:    from typing_extensions import Protocol, Literal

如何支持多个复杂的输入/输出签名?@overload

官方文档:https://docs.python.org/3/library/typing.html#typing.overload

overload翻译过来是“重载”的意思,Java中有这样的两个概念,重写(override)和重载(overload)。重写其实是在保证输入和输出不变的情况下重写实现逻辑。而重载则是允许修改输入和输出,即同一个方法名可以支持多种类型的输入和输出。python 中不提供函数重载,python 作为一个动态语言,天然支持可变参数类型和可变参数个数。

因此有两种方案:

1. typing.TypeVar2. typing.overload

第一种:对于固定数量参数的方法而言,同一个参数如果打算接受多种类型,可以这么用,比方说参数可以是:int, float, str:

import typingT = typing.TypeVar('T', int, float, str)def foo(name: T) -> str:    return 'hello ' + str(name)foo(2)这种方案更类似于静态语言中的interface的概念,定义一个通用的父类,这样的话,你可以传递子类型过去。

第二种:也就是重载的方式。但是跟静态语言中还是很有差别。

import typing@typing.overloaddef foo(name: str) -> str:    ...@typing.overloaddef foo(name: float) -> float:    ...@typing.overloaddef foo(name: int, age: int) -> str:    ...def foo(name, age=18):    return 'hello ' + str(name)foo(2)通过定义多个同名函数,上面的同名函数需要通过 overload 装饰器装饰。可以看到被装饰的函数的输入类型和输出类型都可以更改。但是,最后的实现方法一定要通用,也就是没有类型注解。被overload装饰的函数仅仅是为了受益于类型检查工具,因为它们会被没有overload装饰的函数定义覆盖,尽管未被装饰的函数是用于运行时的,但是会被类型检查工具忽略。

如何确保输入/返回类型匹配给定多个输入类型?

from typing import TypeVarT = TypeVar("T", int, float, list[int], list[float])U = int | float | list[int] | list[float]def f(x: T) -> T: ...def g(x: U) -> U: ...(f(1))    # int*(f(1.5))  # float*(f([1]))  # list*[int](g(1))    # Union[int, float, list[int], list[float]](g(1.5))  # Union[int, float, list[int], list[float]](g([1]))  # Union[int, float, list[int], list[float]]

如何制作一个支持多个输入类型的容器(例如,列出[int])?

from typing import Generic, TypeVarT = TypeVar("T")class Thing(Generic[T]):    def __init__(self, x: T) -> None:        self.x: T = x    def func(self) -> T:        return self.xreveal_type(Thing(1))    # Thing[builtins.int*]reveal_type(Thing(1.5))  # Thing[builtins.float*]
使用 mypy 进行类型检查

我们可以使用 pip 来安装mypy:

pip install mypy

test.py:

from typing_extensions import Literaldef li(s: str, mode: Literal["x", "xx", "d", "e"]) -> str:    passresult = li(s="xx", mode="xx")  # Expected type 'Literal["x", "xx", "d", "e"]', got 'Literal["77"]' insteadprint(result)

命令:mypy 文件路径

D:\test\manytable>mypy type_check.pySuccess: no issues found in 1 source fileD:\test\manytable>mypy type_check.pytype_check.py:8: error: Argument "s" to "li" has incompatible type "int"; expected "str"Found 1 error in 1 file (checked 1 source file)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值