Oft crash, nicht sofort... ich dreh noch durch...

Fragen zu Tkinter.
Antworten
Ctrl-Z
User
Beiträge: 12
Registriert: Freitag 1. Mai 2015, 03:55

Hi,

ich versuche Threads zu erlernen. Aus dem Buch "Objektorientierte Programmierung mit Python" von Weigend lass ich folgenden Code laufen:

Code: Alles auswählen

from Tkinter import *
from threading import *
from time import *
from random import *

class Weltraum:
    def __init__(self):
        #seed() # from random
        self.fenster = Tk()
        self.canvas = Canvas(self.fenster, width = '10c', height = '7c', bg = 'black')
        self.canvas.pack()
        m1 = 'E:/Wissenschaft/Projekt/Telnet/PythonTests/met1k1.gif'
        m2 = 'E:/Wissenschaft/Projekt/Telnet/PythonTests/met2k1.gif'
        for bild in [m1, m2, m2, m1, m2, m1, m2]:
            Meteor(self, bild)
        self.fenster.mainloop()

class Meteor(Thread):
    def __init__(self, weltraum, bilddatei):
        Thread.__init__(self)
        self.w = weltraum
        self.c = weltraum.canvas
        self.bild = PhotoImage(file=bilddatei)
        self.id = self.c.create_image(50, -50, image = self.bild)
        self.__neustart()
        self.start()

    def __neustart(self):
        self.c.coords(self.id, randint(0, 250), -randint(30, 100))
        self.vx = randint(-2, +2)
        self.vy = randint(1, 3)

    def run(self):
        while 1:
            sleep(0.05)
            self.c.move(self.id, self.vx, self.vy)
            if self.c.coords(self.id)[1] > 300:
                self.__neustart()

def main():
    w = Weltraum()

if __name__ == '__main__':
    main()
Geiler Weise ist das Programm beim ersten Versuch gleich gelaufen. Ich könnte schwören, dass ich den Quellcode nicht angefasst habe, aber der Fehler sitzt meistens an der Tastatur... ich habe den schon zig mal mit der Buchvorlage verglichen...

Meistens fliegt es mir mit "exceptions.EOFError: [Errno 10054]" um die Ohren.
Insgesamt:
Exception in thread Thread-6:
Traceback (most recent call last):
File "C:\Program Files\WinPython-64bit-2.7.6.4\python-2.7.6.amd64\lib\threading.py", line 810, in __bootstrap_inner
self.run()
File "E:\Wissenschaft\Projekt\Telnet\PythonTests\meteor1.py", line 37, in run
if self.c.coords(self.id)[1] > 300:
IndexError: list index out of range

Traceback (most recent call last):
File "<string>", line 73, in execInThread
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\netref.py", line 196, in __call__
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\netref.py", line 71, in syncreq
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 431, in sync_request
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 379, in serve
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 337, in _recv
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\channel.py", line 50, in recv
File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\stream.py", line 166, in read
EOFError: [Errno 10054] Eine vorhandene Verbindung wurde vom Remotehost geschlossen
Danke für einen Tipp, was hier schief läuft!
BlackJack

@Ctrl-Z: Also das Grundproblem ist das die GUI nur von dem Thread aus manipuliert werden darf in dem die `mainloop()` läuft weil Tkinter, wie die meisten GUI-Rahmenwerke, nicht thread-sicher ist. Deine Idee ein Thread pro Meteor ist also nicht so günstig weil es einfacher ist das ohne Threads zu lösen als sich den Zusatzaufwand zu leisten das mit Threads und sicherere Kommunikation zwischen diesen hinzubekommen.

Sonstige Sachen die dringend abgestellt werden sollten: Sternchen-Importe, einbuchstabige Attributnamen (ausser vielleicht `x` und `y` für Koordinaten), Thread-Objekte die allein durch das erstellen des Objekts schon einen Thread starten, und doppelte führende Unterstriche bei Attributnamen.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Ctrl-Z: Der Code ist nicht nur ausgesprochen schlecht, sondern auch noch fehlerhaft und zeigt nur, wie man Python, Objektorientierung und Threading nicht programmieren sollte.

Das fängt schon mit den Sternchenimporten an. Das sollte man nie machen, weil man nicht weiß, von wo man welche Namen importiert. Im konkreten Fall importieren sowohl Tkinter als auch threading eine Event-Klasse. Welche verwendet wird hängt von der Reihenfolge der Importe ab!

