Python Stub File


.pyi文件

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文件的主要用途包括:

  1. 为第三方库提供类型信息:一些第三方库可能没有使用类型注释或者使用的是Python 2.x的注释方式,那么可以通过提供.pyi文件,为这些库提供类型信息,而不需要修改原有的源代码。

  2. 为动态代码提供类型信息:Python是一种动态类型的语言,有些代码(如使用eval()的代码)在运行时才能确定类型,这种情况下,可以通过.pyi文件提供类型信息。

  3. 为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和静态类型检查器的解析速度

Pytorch的.pyi文件

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有几个需要遵循的传统:

  1. 可以为库(或模块)编写一个Stub File,并将其存储为与库模块在同一目录下的.pyi文件
  2. 也可以将你的Stub File放在一个专门的目录中(例如,myproject/stubs)。在这种情况下你需要设置环境变量MYPYPATH以引用该目录。例如:
export MYPYPATH=~/work/myproject/stubs
  1. 对于模块,Stub File名称需要和模块名一致,例如,对于模块csv.pyStub File,文件名用csv.pyi。对于包,使用带有__init__.pyi的子目录
  2. 如果一个目录同时包含一个模块的.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: ...

当我们在别的文件中引用这个类的时候,就会出现对应的解析

IDE自动提示重载

同时还会给出Docstring


文章作者: Jack Wang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Jack Wang !
  目录