2025-04-19 Python 强类型编程

news2025/5/10 18:35:43

文章目录

  • 1 方法标注
    • 1.1 参数与返回值
    • 1.2 变参类型
    • 1.3 函数类型
  • 2 数据类型
    • 2.1 内置类型
    • 2.2 复杂数据结构
    • 2.3 类别选择
    • 2.4 泛型
  • 3 标注方式
    • 3.1 注释标注
    • 3.2 文件标注
  • 4 特殊情形
    • 4.1 前置引用
    • 4.2 函数标注扩展
    • 4.3 协变与逆变
    • 4.4 dataclass
  • 5 高级内容
    • 5.1 接口
    • 5.2 泛型的协变/逆变
    • 5.3 字面量类型
    • 5.4 静态检查
    • 5.5 Final 与 final
    • 5.6 关闭静态类型检查
  • 工具参考
  • 参考链接

环境:

  • Pycharm Professional 2025.1
  • Python 3.12.9

​ Python 是一门强类型的动态类型语言,可以动态构造脚本执行、修改函数、对象类型结构、变量类型,但不允许类型不匹配的操作。

​ Python 也提供了类型标注功能,有了类型标注提示后,就可以在编码时即发现错误。

image-20250418123709397

强类型检查的优势

  1. 几大优势:
    • 易读:比 docstring 更易理解接口协议,也更易使用第三方库(IDE 工具支持)
    • 排错:编码、编译期间即可发现错误(IDE 工具支持)
    • 重构:接口范围明确,更易于理解和放心重构。
    • 性能:(可能)可以做到静态编译优化。
  2. 现实:
    • 生态-IDE:(方类型检查工具比较全面(PyCharm、VSCode、VIM 等)。
    • 生态库工具:(方库有官方或大厂支持且很流行。
    • 官方 /mypy-9.5 K star、Facebook/.pyre-5 K Star,Google/pytype-3 K star 等。
    • 成熟度:静态检查已被广泛验证有效性,尤其大型工程(dropboxi 迁移 400 万行到静态标注)
    • 行业:主流编程语言以静态强类型检查为主(C++、Jva、Go 等);其他动态语言的静态类型扩展迅猛发展(如 TypeScript)。

​ 如下是一个非常长的(14个)PEP(Python 改进建议)的列表和落地情况(图中时间是文档时间 +1 年左右是实际落地时间),并且在 PEP 483 开始快速迭代(图中橙色是比较重要的迭代),并且到了 Python3.7 才真正算是成型。

image-20250418125523443

什么时候建议采用强类型检查

  • 提供 SDK、库/接口给其他人时。

    比 docstring 更清晰、主流 IDE 支持提示和校验。

  • 代码行数越多,价值越大。

    规范化编码,通过工具可以辅助发现潜在 BUG。

  • 需要写 UT(单元测试)的地方,就需要类型检查(by Bernat Gabor)。

以下情况适当考虑

  • 原型(Prototype)或验证性质项目(POC)的代码,可以先不引入。
  • 大量旧有代码,需要逐步阶段性引入(参考 Dropbox 经验)。
  • 不熟悉 Python 和类型提供功能用法时,可以先不引入。

1 方法标注

1.1 参数与返回值

​ 对函数参数,返回值进行标注。

image-20250418130632447
def add(v1: int, v2: int) -> int:
    """两个数字相加"""
    return v1 + v2

1.2 变参类型

​ 对变参、命名变参直接标注其类型。

image-20250418211059472
def foo(*args: str, **kwds: int):
    ...


foo('a', 'b', 'c')
foo('a', 'b', 1)  # 错误
foo(x=1, y=2)
foo(x=1, y='x')  # 错误
foo('', z=0)

1.3 函数类型

​ 函数类型:Callable[[参数1类型, 参数2类型, ...], 返回类型]

image-20250418211741331

2 数据类型

2.1 内置类型

​ 对内置类型标注,在变量声明后面添加 :类型 即可。

image-20250418130429019
a: int = 100
b: str = "abc"

2.2 复杂数据结构

Sequence

  1. Sequence 表示 server 可以是任何序列类型(包括 list, tuple, collections.deque 等)。
  2. Sequence 更通用,允许调用方传入列表、元组或其他序列类型。
  3. list 表示 server 必须是一个具体的列表(list),不能是元组或其他序列类型。
image-20250418131737490
from collections.abc import Sequence

ConnectionOptions = dict[str, str]
Address = tuple[str, int]
Server = tuple[Address, ConnectionOptions]


def connect(message: str,
            server: Sequence[Server]) -> None:
    ...


# 等价于
def connect1(message: str,
             server: Sequence[tuple[tuple[str, int], dict[str, str]]]) -> None:
    ...

2.3 类别选择

  • 使用 Union 定义可能的类型选择:x、y、返回值的类型同一时刻可以不同。
  • 使用 TypeVar 定义一组类型:x、y、返回值的类型同一时刻必须相同。
image-20250418211420368
from typing import Union, TypeVar, Text

AnyStr1 = Union[Text, bytes]
AnyStr2 = TypeVar('AnyStr2', Text, bytes)


def concat1(x: AnyStr1, y: AnyStr1) -> AnyStr1:
    pass


def concat2(x: AnyStr2, y: AnyStr2) -> AnyStr2:
    pass


concat1('a', 'b')  # 正确
concat1(b'a', b'b')  # 正确
concat1('a', b'b')  # 正确
concat2('a', 'b')  # 正确
concat2(b'a', b'b')  # 正确
concat2('a', b'b')  # 错误

2.4 泛型

​ 使用 TypeVar 表示泛型。

容器泛型示例 1

image-20250418212011985
T = TypeVar('T')  # 一个泛型类型


# 接受一个元素类型都是T的序列,返回值的类型也是T
def first(l: Sequence[T]) -> T:  # 泛型函数
    return l[0]


first([1, 2]) + 100  # 正确
first(['a', 'c']).upper()  # 正确
first(['a', 'c']) + 100  # 错误

容器泛型示例 2

image-20250418213017654
from typing import TypeVar, Iterable

T = TypeVar('T', bound=float)
Vector = Iterable[tuple[T, T]]


def inproduct(v: Vector[T]) -> T:
    return sum(x * y for x, y in v)

类与泛型

image-20250418213432735
from typing import TypeVar, Generic

T = TypeVar('T')


class MyClass(Generic[T]):
    def meth_1(self, x: T) -> T:
        ...

    def meth_2(self, x: T) -> T:
        ...


a: MyClass[int] = MyClass()
a.meth_1(1)  # 0K
a.meth_2('a')  # 错误

3 标注方式

3.1 注释标注

  • 使用注释 # type: xxxx 进行标注
  • 函数的标注方式:
    • (参数类型列表)->返回值类型
image-20250418213929802
from typing import List


class A(object):
    def __init__(self):
        # type: ()->None
        self.elements = []  # type: List[int]

    def add(self, element):
        # type: (List[int])->None
        self.elements.append(element)

3.2 文件标注

  • 独立的 stub 文件(.pyi)与源文件并行即可(放在同一目录下)。
  • 优点:
    • 不需要修改源代码(减少引入 BUG 可能)
    • Pyi 可以使用最新的语法(源文件可以是低版本)
    • 测试友好。
    • 不拥有的三方库,也可以补充标注信息。
  • 缺点:
    • 代码重复写了一遍头(工作量)
    • 打包变得复杂
image-20250418214958882
# pyi文件

from typing import List


class A(object):
    elements = ...  # type: List[int]

    def __init__(self) -> None:
        ...

    def add(self, element: int) -> None:
        ...

4 特殊情形

4.1 前置引用

​ 二叉树节在进行类型标注时需要引用自己,这种情况下需要使用前置引用,方式是用字符串代替类型标注。

image-20250418215453476
from typing import List


class Tree:
    def __init__(self, left: 'Tree', right: 'Tree'):
        self.left = left
        self.right = right

    def leaves(self) -> List['Tree']:
        ...

4.2 函数标注扩展

  • 使用 overload 进行参数返回值描述。
  • 必须有一个无修饰版本做真正实现。
  • 仅用于静态类型检查,运行时重载可以使用 functools.singleddispatch 等。
image-20250418215948265
from fontTools import unicode
from typing import overload


@overload
def utf8(value: None) -> None:
    ...


@overload
def utf8(value: bytes) -> bytes:
    ...


@overload
def utf8(value: unicode) -> bytes:
    ...


def utf8(value):
    ...  # 实际的实现,必须有

​ 有时也可以用 TypeVar 代替(更简洁)。

image-20250418220155407
from typing import TypeVar

AnyStr = TypeVar('AnyStr', None, unicode, bytes)
AnyStrRet = TypeVar('AnyStrRet', None, unicode, bytes)


def utf8(value: AnyStr) -> AnyStrRet:
    ...  # 实际实现

4.3 协变与逆变

  • 协变:

    让一个粗粒度接口(或委托)可以接收一个更加具体的接口(或委托)作为参数(或返回值);
    例如:老鹰列表赋值给鸟列表。

  • 逆变:

    让一个接口(或委托)的参数类型(或返回值)类型更加具体化,也就是参数类型更强,更明确。
    例如:鸟列表赋值给老鹰列表。

image-20250418220818472
from typing import TypeVar, Generic

T_co = TypeVar('T_co', covariant=True)  # 协变


class MyList(Generic[T_co]):
    def __init__(self, items: List[T_co]) -> None:
        self.items = items


class Bird:
    ...


class Eagle(Bird):
    ...


egls: MyList[Eagle] = MyList([Eagle()])
brds: MyList[Bird] = egls

4.4 dataclass

  1. 带默认值的可变命名元组
image-20250418221642558
from dataclasses import dataclass, field


@dataclass
class InventoryItem:
    name: str
    unit_price: float
    quantity_on_hand: int = 0  # 默认值
  1. 复杂域默认值
image-20250418221625278
@dataclass
class C:
    mylist: List[int] = field(default_factory=list)


c = C()
c.mylist += [1, 2, 3]
  1. 延迟初始化
image-20250418222047777
@dataclass
class C:
    a: float
    b: float
    c: float = field(init=False)  # 默认不初始化 c

    def __post_init__(self):  # 通过延迟初始化 c
        self.c = self.a + self.b


print(C(1, 2))  # print: "C(a=1, b=2, c=3)"

5 高级内容

5.1 接口

​ Python 支持 OOP,也支持 duck typing。在强类型检查中,duck typing 也一样适用。

​ 例如一个函数 close_resource 接受一个参数,要求这个参数必须提供一个特定的方法 .close()。这种需求在强类型检查时,也可以支持,称之为静态 duck typing,相关方案由 PEP 544 支持。

  • 定义了类似于 Java 的接口
    • 可以有实现,可以被继承。
    • 可以继承多个接口,构建一个新接口。
  • 实际类型检查时,不要求继承关系
    • 只需要对象的所有成员方法 signature 匹配 Protocoll 即可(duck-typing)。
image-20250418225515248
from typing import Protocol, Iterable


class IResource(Protocol):
    def close(self) -> None:
        ...


class Resource:  # 也可以继承IResource(非必须)
    def close(self) -> None:
        ...


def close_all(things: Iterable[IResource]) -> None:
    for t in things:
        t.close()


f = open('foo.txt')
r = Resource()
close_all([f, r])  # 通过
close_all([1])  # 不通过

5.2 泛型的协变/逆变

  1. 协议 (Protocol) 必须被实现

    • Box 是抽象接口,不能直接实例化,必须有一个类实现 content() 方法。
  2. 协变 (covariant=True) 的规则

    • Box[int] 可以赋值给 Box[float],因为 intfloat 的子类型(协变允许子类替换父类)。

    • 协变表示:如果 AB 的子类型,则 Box[A]Box[B] 的子类型。

    • intfloat 的子类型(因为所有 int 都可安全当作 float 使用),所以 Box[int] 可赋值给 Box[float]

image-20250418230435339
from typing import TypeVar

T_co = TypeVar('T_co', covariant=True)  # 支持协变或逆变


class Box(Protocol[T_co]):
    def content(self) -> T_co:
        ...


# 具体实现
class IntBox:
    def content(self) -> int:
        return 42


class FloatBox:
    def content(self) -> float:
        return 3.14


# 正确赋值
second_box: Box[int] = IntBox()  # ✅ 实现 Box[int]
box: Box[float] = second_box  # ✅ 协变允许(int 是 float 的子类型)

5.3 字面量类型

​ 不修改类型为 enum 的情况下,限定传递参数:Literal[字面量1,字面量2,]

image-20250418231454562
from typing import Literal, Any


# 总是返回True
def validate_simple(data: Any) -> Literal[True]:
    ...


# 只能是这几个值
MODE = Literal['r', 'rb', 'w', 'wb']


def open_helper(file: str, mode: MODE) -> str:
    ...


open_helper('/some/path', 'r')  # 通过
open_helper('/other/path', 'typo')  # 不通过

5.4 静态检查

  • 可以在静态检查时导入特定的库,运行时不做。
  • 这种情况下,相关标注只能用注释或字符串(前置)方式标注。
image-20250418232014786
import typing

if typing.TYPE_CHECKING:
    import expensive_mod


def a_func(arg: 'expensive_mod.Someclass') -> None:
    a_var = arg  # type: expensive_mod.Someclass

5.5 Final 与 final

Final

​ 在 Python 3.8 版本中引入一个扩展的标注 typeing.Final,用于标注变量,这个内置标注非常有用,可以理解为实现了 C++ 语法 const 的静态检查作用。

  • 指定变量被初始化后无法再被修改、类变量无法被子类修改。
  • 声明为 Final 的类成员的变量,未初始化的,必须在 __init__ 里面初始化。
image-20250418233257225
from typing import Final

MAX_SIZE: Final = 9000
MAX_SIZE += 1  # 错误


class ImmutablePoint:
    x: Final[int]
    y: Final[int]  # 错误

    def __init__(self) -> None:
        self.x = 1  # 未初始化y

    def s(self):
        self.x = 1  # x不能被重新赋值

final

​ 另一个被引入的就是小写的 typing.final,用于标注类,可以理解为实现了 Java 语法 final 的静态检查作用。虽然 Python 本身就可以扩展实现运行时 final 的作用,但是这里实现了检查期的final,这个官方版本可以说非常的有用。

​ 如下,除了可以修饰类(不能被继承),甚至可以修饰类的方法(不能被重写)

image-20250418233626883
from typing import final


@final
class Base:
    ...


class Derived(Base):  # 错误
    ...


class Base2:
    @final
    def foo(self):
        ...


class Derived2(Base2):
    def foo(self):  # 错误
        ...

5.6 关闭静态类型检查

​ 特定情况下,我们也需要关闭静态检查(例如测试或开发中间时),这种情况只需要使用 typing.no_type_check 修饰函数或类来关闭。

​ 如果希望关闭对一个装饰器的静态检查的话,需要使用 typing.no_type_check_decorator 修饰装饰器来关闭。

image-20250419000134243
from typing import no_type_check, no_type_check_decorator


@no_type_check
def add(v1: int, v2: int) -> int:
    ...


@no_type_check_decorator
def log_enter_exit(fn):
    def __wrapped(*args: int, **kwargs: int):
        ...
    return __wrapped

工具参考

  • typeshed:Python 内置标准和三方库的 pyi 集合 repo(PyCharm、mypy、pytype 已包含)
  • mypy:官方标准静态类型检查工具。
  • pyre:Facebook 开源的静态类型检查工具。
  • pytype:google 开源的 Python 静态代码扫描工具(不依赖标注)。
  • pyannotate:dropbox 开源的自动给 Python 添加类型标注的工具。
  • pydantic:一个基于标注的 Python 数据校验与配置管理库。

参考链接

  • 丁来强-Python强类型编程最佳实践_哔哩哔哩_bilibili。
  • Python的强类型特性与类型检查实践-CSDN博客。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2338642.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

spring-batch批处理框架(2)

文章目录 八、作业控制8.1 作业启动8.1.1 SpringBoot 启动8.1.2 Spring 单元测试启动8.1.3 RESTful API 启动 8.2 作业停止方案1:Step 步骤监听器方式方案2:StepExecution停止标记 8.3 作业重启8.3.1 禁止重启8.3.2 限制重启次数8.3.3 无限重启 九、Item…

动态规划算法的欢乐密码(一):斐波那契数模型

专栏:算法的魔法世界 个人主页:手握风云 目录 一、动态规划 二、例题讲解 2.1. 第 N 个泰波那契数 2.2. 三步问题 2.3. 使用最小花费爬楼梯 2.4. 解码方法 一、动态规划 动态规划是一种将复杂问题分解为更小的子问题,并利用子问题的解来…

Echarts柱状图斜线环纹(图形的贴花图案)

单独设置 <!--此示例下载自 https://echarts.apache.org/examples/zh/editor.html?cbar-stack&codePYBwLglsB2AEC8sDeAoWszGAG0iAXMmuhgE4QDmFApqYQOQCGAHhAM70A0x6L7ACsAjQwtQqhIkwATxDUGbABaMAJsADu9HrAC-xHd3TZqNaCvEHiFcuaKTjAMzAMAzAFIu28hUXPY9ABYPQxIAI2AwTABbV…

2025.04.19【Spider】| 蜘蛛图绘制技巧精解

Basic multi-group radar chart Start with a basic version, learn how to format your input dataset Radar chart with ggradar A Spider chart made using the ggradar package and a lot of customization.A work by Tuo Wang 文章目录 Basic multi-group radar chartRa…

【Linux】深入理解Linux文件系统:从C接口到内核设计哲学

文章目录 前言一、C语言中的文件接口1. 文件指针&#xff08;句柄&#xff09;FILE*以写方式打开文件&#xff0c;若文件不存在会新建一个文件W写入方式&#xff0c;在打开文件之前都会将文件内容全部清空追加写方式&#xff0c;其用法与写方法一致&#xff0c;不同在于a方法可…

基于尚硅谷FreeRTOS视频笔记——15—系统配制文件说明与数据规范

目录 配置函数 INCLUDE函数 config函数 数据类型 命名规范 函数与宏 配置函数 官网上可以查找 最核心的就是 config和INCLUDE INCLUDE函数 这些就是裁剪的函数 它们使用一个ifndef。如果定义了&#xff0c;就如果定义了这个宏定义&#xff0c;那么代码就生效。 通过ifn…

Linux网络编程 深入解析TFTP协议:基于UDP的文件传输实战

知识点1【TFTP的概述】 学习通信的基本&#xff1a;通信协议&#xff08;具体发送上面样的报文&#xff09;、通信流程&#xff08;按照什么步骤发送&#xff09; 1、TFTP的概述 tftp&#xff1a;简单文件传输协议&#xff0c;**基于UDP&#xff0c;**不进行用户有效性验证 …

c# MES生产进度看板,报警看板 热流道行业可用实时看生产进度

MES生产进度看板&#xff0c;报警看板 热流道行业可用实时看生产进度 背景 本软件是给宁波热流道行业客户开发的生产电子看板软件系统 功能 1.录入工艺流程图&#xff08;途程图&#xff09;由多个站别组成。可以手动设置每个工艺站点完成百分比。 2.可以看生成到哪个工…

初识Redis · C++客户端string

目录 前言&#xff1a; string的API使用 set get&#xff1a; expire: NX XX: mset,mget&#xff1a; getrange setrange: incr decr 前言&#xff1a; 在前文&#xff0c;我们已经学习了Redis的定制化客户端怎么来的&#xff0c;以及如何配置好Redis定制化客户端&…

华硕原厂系统枪神9/9p超竟版-WIN11原装开箱出厂系统安装

华硕原厂系统枪神9/9p超竟版-WIN11-24H2-专业工作站版本安装可带F12-ASUSRecovery恢复功能 适用机型&#xff1a; G635LX、G635LW、G835LX、G835LW、G615LW、G615LP、G615LM、G615LH G815LW、G815LP、G815LM、G815LH、G635LR、G835LR、G615LR、G815LR 远程恢复安装&#xff…

CF1016赛后总结

文章目录 前言T1:Ideal GeneratorT2&#xff1a;Expensive NumberT3:Simple RepetitionT4&#xff1a;Skibidi TableT5:Min Max MEXT6:Hackers and Neural NetworksT7:Shorten the Array 前言 由于最近在半期考试&#xff0c;更新稍微晚了一点&#xff0c;还望大家见谅 &#…

QT聊天项目DAY06

1.从git上同步项目 编译测试&#xff0c;编译通过 Post请求测试 测试成功 2. email is 打印有问题&#xff0c;检查 解析结果是存储在jsonResult中的&#xff0c;修改 3. 客户端实现Post验证码请求 3.1 同步Qt客户端项目 检查QT版本&#xff0c;由于我在公司用的还是QT5.12.9…

GNU,GDB,GCC,G++是什么?与其他编译器又有什么关系?

文章目录 前言1. GNU和他的工具1.1 gcc与g1.2 gdb 2.Windows的Mingw/MSVC3.LLVM的clang/clang4.Make/CMake 前言 在开始之前我们先放一段Hello World&#xff1a;hello.c #include <stdio.h>int main() {printf("Hello World");return 0; }然后就是一段老生常…

笔记整理五

STP生成树 stp生成树是用于解决二层环路问题的协议。 二层环路为有以下三种&#xff1a; 1.广播风暴 2.MAC地址的偏移&#xff08;每一次循环&#xff0c;都会导致交换机来回刷新MAC地址表记录&#xff09; 3.多帧复制 stp生成树&#xff1a;需要将原本的环型拓扑结构转换…

奥比中光tof相机开发学习笔记

针对奥比中光 tof相机&#xff0c;官方提供的资料如下ProcessOn Mindmap|思维导图 Orbbec SDK Python Wrapper基于Orbbec SDK进行设计封装&#xff0c;主要实现数据流接收&#xff0c;设备指令控制。下面就其开发适配进行如下总结&#xff1a; &#xff08;1&#xff09;系统配…

【面试向】点积与注意力机制,逐步编码理解自注意力机制

点积&#xff08;dot product&#xff09;两个向量点积的数学公式点积&#xff08;dot product&#xff09;与 Attention 注意力机制&#xff08;Attention&#xff09;注意力机制的核心思想注意力机制中的缩放点积自注意力机制中&#xff0c;谁注意谁&#xff1f; 逐步编码理解…

一个 CTO 的深度思考

今天和一些同事聊了一会&#xff0c;以下是我的观点 我的观点&#xff0c;成年人只能筛选&#xff0c;不能培养在组织中&#xff0c;应该永远向有结果的人看齐。不能当他站出来讲话的时候&#xff0c;大家还要讨论讨论&#xff0c;他虽然拿到结果了&#xff0c;但是他就是有一…

SQL通用语法和注释,SQL语句分类(DDL,DML,DQL,DCL)及案例

目录 SQL通用语法和注释 SQL语句分类&#xff08;DDL&#xff0c;DML&#xff0c;DQL&#xff0c;DCL&#xff0c;TPL&#xff0c;CCL&#xff09; DDL&#xff08;数据定义语言&#xff09; 数据库操作 查询&#xff08;SHOW、SELECT&#xff09; 创建&#xff08;CREAT…

AUTOSAR图解==>AUTOSAR_SWS_KeyManager

AUTOSAR KeyManager详细分析 AUTOSAR 4.4.0 版本密钥与证书管理模块技术分析 目录 1. 概述2. KeyManager架构 2.1 KeyManager在AUTOSAR架构中的位置2.2 架构说明 3. KeyManager模块结构 3.1 模块组件详解3.2 配置项说明 4. KeyManager证书验证流程 4.1 证书验证流程分析 5. Ke…

Jsp技术入门指南【七】JSP动作讲解

Jsp技术入门指南【七】JSP动作讲解 前言一、什么是JSP动作&#xff1f;二、核心JSP动作详解1. jsp:include&#xff1a;动态包含其他页面与<% include %>的区别 2. jsp:forward&#xff1a;请求转发到另一个页面3. jsp:param&#xff1a;为动作传递参数4. jsp:useBean&am…