Python3类型安全type hint

本文为作者原创,转载请注明出处:https://www.cnblogs.com/zhaoqingqing/p/18608123



type hint大纲#

  1. 什么是类型安全以及我们为什么需要类型安全

  2. 如何使用Python3的type hint

  3. 老项目如何逐步引入type hint

  4. Type hint在运行时的应用

疑问#

type hint在编译时会被去掉吧?

是的,Python的类型提示(Type Hints)只是一种语法糖,它们不会影响Python代码的运行。类型提示在运行时并不会进行类型检查,也不会影响代码的性能。它们主要是用来帮助程序员理解函数期望的输入和输出类型,以及提供给静态类型检查工具和IDE使用,以帮助找出潜在的错误。


类型标注能让我的程序运行得更快吗?

不会,Python的类型标注(Type Hints)并不会让你的程序运行得更快。类型标注在Python中主要是用于静态类型检查,以及提高代码的可读性和可维护性。它们在运行时并不会进行任何类型检查,也不会改变Python的动态类型特性,因此不会影响程序的运行速度。如果你想提高Python程序的运行速度,可以考虑使用如Cython、PyPy等工具,或者优化你的算法和数据结构。


为什么新的语言大部分都选择了类型在变量名的后面?

Copy
//Rust: fn greeting(name: &str) -> String { } //Go: func greeting(name string) string { } //Typescript: function greeting(name: string): string { }

python3的type check#

Literal 在Python3中,字面量(Literal)是指在代码中直接使用的特定值。字面量可以是数字字面量,字符串字面量,布尔字面量,特殊字面量,或者容器字面量

Autocomplete 自动补全

添加type hint的位置
• 函数/方法签名
• 变量初始化

Copy
name: str = "Python3" name = "Python3" # type checker know it’s a str
Copy
def greeting(name: str) -> str return 'Hello ' + name

多种类型(Union)#

Copy
from typing import Union def accept_task(task_id: int) -> None: task_type: Union[str, int] if is_side_task(task_id): task_type = "Side Task" else: task_type = 1

可选import(Optional)#

Copy
from typing import Optional def accept_task(task_id: int) -> None: task_type: Optional[str] #这两种可选写法都ok task_type: str | None #这两种可选写法都ok if is_side_task(task_id): task_type = "Side Task" else: task_type = None

条件import(TYPE_CHECKING)#

原来的import存在以下问题:

Copy
from data.config.monster import MonsterConfig def spawn_monster(monster: MonsterConfig) -> None: ...

可能需要避免import的情况:
• 会造成循环import
• Import有side-effect或者特定的时机要求
• Import耗时太长

增加条件后的import

Copy
from __future__ import annotations import typing if typing.TYPE_CHECKING: from data.config.monster import MonsterConfig def spawn_monster(monster: MonsterConfig) -> None: ...

前向引用(前向声明)#

类用字符串来代替?或者导入annotations

NewType#

在Python中,类型别名是一个方便的方式,用于为复杂的类型标注提供一个简单的名称。你可以使用 typing.TypeVartyping.NewType 创建类型别名。

例如,如果你有一个复杂的类型,如 List[Tuple[str, str, int]],你可以创建一个类型别名来简化它:

Copy
from typing import List, Tuple, TypeVar PersonInfo = List[Tuple[str, str, int]] def get_people_info() -> PersonInfo: return [('Alice', 'Engineer', 30), ('Bob', 'Doctor', 40)]

在这个例子中,PersonInfo 是一个类型别名,代表 List[Tuple[str, str, int]] 类型。这样可以使代码更易读,更易维护。

Callable#

在Python中,typing模块提供了Callable,这是一个类型提示,用于表示可调用的类型,比如函数或方法。

下面是一个使用Callable的例子:

Copy
from typing import Callable def apply_func(x: int, func: Callable[[int], int]) -> int: return func(x) def double(x: int) -> int: return 2 * x print(apply_func(5, double)) # 输出:10

在这个例子中,apply_func函数接受一个整数x和一个函数func作为参数。func的类型被注解为Callable[[int], int],这表示它是一个接受一个整数参数并返回一个整数的函数。double函数就是这样一个函数,所以我们可以将它作为apply_func的参数。

Protocol#

由于python函数中的参数无类型,或者说我们不关心对象的类型只关心它能做什么,所以可以通过隐式或某个规则来绕过typing的检查?

在Python 3.8及以上版本中,typing模块提供了Protocol类,它是一种特殊的类,用于定义结构化的类型协议。如果一个类满足了Protocol的定义,那么它就被认为是实现了该协议,无论它是否显式地继承了Protocol

下面是一个使用Protocol的例子:

Copy
from typing import Protocol class SupportsClose(Protocol): def close(self) -> None: ... def close_resource(resource: SupportsClose) -> None: resource.close() class Resource: def close(self) -> None: print("Resource closed") resource = Resource() close_resource(resource) # 输出:Resource closed

