Поместите следующее в файл 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
и вуаля! Он висит! Он отключится, если вы оберните код в функцию foo
и сделайте import hello; hello.foo()
вместо этого.
Почему Pariko зависает при использовании в инициализации модуля? Как Paramiko даже знает, что он используется во время инициализации модуля в первую очередь?
Ответ 1
Парамико использует отдельные потоки для основного транспорта.
У вас никогда не должно быть модуля, который порождает поток как побочный эффект импорта. Насколько я понимаю, существует единственная блокировка импорта, поэтому, когда дочерний поток из вашего модуля пытается выполнить другой импорт, он может блокироваться бесконечно, потому что ваш основной поток по-прежнему удерживает блокировку. (Вероятно, есть другие ошибки, о которых я тоже не знаю)
В целом, при импорте модули не должны иметь побочных эффектов, или вы получите непредсказуемые результаты. Просто уберите исполнение с трюком __name__ == '__main__'
, и все будет в порядке.
[EDIT]
Я не могу создать простой тестовый пример, который воспроизводит этот тупик. Я все еще полагаю, что это проблема с потоком с импортом, потому что код auth ожидает событие, которое никогда не срабатывает. Это может быть ошибка в paramiko или python, но хорошая новость заключается в том, что вы никогда не должны ее видеть, если делаете что-то правильно;)
Это хороший пример, почему вы всегда хотите минимизировать побочные эффекты, и почему методы функционального программирования становятся все более распространенными.
Ответ 2
Как JimB указал, что это проблема импорта, когда python пытается неявно импортировать декодер str.decode('utf-8')
при первом использовании во время ssh попытка подключения. Подробнее см. Раздел "Анализ".
В целом нельзя не подчеркнуть, что вам следует избегать наличия модуля, автоматически создающего новые потоки при импорте. Если можно, старайтесь избегать кода магического модуля вообще, поскольку он почти всегда приводит к нежелательным побочным эффектам.
-
Простое и правильное решение вашей проблемы - как уже упоминалось - заключается в том, чтобы поместить ваш код в тело if __name__ == '__main__':
, которое будет выполняться только в том случае, если вы выполните этот конкретный модуль и не будете выполняться, когда этот mmodule импортируется другими модулями.
-
(не рекомендуется) Еще одно исправление заключается в том, чтобы просто сделать dummy str.decode('utf-8') в вашем коде, прежде чем вы вызовете SSHClient.connect()
- см. анализ ниже.
Итак, какова основная причина этой проблемы?
Анализ (простой пароль)
Совет. Если вы хотите отлаживать потоки в импорте python и устанавливать 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)
(обратите внимание, что тайм-аут - это только тайм-аут чтения сокета), проверяет соответствие строки в конце
и в противном случае истекает время.
-
Если серверный баннер был принят, он пытается utf-8 декодировать строку ответа packet.py:287::return u(buf)
, и в этом случае происходит тупиковая ситуация. u(s, encoding='utf-8')
выполняет str.decode('utf-i') и неявно импортирует encodings.utf8
в encodings:99
через encodings.search_function
, заканчивая в тупике импорта.
Таким образом, грязным решением было бы просто импортировать декодер utf-8 один раз, чтобы не блокировать этот специфический импорт из-за побочных эффектов импорта модуля. (''.decode('utf-8')
)
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()
ref paramico issue tracker: проблема 104
Ответ 3
". decode (" 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