Почему Paramiko зависает, если вы используете его при загрузке модуля?
поместите следующее в файл hello.py (и easy_install paramiko
если у вас этого нет):
hostname,username,password='fill','these','in'
import paramiko
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
заполните первую строку соответствующим образом.
теперь типа
python hello.py
и вы увидите некоторые выходные данные ls.
теперь вместо типа
python
, а затем из типа переводчика
import hello
и вуаля! Он висит! Это unhang если вы обернуть код в функцию foo
и import hello; hello.foo()
вместо.
почему Paramiko зависает при использовании в инициализации модуля? как Парамико вообще знает, что он используется во время инициализации модуля в первую очередь?
3 ответов
Paramiko использует отдельные потоки для базового транспорта. Вы должны никогда есть модуль, который порождает поток в качестве побочного эффекта от импорта. Насколько я понимаю, существует одна блокировка импорта, поэтому, когда дочерний поток из вашего модуля пытается выполнить другой импорт, он может блокировать бесконечно, потому что ваш основной поток все еще держит блокировку. (Есть, вероятно, другие gotchas, о которых я тоже не знаю)
вообще, модули не должны иметь побочные эффекты любой вид при импорте, или вы получите непредсказуемые результаты. Просто отложите исполнение с помощью __name__ == '__main__'
трюк, и все будет хорошо.
[редактирование] Кажется, я не могу создать простой тестовый случай, который воспроизводит этот тупик. Я все еще предполагаю, что это проблема с потоком с импортом, потому что код аутентификации ждет события, которое никогда не срабатывает. Это может быть ошибка в paramiko или python, но хорошей новостью является то, что вы никогда не должны ее видеть, если делаете все правильно ;)
этот хороший пример почему вы всегда хотите уменьшить побочные эффекты, и почему методы функционального программирования будут более превалирующими.
As JimB указал, что это вопрос когда python пытается неявно импортировать str.decode('utf-8')
декодер при первом использовании во время попытки подключения ssh. См.анализ для сведения.
в общем, нельзя подчеркнуть достаточно, что вы должны избегать модуля, автоматически порождающего новые потоки при импорте. Если вы можете, попробуйте избежать кода magic module в целом, поскольку он почти всегда приводит к нежелательным побочные явления.
простое и разумное решение вашей проблемы - как уже упоминалось-это поместить ваш код в
if __name__ == '__main__':
тело, которое будет выполняться только при выполнении этого конкретного модуля и не будет выполняться, когда этот mmodule импортируется другими модулями.(не рекомендуется) другое исправление-просто сделать фиктивный str.декодируйте ('utf-8') в своем коде перед вызовом
SSHClient.connect()
- см. Анализ под.
так в чем же коренная причина этой проблемы?
анализ (простой пароль)
подсказка: если вы хотите отладить поток в Python import и set threading._VERBOSE = True
-
paramiko.SSHClient().connect(.., look_for_keys=False, ..)
неявно порождает новый поток для подключения. Вы также можете увидеть это, если вы включите отладочный вывод дляparamiko.transport
.
[Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L
этот в основном, в составе
SSHClient.connect()
. Когдаclient.py:324::start_client()
вызывается, создается блокировкаtransport.py:399::event=threading.Event()
и поток началtransport.py:400::self.start()
. Обратите внимание, чтоstart()
метод затем выполнит классtransport.py:1565::run()
метод.transport.py:1580::self._log(..)
печатает сообщение нашего журнала "начальный поток", а затем переходит кtransport.py:1584::self._check_banner()
.check_banner
делает одну вещь. Он извлекает SSH-баннер (первый ответ от сервера)transport.py:1707::self.packetizer.readline(timeout)
(обратите внимание, что тайм-аут-это просто тайм-аут чтения сокета), проверяет наличие linefeed в конце и иное время.в случае получения баннера сервера, он пытается utf-8 декодировать строку ответа
packet.py:287::return u(buf)
и вот где затор происходит. Theu(s, encoding='utf-8')
делает str.декодировать ('utf-i') и неявно импортироватьencodings.utf8
наencodings:99
viaencodings.search_function
в конечном итоге в тупик импорта.
таким образом, грязным исправлением было бы просто импортировать декодер utf-8 один раз, чтобы не блокируйте этот импорт specifiy из-за побочных эффектов импорта модуля. (''.decode('utf-8')
)
исправить
dirty fix - не рекомендуется
import paramiko
hostname,username,password='fill','these','in'
''.decode('utf-8') # dirty fix
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
определить
import paramiko
if __name__ == '__main__':
hostname,username,password='fill','these','in'
c = paramiko.SSHClient()
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
c.connect(hostname=hostname, username=username, password=password)
i,o,e = c.exec_command('ls /')
print(o.read())
c.close()
"".декодирование ("utf-8") не сработало для меня, я закончил это делать.
from paramiko import py3compat
# dirty hack to fix threading import lock (issue 104) by preloading module
py3compat.u("dirty hack")
У меня есть обертка для paramiko с этим реализованным. https://github.com/bucknerns/sshaolin