如何让python subprocess.Popen先看到select.poll后又看不到?(select 'module'对象没有属性'poll')
我正在使用Yelp的一个很棒的库mrjob,来在亚马逊的弹性Map Reduce上运行我的Python程序。这个库依赖于Python标准库中的subprocess模块。在我的Mac上,使用Python 2.7.2,一切都运行得很好。
但是,当我把完全相同的代码切换到Ubuntu LTS 11.04上,仍然使用Python 2.7.2时,我遇到了一些奇怪的事情:
mrjob加载了任务,然后尝试通过subprocess与它的子进程进行通信,但出现了这个错误:
File "/usr/local/lib/python2.7/dist-packages/mrjob-0.3.1-py2.7.egg/mrjob/emr.py", line 1212, in _build_steps steps = self._get_steps() File "/usr/local/lib/python2.7/dist-packages/mrjob-0.3.1-py2.7.egg/mrjob/runner.py", line 1003, in _get_steps stdout, stderr = steps_proc.communicate() File "/usr/lib/python2.7/subprocess.py", line 754, in communicate return self._communicate(input) File "/usr/lib/python2.7/subprocess.py", line 1302, in _communicate stdout, stderr = self._communicate_with_poll(input) File "/usr/lib/python2.7/subprocess.py", line 1332, in _communicate_with_poll poller = select.poll() AttributeError: 'module' object has no attribute 'poll'
看起来这个问题是出在subprocess上,而不是mrjob。
我深入查看了/usr/lib/python2.7/subprocess.py,发现它在导入时运行了:
if mswindows: ... snip ... else: import select _has_poll = hasattr(select, 'poll')
通过编辑这个文件,我确认它确实将_has_poll设置为True。这是正确的;在命令行上也很容易验证。
然而,当执行进程到达使用Popen._communicate_with_poll时,select模块似乎发生了变化!在它尝试使用select.poll()之前,我打印了dir(select)来查看。
['EPOLLERR', 'EPOLLET', 'EPOLLHUP', 'EPOLLIN', 'EPOLLMSG', 'EPOLLONESHOT', 'EPOLLOUT', 'EPOLLPRI', 'EPOLLRDBAND', 'EPOLLRDNORM', 'EPOLLWRBAND', 'EPOLLWRNORM', 'PIPE_BUF', 'POLLERR', 'POLLHUP', 'POLLIN', 'POLLMSG', 'POLLNVAL', 'POLLOUT', 'POLLPRI', 'POLLRDBAND', 'POLLRDNORM', 'POLLWRBAND', 'POLLWRNORM', '__doc__', '__name__', '__package__', 'error', 'select']
居然没有叫'poll'的属性!这怎么可能消失了呢?
所以,我硬编码将_has_poll设置为False,然后mrjob就高高兴兴地继续工作,在AWS EMR上运行我的任务,subprocess使用communicate_with_select……而我却不得不忍受一个被我手动修改过的标准库……
有什么建议吗?:-)
2 个回答
抱歉我写了一个完整的回答,而不是评论,因为这样我会失去代码的缩进。
我不能直接帮你,因为你的代码似乎有些特别,但我可以帮你找出问题。你可以利用Python模块可以是任意对象这个特点,试试下面的代码:
class FakeModule(dict):
def __init__(self, origmodule):
self._origmodule = origmodule
self.__all__ = dir(origmodule)
def __getattr__(self, attr):
return getattr(self._origmodule, attr)
def __delattr__(self, attr):
if attr == "poll":
raise RuntimeError, "Trying to delete poll!"
self._origmodule.__delattr__(attr)
def replaceSelect():
import sys
import select
fakeselect = FakeModule(select)
sys.modules["select"] = fakeselect
replaceSelect()
import select
del select.poll
这样你会得到类似下面的输出:
Traceback (most recent call last):
File "domy.py", line 27, in <module>
del select.poll
File "domy.py", line 14, in __delattr__
raise RuntimeError, "Trying to delete poll!"
RuntimeError: Trying to delete poll!
在你的代码中调用replaceSelect(),你应该能得到一个错误追踪信息,这样你就能知道是谁在删除poll(),从而理解原因。
我希望我提供的FakeModule实现足够好,否则你可能需要对它进行一些修改。
我遇到过类似的问题,发现 gevent 会把 Python 自带的 select
模块替换成 gevent.select.select
,而这个替换后的模块没有 poll
方法(因为这个方法会阻塞)。不过,出于某种原因,gevent 默认并不会对 subprocess
进行修改,而 subprocess
是使用 select.poll
的。
一个简单的解决办法是把 subprocess
替换成 gevent.subprocess
:
import gevent.monkey
gevent.monkey.patch_all(subprocess=True)
import sys
import gevent.subprocess
sys.modules['subprocess'] = gevent.subprocess
如果你在导入 mrjob 库之前这样做,应该就能正常工作了。