将以下内容放入文件中 你好.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() 反而。

为什么在模块初始化中使用 Paramiko 时会挂起? Paramiko 是如何知道它在模块初始化过程中被使用的呢?

有帮助吗?

解决方案

的paramiko使用单独的线程为底层传输。 你应该的从不的有产生一个线程作为进口的副作用的模块。据我了解,有一个单一的进口可用的锁,所以当从模块子线程尝试另一个导入,就可以无限期阻塞,因为你的主线程仍然持有锁。 (有一些我不知道的太多可能其他陷阱)

在一般情况下,模块不应该导入时有任何形式的副作用,或者你会得到不可预知的结果。牵住了与__name__ == '__main__'招执行,你会没事的。

[编辑] 我似乎无法创建重现这一僵局一个简单的测试用例。我仍然认为这是一个线程问题与进口,因为该授权码正在等待永远不会触发一个事件。这可能是的paramiko,或Python的一个bug,但好消息是,你应该不会看到它,如果你做正确的事情;)

这是一个很好的例子,为什么你总是希望尽量减少副作用,以及为什么函数式编程技术正变得越来越普遍。

其他提示

作为 吉姆·B 指出这是一个 进口问题 当 python 尝试隐式导入时 str.decode('utf-8') 在 ssh 连接尝试期间首次使用时的解码器。看 分析 部分了解详细信息。

一般来说,无论怎样强调都应该避免让模块在导入时自动生成新线程。如果可以的话,尽量避免使用魔法模块代码,因为它几乎总是会导致不必要的副作用。

  1. 正如已经提到的,解决您的问题的简单而明智的方法是将您的代码放入 if __name__ == '__main__': body 只在执行该特定模块时才会执行,而当该 mmodule 被其他模块导入时不会执行。

  2. (不推荐)另一个修复方法是在调用之前在代码中执行一个虚拟 str.decode('utf-8') SSHClient.connect() - 请参阅下面的分析。

那么这个问题的根本原因是什么呢?

分析(简单密码验证)

暗示:如果你想在 python import 和 set 中调试线程 threading._VERBOSE = True

  1. paramiko.SSHClient().connect(.., look_for_keys=False, ..) 隐式地为您的连接生成一个新线程。如果您打开调试输出,您也可以看到这一点 paramiko.transport.

[Thread-5 ] [paramiko.transport ] DEBUG : starting thread (client mode): 0x317f1d0L

  1. 这基本上是作为一部分完成的 SSHClient.connect(). 。什么时候 client.py:324::start_client() 被调用,创建一个锁 transport.py:399::event=threading.Event() 并且线程已启动 transport.py:400::self.start(). 。请注意, start() 然后方法将执行类的 transport.py:1565::run() 方法。

  2. transport.py:1580::self._log(..) 打印我们的日志消息“正在启动线程”,然后继续 transport.py:1584::self._check_banner().

  3. check_banner 做一件事。它检索 ssh 横幅(来自服务器的第一个响应) transport.py:1707::self.packetizer.readline(timeout) (请注意,超时只是一个套接字读取超时),在结尾处检查lineFeed,否则会出来。

  4. 如果收到服务器横幅,它会尝试对响应字符串进行 utf-8 解码 packet.py:287::return u(buf) 这就是僵局发生的地方。这 u(s, encoding='utf-8') 执行 str.decode('utf-i') 并隐式导入 encodings.utf8encodings:99 通过 encodings.search_function 最终陷入进口僵局。

因此,一个肮脏的修复方法是只导入一次 utf-8 解码器,以免由于模块导入副作用而阻止该特定导入。(''.decode('utf-8'))

使固定

脏修复 - 不建议

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

参考 帕拉米科问题跟踪器:第104期

“” 解码( “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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top