在这个例子中,SupportsClose是一个Protocol,它定义了一个close方法。close_resource函数接受一个SupportsClose类型的参数。尽管Resource类并没有显式地继承SupportsClose,但是它实现了close方法,所以它被认为是实现了SupportsClose协议,可以作为close_resource的参数。

泛型Generic#

在Python 3中,typing模块提供了Generic类,用于定义泛型类型。泛型类型是指在定义时不指定具体类型,而在实例化时才确定具体类型的类型。

下面是一个使用Generic的例子:

Copy
from typing import Generic, TypeVar T = TypeVar('T') class Stack(Generic[T]): def __init__(self) -> None: self.items = [] def push(self, item: T) -> None: self.items.append(item) def pop(self) -> T: return self.items.pop() # 使用时指定具体类型 stack = Stack[int]() stack.push(1) print(stack.pop()) # 输出:1

在这个例子中,Stack是一个泛型类,它接受一个类型参数T。在实例化Stack时,我们需要指定T的具体类型。例如,Stack[int]()创建了一个只接受int类型元素的栈。

使用泛型可以提高代码的复用性,使得我们可以用一套代码来处理多种类型的数据。同时,它也可以提高类型安全性,因为我们可以在编译时检查类型的正确性。

协变与逆变Covariant & Contravariant#

变成c++的写法了?这个章节的内容非常多

Mypy Plugin#

目前使用最多的pright,由微软主导是vscode自带的

mypy使用的较少

https://mypy.readthedocs.io/en/stable/extending_mypy.html
• 缺少文档
• 和其他type checker不兼容

Overload#

如果项目比较特殊,非要使用mypy的话,通过overload来实现

pyi文件#

只包含函数和变量类型“声明”的Python文件,Pyi文件的优先级高于py文件
使用场景:
• C/C++ extension模块的类型标注
• Python2/Python3兼容代码的类型标注
• Import耗时较长的模块的类型标注
• 为原本没有类型标注的第三方库添加类型标注
放置位置:
• 位于py文件同级目录
• 位于单独的stub目录(通过参数传递给type checker)

老项目引入 type checking#

兼容老代码#

迭代老代码#

逐步将老的代码文件从exclude中移除
• 更新或为第三方库添加type hint
• 移除disable_error_code
• 单独跳过特定的第三方库

Copy
[[tool.mypy.overrides]] module = "some_legacy_third_party" follow_imports = skip

禁止未标注的新代码#

运行时应用(Runtime API )#

类型标注Annotation#

Copy
import typing typing.get_type_hints typing.get_origin assert get_origin(str) is None assert get_origin(Dict[str, int]) is dict assert get_origin(Union[int, str]) is Union P = ParamSpec('P') assert get_origin(P.args) is P assert get_origin(P.kwargs) is P typing.get_args assert get_args(int) == () assert get_args(Dict[int, str]) == (int, str) assert get_args(Union[int, str]) == (int, str)

PEP 563#

PEP 563’s default change is clearly too disruptive to downstream users
and third-party libraries to happen right now. We can’t risk breaking even
a small subset of the FastAPI/pydantic users, not to mention other uses of
evaluated type annotations that we’re not aware of yet

PEP 563 的默认更改显然对下游用户和第三方库的破坏性太大,目前无法实现。我们不能冒险破坏 FastAPI/pydantic 用户的一小部分,更不用说我们尚不知道的评估类型注释的其他用途

Pydantic#

定义每个class中字段的类型,强制指定类型和字段

FastAPI#

调用者就相当于在使用强类型语言

Runtime API#

Bevy Style ECS API#

bevy是一个使用ecs模式的游戏引擎

Mypyc#

Cython 3.0#

使用cython在运行时热更替换C代码

总结#

Type hint能够帮助我们提早发现程序中的类型错误
• 我们可以逐步分阶段在项目中引入type hint
• 我们可以在运行时合理的利用type hint
• Type hint可以帮助我们编译出性能更高的程序
• Python的type hint还在快速的发展中,要用动态的眼光去看代它

作者:赵青青   一名在【网易游戏】做游戏开发的程序员,擅长Unity3D,游戏开发,.NET等领域。
本文版权归作者和博客园共有,欢迎转载,转载之后请务必在文章明显位置标出原文链接和作者,谢谢。
如果本文对您有帮助,请点击【推荐】您的赞赏将鼓励我继续创作!想跟我一起进步么?那就【关注】我吧。
posted @   赵青青  阅读(56)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· Deepseek官网太卡,教你白嫖阿里云的Deepseek-R1满血版
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?
· RFID实践——.NET IoT程序读取高频RFID卡/标签
历史上的今天:
2013-12-15 数学知识在游戏中的运用
2013-12-15 游戏暂停同时角色动作暂停
CONTENTS
点击右上角即可分享
微信分享提示