subprocess.Popen在外面但不在ipyparallel内部工作?

时间:2016-08-08 22:59:52

标签: python-2.7 subprocess stanford-nlp ipython-parallel

我正在尝试使用ipyparallel并行here中的某些代码。简而言之,我可以使函数在apply_sync()之外正常工作,但我似乎无法让它们在其中工作(我发誓我之前有这个工作,但我找不到一个版本的代码没有破坏)。一个简单的例子:

def test3(fname = '7_1197_.txt'):
    import subprocess
    command = 'touch data/sentiment/' + fname + '.test'
    child = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
    while True:
        out = child.stdout.read(1)
        if out == '' and child.poll() != None:
            return 
test3() #this works, creates a file with the .test extention
results = view.map_sync(test3, speeches) #this doesn't work. No files created.

这是我实际要使用的函数的简短版本。它本身很好用。在apply_sync()中,它会根据java调整htop个进程,但似乎没有从这些进程中获得任何回复。

def test2(fname = '7_1197_.txt'):
    import subprocess

    settings = ' -mx5g edu.stanford.nlp.sentiment.SentimentPipeline'
    inputFile = ' -file data/sentiment/' + fname
    command = 'java ' + settings + inputFile
    child = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
    results = []
    while True:
        out = child.stdout.read(1)
        if out == '' and child.poll() != None:
            return ''.join(results)
        if out != '':
            results.extend(out)
test2() #Works fine, produces output
results = view.map_sync(test2, speeches) #Doesn't work: the results are empty strings.

我尝试了一个返回命令变量的版本。发送到Popen的命令很好,并且在命令行中手动粘贴时可以正常工作。我想也许这只是管道问题,但更改命令以将输出重定向到' > '+fname+'.out'的文件在apply_sync()调用内也不起作用(不生成输出文件)。

我应该怎么做才能从系统回调中获得stdout

1 个答案:

答案 0 :(得分:1)

我看到两个潜在的陷阱。一个用于阻止,一个用于丢失文件。对于丢失的文件,您应确保引擎和本地会话位于同一工作目录中,或确保使用绝对路径。一种在本地和远程同步路径的快速方法:

client[:].apply_sync(os.chdir, os.getcwd())

说:获取本地 cwd,然后在任何地方调用os.chdir,以便我们共享相同的工作目录。如果您在IPython会话中,快捷方式是:

%px cd {os.getcwd()}

至于阻塞,我的第一个想法是:你是否可以在并行运行时使用Python 3?如果是这样,child.stdout.read将返回字节而不是 text 。在Python 2中,str is bytes,所以out == ''将起作用,但在Python 3中,条件out == ''将永远不会是真的,因为b'' != u'',并且您的函数将永远不会返回。 / p>

一些更有用的信息:

  1. stdout.read(N)将读取最多该字节数,并在输出完成时截断。这很有用,因为read(1)将循环许多次,即使输出都等待读取。
  2. 如果输出完成,
  3. stdout.read()将只返回一个空的字节串,所以你只需要在返回之前检查,而不是child.poll()。 (只要您没有在FD上设置NOWAIT,这是正确的,这是一些高级用法)。
  4. 如果要在函数返回之前查看部分输出,可以在sys.stdout上重新显示输出,并在不等待最终结果的情况下查看IPython中的部分输出。
  5. 所以这里有一些函数的实现,具有不同的目标。

    第一个似乎是使用Popen.communicate来完成你当前的目标,如果你实际上不想对部分输出做任何事情和/或你在函数中没有任何关系,那么这是最简单的选择。等待输出:

    def simple(fname = '7_1197_.txt'):
        import subprocess
        command = 'echo "{0}" && touch -v data/sentiment/{0}.test'.format(fname)
        child = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE)
        # if we aren't doing anything with partial outputs,
        # child.communicate() does all of our waiting/capturing for us:
        out, err = child.communicate()
        return out
    

    (包含stderr捕获,使用stderr=subprocess.PIPE或将stderr合并到带有stderr=subprocess.STDOUT的stdout中可能也很有用。)

    这是另一个例子,将stderr收集到stdout中,然后读取块:

    def chunked(fname = '7_1197_.txt'):
        import subprocess
        command = 'echo "{0}" && touch data/sentiment/{0}.test'.format(fname)
        child = subprocess.Popen(command, shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT,
                                )
        chunks = []
        while True:
            chunk = child.stdout.read(80) # read roughly one line at a time
            if chunk:
                chunks.append(chunk)
                continue
            else:
                # read will only return an empty bytestring when output is finished
                break
        return b''.join(chunks)
    

    请注意,我们可以使用if not chunk条件来确定输出何时结束,而不是if chunk == '',因为空字节串是Falsy。如果我们没有对部分输出做某事,那么实际上没有理由使用它而不是上面更简单的.communicate()版本。

    最后,这是一个可以与IPython一起使用的版本,它不是捕获并返回输出,而是重新显示它,我们可以用来在客户端显示部分输出:

    def chunked_redisplayed(fname = '7_1197_.txt'):
        import sys, subprocess
        command = 'for i in {{1..20}}; do echo "{0}"; sleep 0.25; done'.format(fname)
        child = subprocess.Popen(command, shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.STDOUT,
                                )
        while True:
            chunk = child.stdout.read(80) # read roughly one line at a time
            if chunk:
                sys.stdout.write(chunk.decode('utf8', 'replace'))
                continue
            else:
                # read will only return an empty bytestring when output is finished
                break
    

    在客户端中,如果您使用map_async代替map_sync,则可以查看result.stdout,这是目前为止的stdout-streams 列表,所以你可以查看进展情况:

    amr = view.map_async(chunked_redisplayed, speeches)
    amr.stdout # list of stdout text, updated in the background as output is produced
    amr.wait_interactive() # waits and shows progress
    amr.get() # waits for and returns the actual result