Erst das Geschäftliche, dann das Vergnügen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

ich werde von einigen Regulars darauf hingewiesen, dass ich GUI und Logik sauber voneinander trennen soll. Ist dies aber nicht nur begrenzt möglich? Ich stütze mich mal wieder auf meinen Quelltext - konkret: auf die run()-Funktion im QThread (Zeile 43-84). Würde ich also die Funktion innerhalb von run extrahieren, sähe dies dann wie folgt aus:

Code: Alles auswählen

FILE_NAME = "downloader.py"

try:
    import os
    import sys
    import requests
    print "STATUS [OK]  ", FILE_NAME
    print "STATUS [MESSAGE]  (", FILE_NAME, "): All modules are imported"
except ImportError as ImpErr:
    print "STATUS [FAILED]  ", FILE_NAME
    print "STATUS [MESSAGE]  (", FILE_NAME, "): ", ImpErr
    raise

def download(file_url, temp_location):
    try:
        download_file = requests.get(file_url, stream=True)

    except (requests.exceptions.URLRequired,
                requests.exceptions.ConnectionError,
                requests.exceptions.HTTPError,
                requests.exceptions.Timeout,
                requests.exceptions.ConnectTimeout,
                requests.exceptions.ReadTimeout), g:
        print 'Could not download ', g
        self.error_http.emit()
    else:
        file_size = int(requests.head(file_url).headers.get('content-length', [0]))
        print "%s Byte" %file_size
        result = file_size / (1024*5)
        print result
        chunk_size = int(result)
        downloaded_bytes = 0
        try:
            with open(temp_location, 'wb') as fd:
                for chunk in download_file.iter_content(chunk_size):
                    fd.write(chunk)
                    downloaded_bytes = fd.tell()
                    print (float(downloaded_bytes)/file_size*100)
                    #return (float(downloaded_bytes)/file_size*100)
        except IOError as IoErr:
            print IoErr
        else:
            print "Finish"
            return 'Done'
            #self.finished_download.emit()
            #self.finished_thread.emit()

if __name__ == '__main__':
    base_path = os.path.dirname(os.path.abspath(__file__))
    temp_path = os.path.join(base_path, 'temp', 'example-app-0.3.win32.zip')
    download_url = 'http://sophus.bplaced.net/download/example-app-0.3.win32.zip'
    download(download_url, temp_path)
Und die run()-Funktion im QThread dann so:

Code: Alles auswählen

from xarphus.core.downloader import download
class Download_Thread(QThread):
    finished_thread = pyqtSignal()
    error_http = pyqtSignal()
    finished_download = pyqtSignal()
    notify_progress = pyqtSignal(int)

    def __init__(self, location, link, parent=None):
        QThread.__init__(self, parent)

        self.url = link
        self.location = location
        print self.location

        self._run_semaphore = QSemaphore(1)

    def run(self):
        return_value = download(self.url, self.location)
        print return_value 
        if return_value == "Done":
            self.finished_download.emit()
Wenn die download()-Funktion am Ende fertig ist (Zeile 44), gibt die Funktion einen Wert zurück, hier 'Done'. In der run()-Funktion frage ich nach dem Wert return_value ob in dieser 'Done' steht, und wenn ja, wird ein Emit-Signal abgesetzt. Soweit klappt es auch alles. Aber wo ich allerdings ein Problem sehe, ist, das man nicht mit der Progressbar arbeiten kann. Denn würde ich Zeile 39 dekommentieren - also aus dem Kommentar rausnehmen, so würde die Schleife nur ein einziges Mal durchlaufen, da der return die Funktion beendet.

Wenn ich es also richtig verstehe, dann muss ich gezwungendermaßen die download-Funktion in der run()-Funktion belassen, so wie es im Quelltext, den ich verlinkt habe, zu sehen ist, und eine klare Trennung wäre hier nicht möglich? Oder habe ich etwas übersehen?

Mir geht es nicht um den Download-QThread per se, sondern um die Handhabung der Funktion. Mir fiel auf Anhieb keine weitere Funktion ein, in welcher innerhalb der Funktion nicht nur eine Sache kurz erledigt und am Ende der Wert zurückgegeben wird, sondern wo innerhalb der Funktionen viele Zwischenschritte gibt. Und diese Download-Funktion nahm ich nur als ein Beispiel, um zu zeigen, dass ich da ein Verständnis-Problem habe, inwiefern ich in solcher Konstellation mit einer Progressbar arbeiten könnte, wenn ich die Funktion extrahiere.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

