2016-04-11 53 views
0

我试图让subprocess.Popen()正常工作,但由于某种原因返回的值是完全错误的。如何让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

+2

['Popen.communicate()'](https://docs.python.org/2/库/ subprocess.html#subprocess.Popen。沟通)从过程返回'stdout'和'stderr'的内容 - 而不是你的'download()'方法_returns_。换句话说,你需要将'成功'和'不成功'的值写入'sys.stdout'。这样做的一种方法是简单地将它们打印出来。 – martineau

+1

如果你期望任何数据通过错误流,我建议你添加一个'stderr = subprocess.PIPE'。 –

+0

@马蒂诺,请张贴您的答案作为答案。 –

回答

0

如果你真的工作使用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()方法在dwnldMedia.py脚本一个精简版:

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']) 
+0

我试过使用多重处理,但不是运行目标脚本,它只是复制我的GUI窗口'ftp = multiprocessing.Process(name =“FTP download”,target = mw ._ ['cwd'] +“dwnldMedia.py “) (其中mw ._ ['cwd']是当前工作目录) –

+0

'multiprocessing'可能会非常棘手。使用它的规则之一 - 听起来像你可能没有遵循 - 是主脚本必须在表示根进程的代码部分附近有一个'if __name__ =='__main __':'guard (因为主脚本是由子进程导入的)。 – martineau