Zugriff auf globale Variable

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.
Benutzeravatar
/me
User
Beiträge: 3556
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

deets hat geschrieben:Das verletzt das Gesetz von Demeter.
"I fought the law, but law won." :mrgreen:
BlackJack

@/me: Du bist Schuld das meine Umgebung jetzt gerade mit Punkrock beglückt wird. :-D
Bonsai
User
Beiträge: 17
Registriert: Montag 20. August 2012, 10:25

@Leonidas, ich hoffe du bist jetzt zufrieden....

@deets, so ganz hab ich das jetzt nicht verstanden. Mit append() lade ich ja wieder 24 mal das Photo rein?
Und das mit der mainloop konnte ich nur erahnen, ob du es so gemeint hast. Da mache ich mir im Übrigen noch meine Gedanken, ob das wirklich sinnvol so ist, weil mein relativ unkompetenter Lehrer sicher nicht weiß, dass man die mainloop() nicht direkt im Konstruktor aufrufen sollte.

DIe ganzen selfs kommen daher, weil ic nicht weiß welche Variablen ich wo später noch brachen werde, wenn das Programm komplexer wird. Da ih das Programm für die Abgabe sowieso noch x-1000 mal durchgehen muss, haben wir uns im Kurs abgesprochen, es so zu versuchen.(nebenbei erschwert es doch das lesen für euch nicht wirklich!?)

Ich habe die 3 Zeilen bei Zug und Gleise jetzt in die Klassen ausgelagert, bekomme aber einen mir unverständlichen Synthaxfehler in dieser Zeile: "self.canvas.create_image(i*20,169,image=self.gleise,anchor=tk.NW)"

Code: Alles auswählen

# -*- coding: cp1252 -*-

import Tkinter as tk                   #verwendete GUI: Tkinter

#Klasse zur Steuerung des Spiels
class Spiel:
    def __init__(self):
        self.grafik = Grafik(self)
        
#Klasse zur Abbildung von Objekten auf dem Canvas
class Grafik:
    def __init__(self, spiel):
        self.spiel = spiel

        #Tkinter-Interface
        self.fenster = tk.Tk()
        
        #Erzeugen eines Hintergrundobjekts
        self.hintergrund=Hintergrund()
        
        #Canvas
        self.canvas = tk.Canvas(width=self.hintergrund.width,height=self.hintergrund.height)
        self.canvas.pack()
        print "Canvas created"
        
        #Hintergrund
        self.hintergrund = self.hintergrund.hintergrund
        self.canvas.create_image(0,0,image=self.hintergrund,anchor=tk.NW)

        #Zug
        self.zug_X = 0
        self.zug_Y = 138
        self.zug = Zug(self, self.zug_X, self.zug_Y)     
        self.zug.fahren()

        #Gleise
        self.gleise = Gleise(self)
        
        mainloop()

    def mainloop():
        self.fenster.mainloop()

#Klasse für die Hintergrundobjekte        
class Hintergrund:
    def __init__(self):
        self.hintergrund = tk.PhotoImage(file = "C:/Users/Manuel/Schule/Informatik_Seminar/hintergrund2.gif")
        self.width = self.hintergrund.width()
        self.height = self.hintergrund.height()