@Sophus: das Problem ist, dass download keine Funktion ist, die sequenziell abläuft, sondern es gibt parallel noch einen weiteren Thread, der Status-Informationen abfragen will. Das ließe sich dadurch auflösen, dass Du als weiteren Parameter eine call-back-Function oder eine Queue übergibst. Deine Funktion hat übrigens noch eine fehlerhafte Fehlerbehandlung, benutzt undefinierte Variablen und gibt noch Zeugs auf dem Bildschirm aus.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@Sirius3: Der Download sollte nur als ein Beispiel dienen. Man könnte dieses Beispiel auch auf andere Situationen übertragen, in welcher man "Zwischenergebnisse" auf die GUI übertragen will. Zum Beispiel beim Entpacken einer größeren zpi-Datei, beim Kopieren einer größeren Datei von einem Ort zum anderen, und all diese Vorgänge dann über die Progressbar ausgeben.

Mir ist nur aufgefallen, dass ich mit einer return()-Methode nicht weiter komme. Aber du hast Call-Back oder Queue angesprochen. Ich muss ehrlich gestehen, dass ich keine Ahnung habe, wie du es meinst.

Aber die Fehlerhafte Fehlermeldung, welche genau meinst du?

EDIT:
Ich habe schon mal angefangen, die Queue zu importieren, zu übertragen...

Code: Alles auswählen

FILE_NAME = "downloader2.py"

try:
    import os
    import requests
    import sys
    import Queue
    from xarphus.core.downloader import download
    print "STATUS [OK]  ", FILE_NAME, ": All modules are imported"
except ImportError as ImpErr:
    print "STATUS [FAILED]  ", FILE_NAME, ": ", ImpErr


try:
    from PyQt4.QtCore import QThread, pyqtSignal, Qt, QSemaphore
    from PyQt4.QtGui import QVBoxLayout, QPushButton, QDialog, QProgressBar, QApplication, QMessageBox

    print "STATUS [OK]  ", FILE_NAME, ": All modules are imported from PyQt4"
except ImportError:
    from PyQt4.QtCore import QThread, pyqtSignal, Qt, QSemaphore
    from PyQt4.QtGui import QVBoxLayout, QPushButton, QDialog, QProgressBar, QApplication, QMessageBox

    print "STATUS [OK]  ", FILE_NAME, ": All modules are imported from PySide"

class Download_Thread(QThread):
    finished_thread = pyqtSignal()
    error_http = pyqtSignal()
    finished_download = pyqtSignal()
    notify_progress = pyqtSignal(int)

    def __init__(self, location, link, parent=None):
        QThread.__init__(self, parent)

        self.url = link
        self.location = location
        print self.location

        self.queue = Queue.Queue()

        self._run_semaphore = QSemaphore(1)

    def run(self):
        me = download(self.url, self.location, self.queue)
- Zeile 7 wird Queue importiert
- Zeile 38 wird die Queue()-Klasse instanziiert und an das Attribut self.queue gebunden.
- Zeile 43 wird das Attribut in Form von Parameter an die Funktion download übergeben

Code: Alles auswählen

def download(file_url, temp_location, queue):
    try:
        download_file = requests.get(file_url, stream=True)

    except (requests.exceptions.URLRequired,
                requests.exceptions.ConnectionError,
                requests.exceptions.HTTPError,
                requests.exceptions.Timeout,
                requests.exceptions.ConnectTimeout,
                requests.exceptions.ReadTimeout), g:
        print 'Could not download ', g
        #self.error_http.emit()
    else:
        file_size = int(requests.head(file_url).headers.get('content-length', [0]))
        print "%s Byte" %file_size
        result = file_size / (1024*5)
        print result
        chunk_size = int(result)
        downloaded_bytes = 0
        try:
            with open(temp_location, 'wb') as fd:
                for chunk in download_file.iter_content(chunk_size):
                    fd.write(chunk)
                    downloaded_bytes = fd.tell()
                    print (float(downloaded_bytes)/file_size*100)
                    queue.get(float(downloaded_bytes)/file_size*100)
                    #self.notify_progress.emit(float(downloaded_bytes)/file_size*100)
        except IOError as IoErr:
            print IoErr
        else:
            print "Finish"
            return 'Done'
-Zeile 26 benutze ich die get()-Methode und Chunk-Zählung wird zum Inhalt von get().

Aber irgendwie will mir das immer noch nicht gelingen.
Antworten