如何让python subprocess.Popen先看到select.poll后又看不到?(select 'module'对象没有属性'poll')

11 投票
2 回答
3301 浏览
提问于 2025-04-17 12:04

我正在使用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 个回答

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实现足够好,否则你可能需要对它进行一些修改。

5

我遇到过类似的问题,发现 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 库之前这样做,应该就能正常工作了。

撰写回答