您看到的异常是由astng
包中的错误引起的(大概是“抽象语法树,下一代”?),这是一个pylint
依赖于同一人编写的工具包。顺便提一下,我总是鼓励人们在可能的情况下使用pyflakes
而不是使用pylint
,因为它快速、简单、快速且可预测,而pylint
尝试做几种深度魔法,不仅速度慢,而且可以准确地实现这种麻烦。:)
以下是 PyPI 上的两个包:
http://pypi.python.org/pypi/pylint
http://pypi.python.org/pypi/astng
请注意,这个问题一定是代码中的错误,pylint
而不是代码中的错误,因为pylint
不会运行您的代码以生成报告——想象一下如果它运行可能会造成的破坏(因为被 linted 的代码可能会删除文件等)!由于您的代码没有运行,因此再多的谨慎(例如使用线程init()
或cleanup()
函数保护您的调用)都可能阻止此错误 - 除非代码片段出于其他原因而改变了我们将要调查的行为。
所以,关于你的实际例外。
我以前从未真正听说_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
函数,甚至会打印一条错误消息——然后返回一个非零退出状态,这是你的问题的原因——如果它不能调用它。
因此,我们必须找出为什么模块存在但在检查完程序并且 Python 退出时threading
没有方法。需要一些仪器。我们可以打印出模块在退出时的样子吗?我们可以!该模块在最后几行中通过实例化它定义的一个类来运行它的“主程序” :_shutdown
pylint
pylint
pylint/lint.py
Run
if __name__ == '__main__':
Run(sys.argv[1:])
所以我lint.py
在我的编辑器中打开了——将每个小项目安装在 Python 虚拟环境中的一个很棒的事情是我可以进入并编辑第三方代码以进行快速实验——并print
在底部添加了以下语句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
然后出来了模块的__file__
字符串:threading
***** /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
可能!
这会生成一个mytracer.out
大约 800k 行的文件,每行看起来像这样:
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
用threading
子模块自省一个包时,它就会神秘地中断。” 显然,它是一种非常紧凑的母语。:)
如果您调整跟踪模块以观察sys.modules
实际导入(threading
我将留给读者做一个练习),您将看到它发生在 时SocketServer
,它在分析期间由其他一些标准库模块导入,反过来又试图无辜地导入threading
.
因此,让我们回顾一下正在发生的事情:
pylint
是危险的魔法。
- 作为它的魔法的一部分,如果它看到 you
import foo
,它就会跑去尝试foo.py
在磁盘上查找、解析它,并预测你是从它的命名空间加载有效还是无效的名称。
- [请参阅下面的评论。] 因为您调用
.split()
的返回值RSA.as_pem()
,所以会pylint
尝试自省该as_pem()
方法,该方法又使用该M2Crypto.BIO
模块,该模块又进行调用以诱导pylint
import threading
。
- 作为加载任何模块的一部分
foo.py
,pylint
抛出包含foo.py
on的目录sys.path
,即使该目录在 package中,因此在该目录中的模块在其分析期间具有隐藏同名标准库模块的特权。
- 当 Python 退出时,
M2Crypto.threading
库位于threading
所属的位置令人不安,因为它想运行_shutdown()
.threading
您应该将此作为错误报告给pylint
. 告诉他们我派你去的。astng
logilab.org
如果您决定在它对您执行此操作后继续使用pylint
,那么在这种情况下似乎有两种解决方案:要么不检查调用 的代码M2Crypto
,要么threading
在导入过程中导入-例如,pylint
通过坚持模块有机会在所有兴奋之前抢到插槽并尝试让抢插槽。import threading
pylint/__init__.py
sys.modules['threading']
pylint
M2Crypto/threading.py
总之,我认为作者astng
说得最好:XXX (syt) iirk。的确。