Python Stub File
stub /stʌb/, noun [ C ] 存根(计算机),票根(会计,通常用于记录交易或付款的证据)
Python是一个动态的弱类型语言,这一特性使得Python具备非常大的灵活性的同时也带来了很多的不便,可以说成也萧何败萧何。这里我们主要针对两个缺点:
- 难以确定变量的具体类型,可读性较低
- 不便与其他强类型语言沟通,可扩展性较低
针对第一个缺点,之前文章介绍的Type Hint
做出了改进,而本文就将介绍如何使用Stub File
解决与其他语言的沟通。
1 介绍
Python存在一类后缀是.pyi
的文件。这些文件称为Python的存根文件
(Stub File
),它是Python Type Hint系统的一部分。
.pyi
文件为Python源代码提供了一种类型注释的方式,这对于动态类型的Python语言来说,可以帮助开发者更好地理解代码,提高代码的可读性和可维护性,同时也有助于IDE和静态类型检查器提供更准确的代码提示和错误检查。
.pyi
文件的主要用途包括:
为第三方库提供类型信息:一些第三方库可能没有使用类型注释或者使用的是Python 2.x的注释方式,那么可以通过提供
.pyi
文件,为这些库提供类型信息,而不需要修改原有的源代码。为动态代码提供类型信息:Python是一种动态类型的语言,有些代码(如使用
eval()
的代码)在运行时才能确定类型,这种情况下,可以通过.pyi
文件提供类型信息。为C扩展提供类型信息:Python的C扩展通常无法直接提供类型信息,可以通过
.pyi
文件为这些C扩展提供类型信息。
下面是一个简单的.pyi
文件的例子:
# content of foo.pyi
from typing import List
def add(a: int, b: int) -> int: ...
def get_names() -> List[str]: ...
在这个例子中,.pyi
文件为add
函数和get_names
函数提供了类型信息。
注意,.pyi
文件中的函数体通常只有一个省略号(...
),这是因为.pyi
文件只用于提供类型信息,不包含实际的实现。
以Pytorch的.pyi
文件为例,其中包含了Pytorch的各种函数的Type Hint信息,这样就加速了IDE和静态类型检查器的解析速度
2 stubgen
一般来说Stub File
都不是手动创建的,而是利用mypy
提供的stubgen
工具自动生成的。
2.1 安装stubgen
stubgen
作为mypy
的一部分,安装mypy
就会自动安装stubgen
python -m pip install mypy -i https://pypi.tuna.tinghua.edu.cn/simple
2.2 使用stubgen
创建Stub File
有几个需要遵循的传统:
- 可以为库(或模块)编写一个
Stub File
,并将其存储为与库模块在同一目录下的.pyi
文件 - 也可以将你的
Stub File
放在一个专门的目录中(例如,myproject/stubs
)。在这种情况下你需要设置环境变量MYPYPATH
以引用该目录。例如:
export MYPYPATH=~/work/myproject/stubs
- 对于模块,
Stub File
名称需要和模块名一致,例如,对于模块csv.py
的Stub File
,文件名用csv.pyi
。对于包,使用带有__init__.pyi
的子目录 - 如果一个目录同时包含一个模块的.py和.pyi文件,那么.pyi文件会被IDE和静态代码检查工具优先解析。这样,即使你不想修改源代码,也可以轻松地为一个模块添加类型注释
2.3 例子
假设现在有一个名称为linked_list.py
的模块,里面提供了双向链表节点类的定义:
# linked_list.py
from pathlib import Path
from typing import overload, Literal, Optional
class LinkedListNode:
def __init__(self) -> None:
self.prev: LinkedListNode
self.next: LinkedListNode
@overload
@classmethod
def new(cls, prev: 'LinkedListNode') -> 'LinkedListNode':
"""创建新的双向链表节点, 并指定上一个节点为prev"""
...
@overload
@classmethod
def new(cls, next: 'LinkedListNode') -> 'LinkedListNode':
"""创建新的双向链表节点, 并指定下一个节点为next"""
...
@overload
@classmethod
def new(cls, prev: 'LinkedListNode', next: 'LinkedListNode') -> 'LinkedListNode':
"""创建新的双向链表节点, 并指定上一个节点为prev, 下一个节点为next"""
...
@classmethod
def new(
cls,
prev: Optional['LinkedListNode'] = None,
next: Optional['LinkedListNode'] = None,
) -> 'LinkedListNode':
obj = cls()
obj.prev = prev
obj.next = next
return obj
在命令行运行
stubgen linked_list.py
就会为当前文件创建一个Stub File
from _typeshed import Incomplete
from pathlib import Path as Path
from typing import overload
class LinkedListNode:
prev: Incomplete
next: Incomplete
def __init__(self) -> None: ...
@overload
@classmethod
def new(cls, prev: LinkedListNode) -> LinkedListNode: ...
@overload
@classmethod
def new(cls, next: LinkedListNode) -> LinkedListNode: ...
@overload
@classmethod
def new(cls, prev: LinkedListNode, next: LinkedListNode) -> LinkedListNode: ...
当我们在别的文件中引用这个类的时候,就会出现对应的解析