Published on

Python如何给二进制模块生成stubs

Authors
  • avatar
    Name
    ttyS3
    Twitter

English title: Python How to Generate Stubs for Binary Module

stubs是什么

stubs主要是给IDE自动提示和语法检测使用的,比如JetBrains PyCharm.

stubs有个规范的名称,叫PEP 561 Typing Stubs

https://www.python.org/dev/peps/pep-0561/#type-checker-module-resolution-order

  1. Stub packages - these packages SHOULD supersede any installed inline package. They can be found at foopkg-stubs for package foopkg.

所以 gi 包(PyGTK包的顶层目录名是gi)的 stubs包是 gi-stubs.

但是使用 JetBrains generator3 生成的包,并不是一个真正的stub文件(.pyi后缀的才是stub文件), 它生成的所有的代码都是.py后缀的。但是,PyCharm 能正确地将它当做stubs识别。

我们使用gi-stubs,而不是直接使用gi, 一是防止user级别的包屏蔽了系统的 gi (位于/usr/lib64/python3.8/site-packages/gi/), 而这个包实际是个stubs,不能真正工作, 这样就会导致正常的代码无法运行,因此顶层目录名一定是gi-stubs而不是gi. 二是,根据Stub packages的规范( foopkg-stubs for package foopkg)

PyGObject-stubs 的不足

最近尝试用py3写一点nautilus scripts, 因此肯定是要用到 PyGTK模块。但是这个模块是GTK lib的一个python绑定,因此实际的代码并不在python这边。 这给IDE的语法检测和自动提示都带来了麻烦。PyCharm 完全不知道from gi.repository import Gtk导入的Gtk模块是个什么东西。但是它很智能,会机智地提示你缺少相应的stubs:

Type hints are not installed They could make code insight better. Install 'PyGObject-stubs==0.0.2'

尝试执行一下pip install --user PyGObject-stubs 安装好这个stubs, 发现 PyCharm 已经可以识别Gtk模块了。

然后这个stubs, 如果你去看它的源码,就会发现,它能做的仅仅是导入一些基本的类型定义,没有任何参数提示。

这其实没有太多作用,它能做的仅仅是能不让编辑器误认为你有语法错误。

尝试自已动手生成

如果不想自己手动生成的,可以直接使用老灯生成好的 https://github.com/ttys3/pygobject-stubs

使用方法: git clone https://github.com/ttys3/pygobject-stubs.git /tmp/ && mv /tmp/pygobject-stubs/gi-stubs ~/.local/lib/python3.8/site-packages/

这里的python3.8根据自己的实际情况修改

Google了一下,好像早期的PyCharm有Generate Stubs for Binary Module这种自动生成stubs的功能,不知道为什么现在没有了。

然后在PyGObject-stubs的github repo发现一个有用的issue( https://github.com/pygobject/pygobject-stubs/issues/5 , 里面提到了使用intellij-communitypython/helpers/generator3.py脚本自动生成stubs.

不过这个issue是2018年的,有点老了。新版的JetBrains IDE已经将这个generator3.py脚本改写成了一个generator3模块了。

另外老灯发现,JetBrains PyCharm也自带了这个generator3模块。

所以,将前面发现的那个issue里的脚本稍做修改,就能适用于最新的IDE和库:

mkdir /tmp/out
for typelib in Atk GLib GModule GObject Gdk GdkPixbuf Gio Gtk Pango; do
    PYTHONPATH=$HOME/Apps/pycharm-community/plugins/python-ce/helpers python3 -m generator3 -d /tmp/out -p gi.repository.$typelib $(python3 -c "import gi; gi.require_versions({'Atk': '1.0', 'GModule': '2.0', 'Gdk': '3.0', 'GdkPixbuf': '2.0', 'Gtk': '3.0', 'Pango': '1.0'}); from gi.repository import $typelib; print($typelib.__path__[-1])")
done

# 让stubs能为PyCharm所用:
mv /tmp/out/gi ~/.local/lib/python3.8/site-packages/gi-stubs
# 或者你可以将 gi-stubs 目录放到任意地址,但是要记得添加到你的项目
# 具体可以参考 https://www.jetbrains.com/help/pycharm/stubs.html#reuse-stubs
# 注意是添加父目录,比如你将 gi-stubs 放到了 `~/repo/python/stubs/gi-stubs`, 那么
# 在PyCharm里添加的时候是添加`~/repo/python/stubs`这个目录

$HOME/Apps/pycharm-community 是本机 PyCharm 安装路径, 请修改为你自己的。

生成命令解释

我们从上面的for里单独拿一条命令出来,以给 Gtk 模块生成的为例。

python3 -c "import gi; gi.require_versions({'Gtk': '3.0'}); from gi.repository import Gtk; print(Gtk.__path__[-1])" 用于找到本机Gtk模块的typelib文件路径:

/usr/lib64/girepository-1.0/Gtk-3.0.typelib

因此我们的生成命令其实是像这样:

PYTHONPATH=$HOME/Apps/pycharm-community/plugins/python-ce/helpers python3 -m generator3 -d /tmp/out -p gi.repository.Gtk /usr/lib64/girepository-1.0/Gtk-3.0.typelib

这个命令会使用 JetBrains generator3 通过解析/usr/lib64/girepository-1.0/Gtk-3.0.typelib这个 G-IR 格式的二进制数据库文件,生成gi.repository.Gtk模块的stubs.

使用效果

自动生成的有些地方会有问题(但是基本上不影响代码自动完成),比如Gdk.SELECTION_CLIPBOARD没有成功解析出它的值(给弄成了None)

SELECTION_CLIPBOARD = None # (!) real value is 'Gdk.Atom.intern("CLIPBOARD", False)'

总体来说效果还是比PyGObject-stubs 好太多:

demo1
demo2