如何让 subprocess.Popen 正常工作?

How to get subprocess.Popen to work correctly?

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

该脚本打开一个 FTP 从服务器下载文件的连接脚本,然后 returns 一个包含成功和未成功下载文件的元组。该脚本在使用 subprocess.call() 之前可以正常工作,但我想使用 Popen() 以便它调用的脚本位于另一个线程中并且不会干扰主程序。

这是我的主要内容 class:

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__init__ 调用 download()):

def download(self):
    #print("connected")
    self.server = FTP(**self.serverDetails)
    self.server.login(**self.userDetails)

    self.server.cwd("/public_html/uploads") #changing to /pub/unix
    #print "File List: \n"
    files = []
    successful = [0]
    unsuccessful = [0]
    self.server.retrlines("NLST",files.append)
    for f in files:
        if(f != '.' and f != '..'):
            #print("downloading:\t"+f)
            local_filename = os.path.join(mw._['cwd']+"media", f)
            with open(local_filename, "wb") as i:
                self.server.retrbinary("RETR " + f, i.write)
                #print("\t| Success")
                successful.append(f)
    for f in files:
        if(f != '.' and f != '..' and f not in successful):
            unsuccessful.append(f)
    return (successful, unsuccessful)

我得到的输出是:

Successful:
Unsuccessful:   None

其中 successful 的值为 None

如果您确实有使用 subprocess.call() 的东西,您不妨继续使用它 — 因为 call() 在内部使用 Popen() — 所以 dwnldMedia.py已经 运行 作为一个单独的子进程(你所谓的新线程),所以代码执行的这方面不会通过直接让您的代码调用 Popen() 来更改。

无论您使用 call() 还是 Popen()+communicate() 下载都不会同时发生(我假设这是您的目标),因为两者都等待脚本完成执行后再继续.对于并发下载,您需要使用 multiprocessing 模块进行多任务处理。由于您正在做的是 I/O 绑定,并发下载也可以使用 thread and/or threading 模块完成(共享数据通常更简单,因为它是所有在同一进程内)。

话虽如此,这实际上是对您问题的回答,这里介绍了如何使用从 subprocess.communicate() 返回的结果并将数据从一个进程传递到另一个进程。您不能简单地将 return 结果从一个进程传递到另一个进程,因为它们位于不同的地址空间中。一种方法是 "pipe" 它们之间的数据。 communicate() 收集所有收到的数据,returns 当它 returns 时,它作为两个字符串的元组,一个用于 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'])