【问题标题】:_shutdown AttributeError (ignored) when linting code that uses M2Crypto_shutdown AttributeError(忽略)当 linting 代码使用 M2Crypto
【发布时间】:2011-09-04 23:32:12
【问题描述】:

我正在按如下方式运行 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)。

【问题讨论】:

  • 我可以在 Linux x86_64 上使用 M2Crypto==0.21.1pylint==0.24.0 重现它。
  • @J.F.感谢您的提示-我最初应该包含版本。现已修复。

标签: python m2crypto pylint


【解决方案1】:

您看到的异常是由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 会中毒吗?让我们只看每行的第一个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 的目录应该在路径上。在文件中寻找第一个FalseTrue 的转换,我看到了这个:

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

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

  1. pylint 是危险的魔法。
  2. 作为它魔法的一部分,如果它看到你import foo,然后它会跑去尝试在磁盘上找到foo.py,解析它,并预测你是从它的命名空间加载有效还是无效的名称。
  3. [见我的评论,下面。] 因为你在 RSA.as_pem() 的返回值上调用 .split()pylint 试图内省 as_pem() 方法,该方法又使用 M2Crypto.BIO 模块,在turn 进行调用,诱导pylint 导入threading
  4. 作为加载任何模块 foo.py 的一部分,pylint 会在 sys.path 上抛出包含 foo.py 的目录,即使该目录位于包内,因此会在其中提供模块目录在其分析期间隐藏同名标准库模块的特权。
  5. 当 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。确实。

【讨论】:

  • 很棒的答案,谢谢!顺便说一句,您在 M2Crypto 导入之前插入“导入线程”的建议不起作用。我仍然对某些事情有点不清楚:例如,如果我删除 .split(),为什么错误会消失?我想是危险的 pylint 魔法的一部分。明天有时间我会再研究一下,然后在 logilab 上开一张票。我很确定赏金是你的 :)
  • 谢谢@Tony Meyer!既是为了恭维,也是为了指出我在最后得到了错误的建议:我的意思是说,将import threading 添加到pylint,而不是示例代码!我现在已经确定了答案。谢谢!
  • @Tony Meyer — 要回答您的其他问题,请在 strace -o trace.out 内运行 pylint,然后在结果跟踪上使用 grep 'open.*py.*= [^-]' 以查看每种情况下导入了哪些 Python 模块。您会看到,当您添加 split() 时,pylint 决定自省 as_pem() 方法,因为它现在想要验证其返回类型。要内省as_pem(),它需要导入BIO.py,否则它会忽略它,这会导致最终到达M2Crypto.threading 的40 多个额外导入的级联。
  • 哇。迄今为止我见过的最彻底的答案。我想我会试试pyflakes
【解决方案2】:

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

我已从 astng 中删除了有问题的行,代码可从 hg repository 获得,直到 logilab-astng 0.23.0 发布。我可以确认这修复了 OP 的 pb。

【讨论】:

  • 太棒了!我很高兴对您的项目有所帮助。 :) 如果可以简单地删除该行,这是否意味着它的原始目的现在已经消失了?而且,最大的问题是:项目所有者自己是否知道该行的作者所说的“XXX (syt) iirk”的真正含义?我一直想知道它代表什么!
  • @Brandon:是的,最初的目的已经消失,这是一个关于相对导入的快速而肮脏的修复,后来通过更改其他 api 正确修复了“上下文文件”(即来自哪个模块导入完成)可以给出。 “XXX (syt) iirk”的意思是:“以后很容易找到关于一些 pb / 快速和肮脏修复的注释”(XXX)“由我”(syt)“这很臭”(iirk) :)
【解决方案3】:

这看起来更像是一个 hack,但我认为它有效。复制“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

【讨论】:

  • 代码没有做同样的事情 - 你拆分 t,而原始拆分 t.as_pem(cipher=None)。在这种情况下拆分成单独的行,它仍然会产生错误。
  • 奇怪的是,如果我将 t.as_pem(cipher=None) 的结果复制到另一个变量并用新行拆分它可以工作 (m = t.as_pem(cipher=None)[:].split ("\n"))
【解决方案4】:

我无法重现(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
                 )

【讨论】:

  • 在问题中,我确实尝试过显式初始化线程 - 这似乎是最有可能的罪魁祸首。有机会我会尝试重建 M2Crypto。
  • 你无法复制的东西很有趣。您尝试了哪个版本的 Python?
  • init()/cleanup() 没有帮助。我在 Ubuntu 11.04 64bit 上尝试过 Python 2.6/2.7 M2Crypto 0.20.1/0.21.1
猜你喜欢
  • 2020-12-26
  • 2013-01-28
  • 2021-06-19
  • 1970-01-01
  • 2015-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多