【问题标题】:How to get subprocess.Popen to work correctly?如何让 subprocess.Popen 正常工作?
【发布时间】:2016-04-11 00:38:58
【问题描述】:

我正在尝试让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__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

【问题讨论】:

  • Popen.communicate() 从进程中返回stdoutstderr 的内容——而不是你的download() 方法返回。换句话说,您需要将successfulunsuccessful 的值写入sys.stdout。一种方法是简单地print 他们出去。
  • 如果您希望有任何数据通过错误流,我建议您添加 stderr=subprocess.PIPE
  • @martineau,请将您的答案作为答案发布。
  • 它不是“另一个线程”,它是一个子进程。
  • 哦..我以为这就像打开一个新线程。你可以使用什么库来打开一个新线程?

标签: python python-2.7 subprocess popen


【解决方案1】:

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

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

话虽如此,所以这实际上是对您问题的回答,以下是如何使用从subprocess.communicate() 返回的结果并将数据从一个进程传递到另一个进程。您不能简单地将return 结果从一个进程传递到另一个进程,因为它们位于不同的地址空间中。一种方法是在它们之间“传输”数据。 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()

这是download() 脚本中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'])

【讨论】:

  • 我尝试过使用多处理,但没有运行目标脚本,它只是复制了我的 GUI 窗口 ftp = multiprocessing.Process(name="FTP download",target=mw._['cwd']+"dwnldMedia.py") (其中 mw._['cwd'] 是当前工作目录)
  • multiprocessing 可能很棘手。使用它的规则之一——听起来你可能没有遵循——是主脚本必须在代表根进程的代码部分周围有一个 if __name__ == '__main__': 保护(因为主脚本是 @ 987654347@ed 由子进程)。
猜你喜欢
  • 1970-01-01
  • 2015-05-08
  • 2020-08-27
  • 2018-06-11
  • 2016-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多