区分 python 同级导入与导入 function/class

Differentiating between a python sibling import vs importing a function/class

我正在编写一个 pylint 检查器,我需要区分作为包的同级导入的导入和函数的导入或 class。

同级导入示例:

from . import sibling_package

函数导入示例:

from numpy import array

后一个例子我想标记,而前者我想允许,所以我需要能够区分两者。

我目前正在使用:

modspec = importlib.util.find_spec('numpy', 'array')

那 returns 一个 ModuleSpec,但我不清楚如何才能达到将导入 array 识别为模块与 function/class 的目标.在这个例子中它是一个函数导入,因此应该被标记。

这不是您仅从导入行就能轻易检测到的。 Python 是高度动态的,直到运行时你才能知道导入解析到什么类型的对象。模块规范无法告诉您此信息,因为模块上的属性可以解析为任何内容(包括另一个模块)。

我能看到的备选方案是:

  • 进行实际导入,然后测试对象类型。

    这并非没有风险,导入 可能 有副作用。导入模块包括执行顶级语句。这些副作用可能很轻微,比如在不满足依赖性时用另一个对象替换一个对象(try: from itertools import zip_longestexcept ImportError: from itertools import izip_longest as ziplongest 是一个微不足道的 Python 2 对比 Python 3 依赖性检查),但导入可能会更改文件系统!

    导入也会减慢检查速度。导入像 numpypandas 这样的模块可以引入大量额外的模块。您通常希望保持 linting 快速,否则开发人员往往不会打扰并完全跳过 linting。

  • 保留已知模块的列表。对于您知道的那些模块,如果它们从模块而不是模块本身导入名称,请抱怨。这很快,并且会捕获大多数常见情况。您可以使用从正在检查的模块周围的文件系统中收集的内容来扩充列表。换句话说,目标是足够快和足够好并接受新导入的一些遗漏。

  • 只有直接调用导入的名称才会报错。注册所有导入的名称,如果 AST 包含该名称的 Call 节点,那么您知道它们导入了一个函数或 class。 from foo import bar,然后后面的 spam = bar('baz') 是一个明确的指示,表明 bar 不是一个模块。