class Gleise:
    def __init__(self, grafik):
        self.grafik = grafik
        self.canvas = self.grafik.canvas
        gleise = []
        for i in xrange(23):
            gleise.append(tk.PhotoImage(file = "C:/Users/Manuel/Schule/Informatik_Seminar/gleis.gif")
            self.canvas.create_image(i*20,169,image=self.gleise[i],anchor=tk.NW)

#Klasse für den fahrenden Zug
class Zug:
    def __init__(self, grafik, x, y):
        self.grafik = grafik
        self.canvas = self.grafik.canvas
        self.x = x
        self.y = y
        self.zug = tk.PhotoImage(file = "C:/Users/Manuel/Schule/Informatik_Seminar/Zug.gif")
        self.canvas.create_image(self.zug_X,self.zug_Y,image=self.zug,anchor=tk.NW,tags='zugtag')

    def fahren(self):
        self.canvas.move('zugtag',1,0)
        self.x = self.x + 1
        print "Bewegung um 1"
        if self.x == 10:
            self.canvas.after_cancel(id)
        self.canvas.after(10, self.fahren)

print "hallo1"
s = Spiel()                 #Starten des Spiels
print "hallo2"          
deets

Du kannst natuerlich nicht stumpf einfach rumkopieren.

Zum einen wuerde ich in Zug nicht self.zug, sonder self.image oder so benutzen - denn ein Zug ist das Bild + Verhalten, nicht das Bild selbst.

Dann benutzt du in create_image self.zug_X - dabei heisst die variable doch self.x im Zug-Objekt...

Die mainloop-Methode hast du jetzt genau so geschrieben, wie ich sie meinte - gut so!

Unnoetig immer noch self.zug_X und self.zug_Y - diese Werte stehen doch im self.zug-Objekt! Und diese wenigen Zeilen code illustrieren auch gleich, warum was dein Lehrer und du da bezueglich der Variablen abgesprochen habt hochgradiger Unfug ist:

Eine der *WICHTIGSTEN*, und das kann ich gar nicht nachdruecklich genug betonen, Paradigmen beim Programmieren ist *ZUSTANDSSPARSAMKEIT*. Wenn es nach mir ginge, dann gaebe es da einen staatlich bestellten Beauftragten fuer, der jedem auf die Finger klopft, der unnoetigen Zustand erzeugt!!!

Und dein Code zeigt eben, warum: du speicherst die vermeintlichen Zug-Koordinaten auf Grafik. Gleichzeitig aber beginnt dein Zug, Timer-Gesteuert ein Eigenleben zu fuehren. Was ja auch genau so sein soll.

Und damit laufen deine Grafik.zug_X/Y-Werte und die tatsaechliche Bildschirmkoordinate zwangslaeufig auseinander! Wenn du also weiteren Code hinzufuegst, der sich dann an der falschen Stelle an den Daten bedient, sind Programmierfehler vorprogrammiert.

Darum: NIEMALS ZUSTAND SPEICHERN IN OBJEKTEN ODER GLOBALEN VARIABLEN, DER AUCH ANDERWEITIG BERECHNET/ERMITTELT WERDEN KANN.

In deinem Fall kannst du zB auf Grafik ein property machen, wenn tatsaechlich andere Stellen des Codes auf Grafik.zug_X zugreifen *muessen*. UU ist es aber auch besser, dem potentiellen Code dann einfach den tatsaechlichen Zug als Objekt mitzugeben.

Gibt es Ausnahmen von dieser Regel? Ja. Irgendwann mal, wenn du ein deutlich besserer Programmierer geworden bist, dann wirst du an bestimmten Stellen Zustand zwischenspeichern, um zB Performance-Probleme in den Griff zu kriegen - Stichwort caching.

Aber auch wenn man gerne mal glaubt, man wuesste, wann & warum etwas langsam ist: die Intuition ist da sehr oft falsch, und darum solltest du das wenn durch profiling belegen. Und fuer den Anfang eben *NICHT UND NIEMALS* machen.

Zu deinem Syntaxfehler kann ich nichts sagen, da du den ja nur beschreibst, nicht hier postest.

Und was das append angeht: ja, wenn du es so "stumpf" machst ergibt sich dadurch natuerlich keine Verbesserung. Aber frag dich doch mal: warum machst du *ueberhaupt* in der Schleife immer noch ein neues Image, und wozu erzeugst du gleise als Liste, wenn du die danach eh wieder wegwirfst?

Das, was du doch wirklich willst, ist das Bild *einmal* zu laden, und dann 23 Canvas-Objekte zu erzeugen, deren ID du speicherst.

Ausserdem hast du in Gleise einen Fehler, weil du gleise anlegest, aber dann self.gleise machst. Das ist so natuerlich nicht moegich.
Bonsai
User
Beiträge: 17
Registriert: Montag 20. August 2012, 10:25

die Zugvariablen in Gleise hatte ich vergessen, ist nun berichtigt.

Was das append angeht habe ich keine Ahnung wie du das meinst. Zum einen fügt append ja in jeden Schleifendurchlauf ein Listeneintrag ans Ende der Liste hinzu, wieso kann ich dann nicht per liste auf diesen Eintrag Zugreifen.

Und wie soll ich es sonst machen? Mir ist bewusst dass ich zurzeit 24mal des Bild reinlade und 24mal das Image erstelle, aber um für jeden Listeneintrag eine neue Position zu haben, kenne ich nichts anderes als ein neues Image zu erstellen.

Zu dem Synthaxfehler kann ich genauso wenig sagen, da IDLE (ja ich programmier zurzeit noch damit) einfach nur sagt "Synthaxfehler in der Zeile", wenn ich die Zeile auskommentiere zeigt er aber einen Synthaxfehler in der Class Zug: -Zeile an, was nun wirklich nicht falsch ist??
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Bonsai

Evtl. etwas in diese Richtung:

Code: Alles auswählen

        self.gleise = tk.PhotoImage(
            file="C:/Users/Manuel/Schule/Informatik_Seminar/gleis.gif")
        for i in range(23):
            self.canvas.create_image(
                i*20, 169, image=self.gleise, anchor=tk.NW)
Hat deets schon angedeutet.

Gruß wuf :wink:
Take it easy Mates!
Bonsai
User
Beiträge: 17
Registriert: Montag 20. August 2012, 10:25

tatsächlich, funktioniert wunderbar, danke wuf.....ich versteh es bloß nicht ganz

den Code inerpretiere ich mal so: Man Speichert das PhotoImage direkt in die Liste selber, sodass es für die Gesamten Listeneinträge zur Verfügung steht, und dann erzeugt man 24 mal ein neues Image mit verschiedenen Positionen. Aber woher weiß die Liste, dass sie jedes Mal einen neuen Eintrag anlegen soll und nicht 24 mal den Ersten Eintrag überschreibt?

Jetzt bleibt mir nurnoch das Problem mit dem weiter oben beschriebenen "after_cancel"
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Bonsai

Code: Alles auswählen

self.gleise
ist keine List mehr. Es ist eine Referenz welche auf das Bildobjekt ist das im Speicher abgelegt wurde. Mit der Schleife klebst du das Bild 24 mal sichbar auf die Canvas. Daten aller 24 Bilder sind dann im Canvasobjekt gespeichert. Hoffe ich habe mich richtig ausgedrückt. OK self.gleise sollte in self.gleis_bild abgeändert werden.

Gruß wuf :wink:
Take it easy Mates!
Bonsai
User
Beiträge: 17
Registriert: Montag 20. August 2012, 10:25

danke dafür, super Erklärung :wink:
Bonsai
User
Beiträge: 17
Registriert: Montag 20. August 2012, 10:25

So, trotz des schönen Wetters, habe ich mich mal wieder überredet etwas weiter zu machen.

Nun wollte ich einen Button erstellen, der bei Aktivierung die fahren()-Methode des Zugs startet, was aber mal wieder nicht funktioniert hat. Zum Test habe ich dann direkt in der Grafikklasse noch eine Methode press (nur zum Test!!) geschrieben, mit welcher der Button zu meiner Verwunderung auch nicht funktioniert hat!? :x

Code: Alles auswählen

# -*- coding: cp1252 -*-

import Tkinter as tk                   #verwendete GUI: Tkinter

#Klasse zur Steuerung des Spiels
class Spiel:
    def __init__(self):
        self.grafik = Grafik(self)
        
#Klasse zur Abbildung von Objekten auf dem Canvas
class Grafik:
    def __init__(self, spiel):
        self.spiel = spiel

        #Tkinter-Interface
        self.fenster = tk.Tk()
        
        #Erzeugen eines Hintergrundobjekts
        self.hintergrund=Hintergrund() 
        
        #Canvas
        self.canvas = tk.Canvas(width=self.hintergrund.width,height=self.hintergrund.height)
        self.canvas.pack()
        print "Canvas created"
        
        #Hintergrund
        self.hintergrund = self.hintergrund.hintergrund
        self.canvas.create_image(0,0,image=self.hintergrund,anchor=tk.NW)
        print "Hintergrund erstellt"

        #Erzeugen eines Zugobjekts
        self.zug = Zug(self)

        #Start-Button
        self.button = tk.Button(self.fenster, text = "Zug starten", command = "self.press")
        self.button.pack()

        #Gleise
        self.gleise = Gleise(self)
        
        self.mainloop()

    #Mainloop außerhalb des Konstruktors (Performance)
    def mainloop(self):
        self.fenster.mainloop()

    def press(self):
        print "pressed"

#Klasse für die Hintergrundobjekte        
class Hintergrund:
    def __init__(self):
        self.hintergrund = tk.PhotoImage(file = "Images/hintergrund2.gif")
        self.width = self.hintergrund.width()
        self.height = self.hintergrund.height()

#Klasse für die am Anfang vorhandenen Gleise
class Gleise:
    def __init__(self, grafik):
        self.grafik = grafik
        self.canvas = self.grafik.canvas
        self.gleise_img = tk.PhotoImage(file="Images/gleis.gif")
        for i in xrange(23):
            self.canvas.create_image(i*20, 169, image=self.gleise_img, anchor=tk.NW)
            print "Gleis erstellt bei", i*20, 169

#Klasse für den fahrenden Zug
class Zug:
    def __init__(self, grafik):
        self.grafik = grafik
        self.canvas = self.grafik.canvas
        self.x = 0
        self.y = 138
        self.zug_img = tk.PhotoImage(file = "Images/Zug.gif")
        self.canvas.create_image(self.x,self.y,image=self.zug_img,anchor=tk.NW,tags='zugtag')

    def fahren(self):
        self.canvas.move('zugtag',1,0)
        self.x = self.x + 1
        print "Bewegung um 1"
        if self.x < 470:
            self.canvas.after(10, self.fahren)

print "hallo1"
s = Spiel()                 #Starten des Spiels
print "hallo2"
            
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Bonsai

Was passiert wenn du die folgenden Modifikationen vornimmst?:

Code: Alles auswählen

self.button = tk.Button(self.fenster, text = "Zug starten", command = self.press)
self.button.pack()
und

Code: Alles auswählen

def press(self):
    print "pressed"
    self.zug.fahren()
Gruß wuf :wink:
Take it easy Mates!
Bonsai
User
Beiträge: 17
Registriert: Montag 20. August 2012, 10:25

wiedermal danke, es waren nur die Anführungszeichen, das wundert mich aber sehr, da ich vor mir ein Handout zum Thema Tkinter-Widgets liegen habe, wo es mit Anführungszeichen erklärt ist :K
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Bonsai

Noch was zur self.fenster.mainloop() Methode. Du startest diese immer noch über den Konstruktor. Nicht mehr direkt aber über eine klasseeigene self.mainloop() Methode. So wie ich deets verstanden habe sollte dies erst geschehen wenn der Konstruktor der Klasse durchlaufen wurde. Das heisst die Klassen 'Spiel' & 'Grafik' müssten wie folgt angepasst werden:

Code: Alles auswählen

#Klasse zur Steuerung des Spiels
class Spiel:
    def __init__(self):
        self.grafik = Grafik(self)
        self.grafik.mainloop()
       
#Klasse zur Abbildung von Objekten auf dem Canvas
class Grafik:
    def __init__(self, spiel):
        self.spiel = spiel

        #Tkinter-Interface
        self.fenster = tk.Tk()
       
        #Erzeugen eines Hintergrundobjekts
        self.hintergrund=Hintergrund()
       
        #Canvas
        self.canvas = tk.Canvas(width=self.hintergrund.width,height=self.hintergrund.height)
        self.canvas.pack()
        print "Canvas created"
       
        #Hintergrund
        self.hintergrund = self.hintergrund.hintergrund
        self.canvas.create_image(0,0,image=self.hintergrund,anchor=tk.NW)
        print "Hintergrund erstellt"

        #Erzeugen eines Zugobjekts
        self.zug = Zug(self)

        #Start-Button
        #self.button = tk.Button(self.fenster, text = "Zug starten", command = "self.press")
        self.button = tk.Button(self.fenster, text = "Zug starten", command = self.press)
        self.button.pack()

        #Gleise
        self.gleise = Gleise(self)
       
        #self.mainloop()
Gruß wuf :wink:
Take it easy Mates!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Und mit Performance hat das, entgegen der Aussage des Kommentars, gar nichts zu tun.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Bonsai
User
Beiträge: 17
Registriert: Montag 20. August 2012, 10:25

danke wuf

@Leonidas, meinst du damit, ich soll die mainloop() weiterhin bequem über den Konstruktor starten??
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Leonidas hat geschrieben:Und mit Performance hat das, entgegen der Aussage des Kommentars, gar nichts zu tun.
Habe dich nicht verstanden?

Gruß wuf :wink:
Take it easy Mates!
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Bonsai hat geschrieben:@Leonidas, meinst du damit, ich soll die mainloop() weiterhin bequem über den Konstruktor starten??
Nein, wie kommst du denn darauf. Das sollte man nicht machen und ich Stimme dem entsprechendem Tippgeber da absolut zu.
wuf hat geschrieben:
Leonidas hat geschrieben:Und mit Performance hat das, entgegen der Aussage des Kommentars, gar nichts zu tun.
Habe dich nicht verstanden?
Ich meine damit dass dieser Kommentar:

Code: Alles auswählen

    #Mainloop außerhalb des Konstruktors (Performance)
Der im Quelltext auf der zweiten Seite dieses Threads aufgetaucht ist, falsch ist. Denn das hat gar nichts mit Performance zu tun, ist sogar kaum messbar langsamer. Man sollte keine Klassen schreiben die bei Instanziierung in Dauerschleifen verfallen, das ist einfach schlechter Stil. So wie du es machst ist schon besser, allerdings würde ich Mainloops eigentlich generell nur in der ``def main()`` starten, denn sonst hat man wieder das Problem mit blockierenden Klassen, statt in ``Grafik`` ist es halt in ``Spiel`` nun, effektiv das gleiche Problem, nur verschoben.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

OK Leonidas

Danke für deine Klarstellung. Bonsai ich habe das Skript einmal nach den neusten Erkenntnisse korrigiert:

Code: Alles auswählen

# -*- coding: cp1252 -*-

import Tkinter as tk  #verwendete GUI: Tkinter

#Klasse zur Steuerung des Spiels
class Spiel:
    def __init__(self):
        self.grafik = Grafik(self)
        
#Klasse zur Abbildung von Objekten auf dem Canvas
class Grafik:
    def __init__(self, spiel):
        self.spiel = spiel
        
        #Tkinter-Interface
        self.fenster = tk.Tk()
       
        #Erzeugen eines Hintergrundobjekts
        self.hintergrund=Hintergrund()
       
        #Canvas
        self.canvas = tk.Canvas(width=self.hintergrund.width,
            height=self.hintergrund.height)
        self.canvas.pack()
        print "Canvas created"
       
        #Hintergrund
        self.hintergrund = self.hintergrund.hintergrund
        self.canvas.create_image(0,0,image=self.hintergrund,anchor=tk.NW)
        print "Hintergrund erstellt"

        #Erzeugen eines Zugobjekts
        self.zug = Zug(self)

        #Start-Button
        self.button = tk.Button(self.fenster, text="Zug starten",
            command=self.press)
        self.button.pack()

        #Gleise
        self.gleise = Gleise(self)

    def mainloop(self):
        self.fenster.mainloop()

    def press(self):
        print "pressed"
        self.zug.fahren()

#Klasse für die Hintergrundobjekte        
class Hintergrund:
    def __init__(self):
        self.hintergrund = tk.PhotoImage(file = "Images/hintergrund2.gif")
        self.width = self.hintergrund.width()
        self.height = self.hintergrund.height()

#Klasse für die am Anfang vorhandenen Gleise
class Gleise:
    def __init__(self, grafik):
        self.grafik = grafik
        self.canvas = self.grafik.canvas
        self.gleise_img = tk.PhotoImage(file="Images/gleis.gif")
        for i in xrange(23):
            self.canvas.create_image(i*20, 169, image=self.gleise_img,
                anchor=tk.NW)
            print "Gleis erstellt bei", i*20, 169

#Klasse für den fahrenden Zug
class Zug:
    def __init__(self, grafik):
        self.grafik = grafik
        self.canvas = self.grafik.canvas
        self.x = 0
        self.y = 138
        self.zug_img = tk.PhotoImage(file = "Images/Zug.gif")
        self.canvas.create_image(self.x,self.y,image=self.zug_img,anchor=tk.NW,
            tags='zugtag')

    def fahren(self):
        self.canvas.move('zugtag',1,0)
        self.x = self.x + 1
        print "Bewegung um 1"
        if self.x < 470:
            self.canvas.after(10, self.fahren)

def main():
    spiel= Spiel()
    spiel.grafik.mainloop()
       
main()  #Starten des Spiels
Gruß wuf :wink:
Take it easy Mates!
Bonsai
User
Beiträge: 17
Registriert: Montag 20. August 2012, 10:25

danke für eure Bemühungen!!
Antworten