Weltraum ist keine Klasse, sondern nur ein besonders umständlicher und undurchsichtiger Weg, globale Variablen einzuführen. Zudem kommt noch dazu, dass sowohl das Tk-Objekt als auch mainloop am besten nur in main vorkommt, weil man nur ein Tk-Objekt haben darf und mehrere Aufrufe von mainloop nur den ohnehin schon schwer zu durchschauenden Ablauf von GUI-Programmen noch mehr verschleiert.
Die Meteor-Instanzen werden nirgends an eine Variable gebunden, sind also nicht kontrollierbar.

Die Meteor-Klasse ist auch nur sehr bedingt eine wirkliche Klasse. Von Thread wird selten sinnvoll abgeleitet, weil man mit dem target-Argument sehr flexibel Funktionen parallel abarbeiten lassen kann. Dann kommt auch niemand auf die Idee, den Thread schon in __init__ zu starten. Die Attributnamen sind sehr schlecht, sie sollten etwas aussagen und nicht nur aus einem Buchstaben bestehen. "w" wird nie verwendet. Doppelte Unterstriche bei Attributen sollte man nur verwenden, wenn man weiß, warum man sie braucht. In Python gibt es die Wahrheitswerte True und False. Diese sollte man auch einsetzen, z.B. bei Endlosschleifen.

Thread-Programmierung ist an sich schwierig, richtig umzusetzen. Bei GUI-Programmen wird es aber wieder viel einfacher: NICHT MACHEN! Die Rahmenwerke sind nicht dazu ausgelegt, dass mehrere Threads sie gleichzeitig verändern. Das führt zu nicht vorhersehbaren und merkwürdigen Fehlermeldungen. Für Beispiele wie dieses, gibt es bei Tk die after-Methode.
Ctrl-Z
User
Beiträge: 12
Registriert: Freitag 1. Mai 2015, 03:55

Sirius3 hat geschrieben:@Ctrl-Z: Der Code ist nicht nur ausgesprochen schlecht, sondern auch noch fehlerhaft und zeigt nur, wie man Python, Objektorientierung und Threading nicht programmieren sollte.

...

Thread-Programmierung ist an sich schwierig, richtig umzusetzen. Bei GUI-Programmen wird es aber wieder viel einfacher: NICHT MACHEN! Die Rahmenwerke sind nicht dazu ausgelegt, dass mehrere Threads sie gleichzeitig verändern. Das führt zu nicht vorhersehbaren und merkwürdigen Fehlermeldungen. Für Beispiele wie dieses, gibt es bei Tk die after-Methode.
Da muss ich sicher noch viel lernen, aber dann ist es schon erstaunlich, dass der Code aus einem Lehrbuch stammt... echt bitter. Super tödlich bei Threads ist ja wohl, wenn man versucht, auf globale Variablen zurück zu greifen...
und wenn Du das so siehst, dann passiert wohl genau das... :(
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ctrl-Z hat geschrieben: Da muss ich sicher noch viel lernen, aber dann ist es schon erstaunlich, dass der Code aus einem Lehrbuch stammt... echt bitter.
Aus dem Grund raten wir hier von vielen Lehrbüchern ab und empfehlen das offizielle Tutorial oder diverse andere Online-Tutorials wie "Learn Python the hard way" ;-) Offenbar sind viele deutschsprachige Bücher schlecht recherchiert und werden von Autoren verfasst, die sich in der Python Welt nicht auskennen.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python
from __future__ import absolute_import, division, print_function

import Tkinter as tk
from random import randint


class Meteor(object):

    def __init__(self, canvas, image):
        self.canvas = canvas
        self.image = image
        self.image_id = self.canvas.create_image(0, 0, image=self.image)
        self.x_velocity = 0
        self.y_velocity = 0
        self.randomize()

    def randomize(self):
        self.canvas.coords(self.image_id, randint(0, 250), -randint(30, 100))
        self.x_velocity = randint(-2, 2)
        self.y_velocity = randint(1, 3)

    def run(self):
        self.canvas.move(self.image_id, self.x_velocity, self.y_velocity)
        if self.canvas.coords(self.image_id)[1] > 300:
            self.randomize()
        self.canvas.after(50, self.run)


def main():
    root = tk.Tk()
    canvas = tk.Canvas(root, width='10c', height='7c', background='black')
    canvas.pack()
    image_a, image_b = (
        tk.PhotoImage(filename) for filename in ['meteor_a.gif', 'meteor_b.gif']
    )
    meteors = [Meteor(canvas, image) for image in [image_a] * 3 + [image_b] * 4]
    for meteor in meteors:
        meteor.run()
    root.mainloop()


if __name__ == '__main__':
    main()
Antworten