_shutdown AttributeError (игнорируется) при написании кода, использующего M2Crypto - PullRequest
9 голосов
/ 05 сентября 2011

Я запускаю пух как следует:

$ python -m pylint.lint m2test.py

с этим кодом:

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

Вывод ворса заканчивается на:

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 ()' вокруг определения функции, но это не решило проблему.

Как я могу предотвратить возникновение этой проблемы?

Я использую M2Crypto 0.21.1, pylint 0.24 и Python 2.7 (также пробовал 2.7.2) на Debian Lenny x86_64.

Ответы [ 4 ]

16 голосов
/ 14 сентября 2011

Исключение, которое вы видите, вызвано ошибкой в ​​пакете astng (предположительно «Абстрактное дерево синтаксиса, следующее поколение»?), Которое представляет собой инструментарий, от которого зависит pylint, написанное теми же людьми. Я должен отметить мимоходом, что я всегда призываю людей использовать pyflakes вместо pylint, когда это возможно, потому что это быстро, просто, быстро и предсказуемо, тогда как pylint пытается сделать несколько видов глубокой магии, которые не только медленно, но это может привести именно к таким проблемам. :)

Вот два пакета на PyPI:

http://pypi.python.org/pypi/pylint

http://pypi.python.org/pypi/astng

И обратите внимание, что эта проблема обязательно должна быть ошибкой в ​​pylint и , а не в вашем коде, потому что pylint не не запускает ваш код для того, чтобы создайте свой отчет - представьте себе хаос, который может быть нанесен, если он это сделает (так как код, который вы наносите, может удалить файлы и т. д.)! Поскольку ваш код не запускается, никакие меры предосторожности, такие как защита вашего вызова с помощью многопоточных функций 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 Standard Library, и они имеют специальный интерпретатор Python, чтобы он вызывался.

Как видно из приведенного выше кода, Python спокойно и без жалоб обрабатывает случай, когда модуль threading никогда не импортируется во время выполнения программы. Но если threading действительно импортируется и все еще существует во время завершения работы, то интерпретатор ищет в функции _shutdown и доходит до того, что выводит сообщение об ошибке, а затем возвращает ненулевое состояние выхода, причина из ваших проблем - если он не может позвонить.

Итак, мы должны выяснить, почему модуль threading существует, но не имеет метода _shutdown в тот момент, когда pylint завершает проверку вашей программы и Python завершает работу. Некоторый инструментарий необходим. Можем ли мы распечатать, как выглядит модуль при выходе pylint? Мы можем! Модуль pylint/lint.py в последних нескольких строках выполняет свою «основную программу», создав экземпляр класса Run, который он определил:

if __name__ == '__main__':
    Run(sys.argv[1:])

Итак, я открыл lint.py в своем редакторе - одна из замечательных особенностей установки каждого маленького проекта в виртуальной среде Python заключается в том, что я могу подключаться и редактировать сторонний код для быстрых экспериментов - и добавил следующее print оператор внизу метода __init__() класса Run:

    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, как если бы он содержал модули верхнего уровня? Посмотрим!

Нам нужно больше, яnstrumentation: нам нужно, чтобы Python сообщал нам, когда каталог с M2Crypto в имени появляется в sys.path. Это действительно замедлит ход событий, но давайте добавим функцию трассировки в __init__.py в pylint - потому что это первый модуль, который импортируется при запуске -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, содержащий около 800 тыс. Строк, каждая из которых выглядит примерно так:

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

И, глядя на строки 132 и 133 в файле builder.py, выявляется наш виновник:

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.

Итак, давайте рассмотрим, что происходит:

  1. pylint опасная магия.
  2. В рамках своей магии, если он видит вас import foo, он убегает, пытаясь найти foo.py на диске, проанализировать его и предсказать, загружаете ли вы действительные или недействительные имена из его пространства имен.
  3. [См. Мой комментарий ниже.] Поскольку вы вызываете .split() для возвращаемого значения RSA.as_pem(), pylint пытается проанализировать метод as_pem(), который, в свою очередь, использует модуль M2Crypto.BIO, который в Ход делает звонки, которые побуждают pylint импортировать threading.
  4. При загрузке любого модуля foo.py, pylint выбрасывает каталог, содержащий foo.py в sys.path, , даже если этот каталог находится внутри пакета , и, следовательно, дает модули в этом каталоге привилегия дублирования одноименных модулей стандартной библиотеки при ее анализе.
  5. Когда выходит Python, расстраивается, что библиотека M2Crypto.threading находится там, где принадлежит threading, потому что она хочет запустить метод _shutdown() threading.

Вы должны сообщить об этом как об ошибке людям pylint / astng на logilab.org. Скажи им, что я послал тебя.

Если вы решитечтобы продолжать использовать pylint после того, как он сделал это для вас, в этом случае, похоже, есть два решения: либо не проверять код, вызывающий M2Crypto, либо импортировать threading во время процесса импорта pylintнапример, вставив import threading в pylint/__init__.py, чтобы модуль получил возможность захватить слот sys.modules['threading'] 1196 * до того, как pylint возбудится и попытается M2Crypto/threading.py захватитьвместо слота.

В заключение, я думаю, что автор astng говорит это лучше всего: XXX (syt) iirk.Действительно.

3 голосов
/ 16 сентября 2011

Большое спасибо Брэндону Крейгу Роудсу за то, что он отыскал это и за такой подробный пост.

Я удалил некорректную строку из astng, код доступен из hg репозитория до выхода logilab-astng 0.23.0. И я могу подтвердить, что это исправляет ОП ОП.

1 голос
/ 13 сентября 2011

Это больше похоже на взлом, но я думаю, что это работает.Копирование результата 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

0 голосов
/ 13 сентября 2011

Я не смог воспроизвести (pylint 0.24 и M2Crypto 0.21.1 на Ubuntu 11.04 64bit), но два предложения:

Явная инициализация потоков:

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
                 )
...