2016-04-11 4 views
0

Ich versuche, subprocess.Popen() korrekt zu funktionieren, aber aus irgendeinem Grund ist der zurückgegebene Wert vollständig falsch.Wie subprocess.Popen richtig funktioniert?

Das Skript öffnet ein FTP-Verbindungsskript, das Dateien von einem Server herunterlädt und dann ein Tupel der erfolgreichen und nicht erfolgreich heruntergeladenen Dateien zurückgibt. Dieses Skript hat vor der Verwendung von subprocess.call() funktioniert, aber ich möchte Popen() verwenden, damit das Skript, das es aufruft, in einem anderen Thread ist und das Hauptprogramm nicht beeinträchtigt.

Hier ist meine Hauptklasse:

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))) 

und hier ist dwnldMedia.py (__init__ Anrufe 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) 

Der Ausgang ich erhalte, ist:

Successful: 
Unsuccessful: None 

Wo successful hat ein Wert von None.

+2

[ 'Popen.communicate()'] (https://docs.python.org/2/ Bibliothek/Subprozess.html # Subprozess.Popen.kommunizieren) gibt den Inhalt von 'stdout' und' stderr' aus dem Prozess zurück - nicht was Ihre 'download()' Methode _returns_ ist. Mit anderen Worten, Sie müssen _write_ die Werte von 'successful' und' successful' in 'sys.stdout' eingeben. Eine Möglichkeit wäre, sie einfach auszudrucken. – martineau

+1

Ich würde empfehlen, dass Sie eine 'stderr = subprocess.PIPE' hinzufügen, wenn Sie irgendwelche Daten über den Fehler-Stream erwarten. –

+0

@martineau, bitte posten Sie Ihre Antwort als Antwort. –

Antwort

0

Wenn Sie die Dinge hatte Arbeit mit subprocess.call() wirklich, könnte man genauso gut nur halten sie mit - da call() verwendet Popen() intern - so ist dwnldMedia.pybereits als separate Unter Prozess ausgeführt wird (was Sie einen neuen genannt thread), so dass dieser Aspekt der Codeausführung nicht geändert wird, indem Sie den Code Popen() direkt aufrufen.

Ob Sie call() oder Popen() + communicate() der Download nicht gleichzeitig auftreten (was ich davon ausgehen, Ihr Ziel ist), weil beide für das Skript warten Ausführung abgeschlossen ist, bevor Sie fortfahren. Für das gleichzeitige Herunterladen benötigen Sie Multitasking mit dem Modul multiprocessing. Da das, was Sie tun, I/O-gebunden ist, könnten gleichzeitige Downloads auch mit den Modulen thread und/oder threading durchgeführt werden (wo es oft einfacher ist, Daten zu teilen, weil alles im selben Prozess läuft).

Mit diesem gesagt, so ist dies tatsächlich eine Antwort auf Ihre Frage, hier ist, wie Sie die Ergebnisse von subprocess.communicate() zurückgegeben und Daten von einem Prozess zum anderen übergeben. Sie können nicht einfach return Ergebnisse von einem Prozess zum anderen, weil sie in separaten Adressräumen sind. Eine Möglichkeit besteht darin, die Daten zwischen ihnen zu "pipen". communicate() sammelt alle empfangenen Daten und gibt sie als Tupel von zwei Strings zurück, wenn sie zurückkehrt, einen für stderr und einen weiteren für stderr.

Das Beispiel verwendet pickle, um die gesendeten Daten in etwas umzuwandeln, das in Python-Objekten auf der Empfängerseite zurückgewiesen werden kann. Das json Modul hat gleich gut funktioniert. Ich musste eine Menge Code aus den Beispielen in Ihrer Frage entfernen, um etwas zu erstellen, das ich ausführen und testen konnte, aber ich habe versucht, die Gesamtstruktur intakt zu halten.

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() 

und hier ist eine abgespeckte Version der download() Methode im dwnldMedia.py Skript:

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

Ich habe versucht, Multiprocessing zu verwenden, aber anstatt das zielgerichtete Skript auszuführen, dupliziert es einfach mein GUI-Fenster 'ftp = multiprocessing.Process (Name =" FTP-Download ", Ziel = mw ._ ['cwd'] +" dwnldMedia.py ")' (wobei mw ._ ['cwd'] das aktuelle Arbeitsverzeichnis ist) –

+0

'multiprocessing' kann schwierig sein. Eine der Regeln dafür ist, dass das Hauptskript einen "if __name__ ==" __main __ 'haben muss: "bewache es um den Teil des Codes, der den Root-Prozess darstellt (weil das Hauptskript von den Unterprozessen "importiert" wird). – martineau