Исключение, которое вы видите, вызвано ошибкой в пакете 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
.
Итак, давайте рассмотрим, что происходит:
pylint
опасная магия.
- В рамках своей магии, если он видит вас
import foo
, он убегает, пытаясь найти foo.py
на диске, проанализировать его и предсказать, загружаете ли вы действительные или недействительные имена из его пространства имен.
- [См. Мой комментарий ниже.] Поскольку вы вызываете
.split()
для возвращаемого значения RSA.as_pem()
, pylint
пытается проанализировать метод as_pem()
, который, в свою очередь, использует модуль M2Crypto.BIO
, который в Ход делает звонки, которые побуждают pylint
импортировать threading
.
- При загрузке любого модуля
foo.py
, pylint
выбрасывает каталог, содержащий foo.py
в sys.path
, , даже если этот каталог находится внутри пакета , и, следовательно, дает модули в этом каталоге привилегия дублирования одноименных модулей стандартной библиотеки при ее анализе.
- Когда выходит 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.Действительно.