9

我正在运行 lint,如下所示:

$ python -m pylint.lint m2test.py

使用此代码:

import M2Crypto
def f():
    M2Crypto.RSA.new_pub_key("").as_pem(cipher=None).split("\n")

lint 输出以:

Exception AttributeError: '_shutdown' in <module 'threading' from '/usr/lib/python2.7/site-packages/M2Crypto-0.21.1-py2.7-linux-x86_64.egg/M2Crypto/threading.pyc'> ignored

这段代码在运行时工作正常(上面实际上是一个最小的测试用例;但完整版确实有效)。异常被忽略,但Bitten认为这是一个失败,所以在这一步停止。

我尝试在函数定义周围添加 'M2Crypto.threading.init()'/'M2Crypto.threading.cleanup()' ,但这并没有解决问题。

我怎样才能防止这个问题发生?

我在 Debian Lenny x86_64 上使用 M2Crypto 0.21.1、pylint 0.24 和 Python 2.7(也尝试过 2.7.2)。

4

4 回答 4

16

您看到的异常是由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没有方法。需要一些仪器。我们可以打印出模块在退出时的样子吗?我们可以!该模块在最后几行中通过实例化它定义的一个类来运行它的“主程序” :_shutdownpylintpylintpylint/lint.pyRun

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会中毒吗?让我们只看第一行TrueFalse每一行,看看有多少连续行以每个值开头:

$ 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每次有人强迫pylintthreading子模块自省一个包时,它就会神秘地中断。” 显然,它是一种非常紧凑的母语。:)

如果您调整跟踪模块以观察sys.modules实际导入(threading我将留给读者做一个练习),您将看到它发生在 时SocketServer,它在分析期间由其他一些标准库模块导入,反过来又试图无辜地导入threading.

因此,让我们回顾一下正在发生的事情:

  1. pylint是危险的魔法。
  2. 作为它的魔法的一部分,如果它看到 you import foo,它就会跑去尝试foo.py在磁盘上查找、解析它,并预测你是从它的命名空间加载有效还是无效的名称。
  3. [请参阅下面的评论。] 因为您调用.split()的返回值RSA.as_pem(),所以会pylint尝试自省该as_pem()方法,该方法又使用该M2Crypto.BIO模块,该模块又进行调用以诱导pylintimport threading
  4. 作为加载任何模块的一部分foo.pypylint抛出包含foo.pyon的目录sys.path即使该目录在 package中,因此在该目录中的模块在其分析期间具有隐藏同名标准库模块的特权。
  5. 当 Python 退出时,M2Crypto.threading库位于threading所属的位置令人不安,因为它想运行_shutdown().threading

您应该将此作为错误报告给pylint. 告诉他们我派你去的。astnglogilab.org

如果您决定在它对您执行此操作后继续使用pylint,那么在这种情况下似乎有两种解决方案:要么不检查调用 的代码M2Crypto,要么threading在导入过程中导入-例如,pylint通过坚持模块有机会在所有兴奋之前抢到插槽并尝试让抢插槽。import threadingpylint/__init__.pysys.modules['threading'] pylintM2Crypto/threading.py

总之,我认为作者astng说得最好:XXX (syt) iirk。的确。

于 2011-09-14T02:06:07.333 回答
3

非常感谢 Brandon Craig Rhodes 对此进行了追踪并提供了如此详细的帖子。

我已经从 astng 中删除了有问题的行,代码从hg 存储库中可用,直到 logilab-astng 0.23.0 出来。我可以确认这修复了 OP 的 pb。

于 2011-09-16T08:06:04.677 回答
1

这看起来更像是一个黑客,但我认为它有效。复制“as_pem()”的结果并将其拆分。

import M2Crypto
def f():
    M2Crypto.RSA.new_pub_key("").as_pem(cipher=None)[:].split("\n")

我正在使用 Python 2.6.7、M2Crypto 0.21.1、pylint 0.23

于 2011-09-13T05:54:44.497 回答
0

我无法重现(Ubuntu 11.04 64 位上的 pylint 0.24 和 M2Crypto 0.21.1),但有两个建议:

显式初始化线程:

import M2Crypto
def f(): 
    M2Crypto.threading.init()
    M2Crypto.RSA.new_pub_key("").as_pem(cipher=None).split("\n")
    M2Crypto.threading.cleanup()

或者在没有线程的情况下重新编译:

m2crypto = Extension(name = 'M2Crypto.__m2crypto',
                 sources = ['SWIG/_m2crypto.i'],
                 extra_compile_args = ['-DTHREADING'],
                 #extra_link_args = ['-Wl,-search_paths_first'], # Uncomment to build Universal Mac binaries
                 )
于 2011-09-13T01:56:47.847 回答