您看到的异常是由astng 包(可能是“抽象语法树,下一代”?)中的错误引起的,这是pylint 所依赖的工具包,由同一个人编写。顺便提一下,我总是鼓励人们尽可能使用pyflakes 而不是pylint,因为它快速、简单、快速且可预测,而pylint 则尝试做几种无法做到的深层魔法只是很慢,但这会使它陷入这种麻烦。 :)
这是 PyPI 上的两个包:
http://pypi.python.org/pypi/pylint
http://pypi.python.org/pypi/astng
请注意,这个问题必然是pylint 中的一个错误,并且不是在您的代码中,因为pylint 确实不在您的代码中运行为了生成它的报告——想象一下如果这样做可能造成的破坏(因为被 linted 的代码可能会删除文件,等等)!由于您的代码没有运行,因此再多的谨慎,例如使用线程 init() 或 cleanup() 函数保护您的调用,都可能阻止此错误 - 除非代码 sn-ps 出于其他原因发生更改我们即将调查的行为。
所以,关于你的实际例外。
我以前从未真正听说过_shutdown!快速搜索 Python 标准库在threading.py 中显示了它的定义,但没有从任何地方调用该函数;只有通过搜索 Python C 源代码,我才发现在解释器关闭期间,pythonrun.c 中的哪个位置实际调用了该函数:
static void
wait_for_thread_shutdown(void)
{
...
PyObject *threading = PyMapping_GetItemString(tstate->interp->modules,
"threading");
if (threading == NULL) {
/* threading not imported */
PyErr_Clear();
return;
}
result = PyObject_CallMethod(threading, "_shutdown", "");
if (result == NULL) {
PyErr_WriteUnraisable(threading);
}
...
}
显然,threading 标准库模块需要某种清理功能,并且他们对 Python 解释器本身进行了特殊处理,以确保它被调用。
从上面的代码可以看出,Python 安静且毫无怨言地处理了threading 模块在程序运行期间从未被导入的情况。但是如果threading 确实被导入了,并且在关机时仍然存在,那么解释器会在内部查找_shutdown 函数,甚至打印一条错误消息——然后返回一个非零退出状态,原因你的问题——如果它不能调用它。
所以我们必须找出为什么threading 模块存在但没有_shutdown 方法,此时pylint 完成检查您的程序并且Python 正在退出。需要一些仪器。我们可以打印出pylint 退出时模块的样子吗?我们可以! pylint/lint.py 模块在它的最后几行中,通过实例化它定义的 Run 类来运行它的“主程序”:
if __name__ == '__main__':
Run(sys.argv[1:])
所以我在我的编辑器中打开了lint.py——将每个小项目安装在 Python 虚拟环境中的一大好处是我可以进入并编辑第三方代码以进行快速实验——并添加了以下@ 987654348@ 声明在Run 类的__init__() 方法底部:
sys.path.pop(0)
print "*****", sys.modules['threading'].__file__ # added by me!
if exit:
sys.exit(self.linter.msg_status)
我重新运行了命令:
python -m pylint.lint m2test.py
出来了threading模块的__file__字符串:
***** /home/brandon/venv/lib/python2.7/site-packages/M2Crypto/threading.pyc
好吧,看看那个。
这就是问题所在!
根据这条路径,实际上存在一个M2Crypto/threading.py 模块,在所有正常情况下,它应该只是称为M2Crypto.threading,因此位于名称下的sys.modules 字典中:
sys.modules['M2Crypto.threading']
但不知何故,该文件也作为主要的 Python threading 模块被加载,遮蔽了位于标准库中的官方 threading 模块。因此,Python 退出逻辑非常正确地抱怨缺少标准库 _shutdown() 函数。
怎么会这样?顶级模块只能出现在sys.path 中明确列出的路径中,而不能出现在它们下面的子目录中。这就引出了一个新问题:在pylint 运行期间,…/M2Crypto/ 目录本身是否会像包含顶级模块一样放在sys.path 上?让我们看看!
我们需要更多检测:我们需要让 Python 告诉我们名称中带有 M2Crypto 的目录出现在 sys.path 中的那一刻。它确实会减慢速度,但让我们在 pylint 的 __init__.py 中添加一个跟踪函数——因为这是在运行 -m pylint.lint 时导入的第一个模块——它将为执行的每一行代码编写一个输出文件告诉我们,sys.path 中是否有坏值:
def install_tracer():
import sys
output = open('mytracer.out', 'w')
def mytracer(frame, event, arg):
broken = any(p.endswith('M2Crypto') for p in sys.path)
output.write('{} {}:{} {}\n'.format(
broken, frame.f_code.co_filename, frame.f_lineno, event))
return mytracer
sys.settrace(mytracer)
install_tracer()
del install_tracer
注意我在这里是多么的小心:我在模块的命名空间中只定义了一个名字,然后在我让pylint继续加载之前小心地删除它以自己清理之后!跟踪函数本身需要的所有资源——即sys 模块和output 打开文件——都在install_tracer() 闭包中可用,因此,从外部看,pylint 看起来与总是。以防万一有人试图反省它,比如pylint可能!
这会生成一个大约 800k 行的文件 mytracer.out,每行看起来像这样:
False /home/brandon/venv/lib/python2.7/posixpath.py:118 call
False 表示sys.path 看起来很干净,文件名和行号是正在执行的代码行,call 表示解释器处于哪个执行阶段。
那么sys.path 会中毒吗?让我们只看每行的第一个True 或False,看看有多少连续行以每个值开头:
$ awk '{print$1}' mytracer.out | uniq -c
607997 False
3173 True
4558 False
33217 True
4304 False
41699 True
2953 False
110503 True
52575 False
哇!那是个问题!对于一次运行几千行,我们的测试用例是True,这意味着解释器在运行时使用…/M2Crypto/——或者路径名的一些变体,其中包含M2Crypto——在路径上,它应该在哪里运行不是;只有包含 …/M2Crypto 的目录应该在路径上。在文件中寻找第一个False 到True 的转换,我看到了这个:
False /home/brandon/venv/lib/python2.7/site-packages/logilab/astng/builder.py:132 line
False /home/brandon/venv/lib/python2.7/posixpath.py:118 call
...
False /home/brandon/venv/lib/python2.7/posixpath.py:124 line
False /home/brandon/venv/lib/python2.7/posixpath.py:124 return
True /home/brandon/venv/lib/python2.7/site-packages/logilab/astng/builder.py:133 line
查看 builder.py 文件中的第 132 和 133 行会发现我们的罪魁祸首:
130 # build astng representation
131 try:
132 sys.path.insert(0, dirname(path)) # XXX (syt) iirk
133 node = self.string_build(data, modname, path)
134 finally:
135 sys.path.pop(0)
注意注释,它是原始代码的一部分,不是我自己添加的!显然,XXX (syt) iirk 是这个程序员奇怪的母语中对这句话的感叹,“把这个模块的父目录放在sys.path 上,这样每次有人强迫pylint 反省带有@ 的包时,pylint 就会神秘地中断。 987654401@子模块。”显然,它是一种非常紧凑的母语。 :)
如果您调整跟踪模块以观察 sys.modules 的实际导入 threading — 我将留给读者做一个练习 — 你会看到它发生在 SocketServer 被其他一些标准导入时分析过程中的库模块,反过来尝试无辜地导入threading。
让我们回顾一下正在发生的事情:
-
pylint 是危险的魔法。
- 作为它魔法的一部分,如果它看到你
import foo,然后它会跑去尝试在磁盘上找到foo.py,解析它,并预测你是从它的命名空间加载有效还是无效的名称。
- [见我的评论,下面。] 因为你在
RSA.as_pem() 的返回值上调用 .split(),pylint 试图内省 as_pem() 方法,该方法又使用 M2Crypto.BIO 模块,在turn 进行调用,诱导pylint 导入threading。
- 作为加载任何模块
foo.py 的一部分,pylint 会在 sys.path 上抛出包含 foo.py 的目录,即使该目录位于包内,因此会在其中提供模块目录在其分析期间隐藏同名标准库模块的特权。
- 当 Python 退出时,
M2Crypto.threading 库位于 threading 所属的位置令人不安,因为它想运行 threading 的 _shutdown() 方法。
您应该将此作为错误报告给pylint / astng 人员logilab.org。告诉他们是我派你来的。
如果你决定继续使用pylint,那么在这种情况下似乎有两种解决方案:要么不检查调用M2Crypto的代码,要么在运行期间导入threading pylint 导入过程 - 例如,通过将 import threading 粘贴到 pylint/__init__.py 中 - 以便模块有机会抓住 sys.modules['threading'] 插槽 之前 pylint 非常兴奋并尝试让M2Crypto/threading.py 抢占空位。
最后,我认为astng 的作者说得最好:XXX (syt) iirk。确实。