Python 如何使subprocess.Popen正常工作?

Python 如何使subprocess.Popen正常工作?,python,python-2.7,subprocess,popen,Python,Python 2.7,Subprocess,Popen,我正在尝试使subprocess.Popen()正常工作,但由于某些原因,返回的值完全错误 该脚本打开从服务器下载文件的FTP连接脚本,然后返回成功下载和未成功下载文件的元组。这个脚本在使用subprocess.call()之前已经工作过,但是我想使用Popen(),这样它调用的脚本就在另一个线程中,不会干扰主程序 这是我的主要课程: def FTPDownload(self): try: ftpReq = subprocess.Popen(['Python', mw._

我正在尝试使
subprocess.Popen()
正常工作,但由于某些原因,返回的值完全错误

该脚本打开从服务器下载文件的FTP连接脚本,然后返回成功下载和未成功下载文件的元组。这个脚本在使用
subprocess.call()
之前已经工作过,但是我想使用
Popen()
,这样它调用的脚本就在另一个线程中,不会干扰主程序

这是我的主要课程:

def FTPDownload(self):
    try:
        ftpReq = subprocess.Popen(['Python', mw._['cwd']+"dwnldMedia.py"],
                                  shell=True,
                                  stdout=subprocess.PIPE)
        successful, unsuccessful = ftpReq.communicate()
        self.consPrompt("Successful:\t"+str(successful))
        self.consPrompt("Unsuccessful:\t"+str(unsuccessful))
    except subprocess.CalledProcessError as e:
        self.consPrompt((cp._['E0']).format(str(e)))
这里是
dwnldMedia.py
调用
下载()
):

我得到的结果是:

成功:
不成功:无

如果
successful
的值为
None
如果您确实使用了一些东西,那么您最好继续使用它-因为
call()
在内部使用
Popen()
,所以
dwnldMedia.py
已经作为一个单独的子进程运行了(您称之为新线程),这样代码执行的这一方面就不会因为直接调用
Popen()
而改变

无论您使用
call()
还是
Popen()
+
communicate()
下载都不会同时进行(我假设这是您的目标),因为两者都会等待脚本完成执行后再继续。对于并发下载,您需要使用
多处理
模块执行多任务。由于您所做的是I/O绑定的,因此也可以使用
线程
和/或
线程
模块来完成并发下载(在这些模块中,共享数据通常比较简单,因为数据都在同一进程中)

话虽如此,这实际上是对您问题的回答,下面是如何使用从
subprocess.communicate()
返回的结果,并将数据从一个进程传递到另一个进程。您不能简单地将
结果从一个进程返回到另一个进程,因为它们位于不同的地址空间中。一种方法是在它们之间“管道化”数据
communicate()
收集接收到的所有数据,并在返回时将其作为两个字符串的元组返回,一个用于
stderr
,另一个用于
stderr

该示例使用
pickle
将发送的数据转换为可以在接收端的Python对象中转回的内容。
json
模块同样运行良好。我不得不从您问题中的示例中删除大量代码,以制作一些可以运行和测试的东西,但我已尝试在下面的内容中保持整体结构的完整性

import cPickle as pickle
import subprocess

class SomeClass(object):
    def FTPDownload(self):
        try:
            # The -u argument puts stdin, stdout and stderr into binary mode
            # (as well an makes them unbuffered). This is needed to avoid
            # an issue with writing pickle data to streams in text mode
            # on Windows.
            ftpReq = subprocess.Popen(['python', '-u', 'dwnldMedia.py'],
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
            stdout, stderr = ftpReq.communicate()
            if stdout:
                # convert object returned into a Python obj
                results = pickle.loads(stdout)
                print('  successful: {successful}'.format(**results))
                print('unsuccessful: {unsuccessful}'.format(**results))
            if stderr:
                print("stderr:\n{}".format(stderr))
        except subprocess.CalledProcessError as exception:
            print('exception: {}'.format(str(exception)))

if __name__ == '__main__':
    instance = SomeClass()
    instance.FTPDownload()
下面是
dwnldMedia.py
脚本中的
download()
方法的精简版本:

import cPickle as pickle
from random import randint  # for testing
import os

# needed if not run in -u mode
#if os.name == 'nt':  # put stdout into binary mode on Windows
#    import sys, msvcrt
#    msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)

class OtherClass(object):
    def __init__(self, files):
        self.files = files
        self.download()

    def download(self):
        files = [fn for fn in self.files if fn != '.' and fn != '..']
        successful = []
        unsuccessful = []
        for fn in files:
            if randint(0, 1) % 2:  # simulate random download success
                successful.append(fn)
        for fn in files:
            if fn not in successful:
                unsuccessful.append(fn)
        results = {  # package lists into single object
            'successful': successful,
            'unsuccessful': unsuccessful
        }
        print(pickle.dumps(results))  # send object by writing it to stdout

instance = OtherClass(['.', '..', 'file1', 'file2', 'file3', 'file4'])

从进程返回
stdout
stderr
的内容,而不是
download()
方法返回的内容。换句话说,您需要将
successful
successful
的值写入
sys.stdout
。一种方法是将它们打印出来。
stderr=subprocess.PIPE
如果您希望错误流中有任何数据,我建议您添加一个
stderr=subprocess.PIPE
。@martineau,请将您的答案作为答案发布。这不是“另一个线程”,而是一个子流程。噢。。我觉得这就像打开了一条新的线。您可以使用哪个库打开新线程?我尝试过使用多处理,但没有运行目标脚本,它只是复制了我的GUI窗口
ftp=multiprocessing.Process(name=“ftp download”,target=mw.\u['cwd']+“dwnldMedia.py”)
(其中mw.['cwd']是当前的工作目录)
多处理
可能很棘手。使用它的规则之一——听起来你可能没有遵循——是主脚本必须有一个
如果uuuu name_uuuu=='uuuuu main_uuu':
围绕表示根进程的代码部分进行保护(因为主脚本是由子进程导入的
import
)。
import cPickle as pickle
from random import randint  # for testing
import os

# needed if not run in -u mode
#if os.name == 'nt':  # put stdout into binary mode on Windows
#    import sys, msvcrt
#    msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)

class OtherClass(object):
    def __init__(self, files):
        self.files = files
        self.download()

    def download(self):
        files = [fn for fn in self.files if fn != '.' and fn != '..']
        successful = []
        unsuccessful = []
        for fn in files:
            if randint(0, 1) % 2:  # simulate random download success
                successful.append(fn)
        for fn in files:
            if fn not in successful:
                unsuccessful.append(fn)
        results = {  # package lists into single object
            'successful': successful,
            'unsuccessful': unsuccessful
        }
        print(pickle.dumps(results))  # send object by writing it to stdout

instance = OtherClass(['.', '..', 'file1', 'file2', 'file3', 'file4'])