Klassenattribut ist nach Erstellung nicht mehr ansprechbar

Fragen zu Tkinter.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Hi,
ich stehe bei meinem Python Code vor einem weiteren Rätsel. Ich erstelle folgendermaßen ein Klassenobjekt:

Code: Alles auswählen

	def init_load_bar(self, query):
		
		self.dl_bar = g_load.download_bar(query, self)
		
Anschließend werden ein paar Threads erstellt und das Programm macht ein paar Berechnungen. Anschließend möchte ich das erstellte Klassenobjekt ansprechen, um die download_bar wieder zu löschen:

Code: Alles auswählen

	def rdy_to_show(self):
		
		self.dl_bar.kill_bar()
Komischerweise wird das Objekt nicht gefunden und es wird folgende Exception geschmissen:

Code: Alles auswählen

  File "main.py", line 24, in rdy_to_show
    self.dl_bar.kill_bar()
AttributeError: mainclass instance has no attribute 'dl_bar'
Wie ist das möglich? Schließlich wurde das Objekt in der Mainclass erstellt!

Bin über jeden Tipp dankbar!

Gruß,
moccajoghurt
BlackJack

@moccajoghurt: Wenn das Attribut nicht gefunden wird, dann wurde es ganz offensichtlich auch nicht an das Objekt gebunden. Oder inzwischen wieder entfernt. Was ungewöhnliches Vorgehen wäre.

Übrigens erstellst Du da nirgends „Klassenobjekte”. `mainclass` wäre am ehesten ein Klassenobjekt — eben ein Objekt das eine Klasse ist. Der Name entspricht übrigens nicht dem Style Guide for Python Code und den Datentyp, in diesem Fall `class` sollte man auch nicht im Namen kodieren. Dass es sich um eine Klasse handelt würde man normalerweise an der konventionellen Namensschreibweise erkennen.

Attribute sollten alle nach abarbeiten der `__init__()`-Methode erstellt sein. Sonst ist der Programm-/Datenfluss schwerer zu verstehen.

Abkürzungen sollte man vermeiden solange es sich nicht um allgemein bekannte Abkürzungen handelt. `ready_to_show()` ist nur zwei Zeichen länger aber man muss nicht raten wass es wohl heissen mag. Wie zum Beispiel auch bei dem `g_` in `g_load()`. Funktionen und Methoden sollten ausserdem in der Regel Tätigkeiten beschreiben. `ready_to_show()` klingt eher nach einem Zustand.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

@moccajoghurt: Wenn das Attribut nicht gefunden wird, dann wurde es ganz offensichtlich auch nicht an das Objekt gebunden. Oder inzwischen wieder entfernt. Was ungewöhnliches Vorgehen wäre.
Eigentlich sollte es mit

Code: Alles auswählen

self.dl_bar = g_load.download_bar(query, self)
an das Objekt gebunden worden sein... Vorher entfernt wurde es definitiv nicht.
Es wird zwischenzeitlich nur eine neue mainloop gestartet und das Objekt aus einem anderen Thread heraus angesprochen (zu dem Zeitpunkt wurde das Objekt aber definitiv bereits erstellt, da dies vor dem Erstellen der neuen Threads passiert).
Sorry für die unkonventionelle Schreibweise, bin ein Python-Neuling und werde mir die Konventionen aneignen. Auch das Erstellen der Variablen werde ich zukünftig besser machen.
Zur Verdeutlichung:
Mit

Code: Alles auswählen

self.dl_bar = g_load.download_bar(query, self)
erstelle ich ein Objekt der Klasse "class download_bar", welche sich im Modul "g_load.py" befindet.
BlackJack

@moccajoghurt: Wenn das Attribut nicht entfernt wurde, dann wurde es gar nicht erst erstellt, also die `init_load_bar()`-Methode vorher nicht aufgerufen. Sonst wäre es ja vorhanden.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Ja davon würde ich eigentlich auch ausgehen, aber sie wurde tatsächlich erstellt, man kann sie ja auch sehen...
Gibt es noch irgendeinen anderen Grund, weshalb sie nicht mehr aufgerufen werden könnte, außer dass sie bereits gelöscht wurde oder gar nicht erstellt wurde. Denn die letzten beiden Gründe kann man ausschließen.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Vielleicht zeigst du etwas mehr Code als die paar vorliegenden Stückchen, dann kann man sich einen besseren Überblick verschaffen. Wahrscheinlich reichen schon der Code der Klasse und der Code an der Stell, an der du die Instanz erzeugst.
Das Leben ist wie ein Tennisball.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Die Klasse, die erstellt wird:

Code: Alles auswählen

from ttk import Progressbar
import Tkinter
import threading


class download_bar():
	
	def __init__(self, query, main):
		self.mainclass = main
		self.window = Tkinter.Tk()
		self.window.overrideredirect(True)
		self.bar = Progressbar(self.window, length=200)
		self.bar.pack(expand=True, fill=Tkinter.BOTH)
		
		self.bar.configure(mode="indeterminate", length=150)
		
		
		t0 = threading.Thread(target=self.loop_bar)
		t0.start()
		
                
		self.start_request(query)
		
		self.window.mainloop()
		
	
	

	def start_request(self, query):
		t = threading.Thread(target= lambda a = query : self.mainclass.send_search_request(a))
		t.start()
	
	def loop_bar(self):
		self.bar.start(interval=1)
	
	def kill_bar(self):
		
		self.window.destroy()
		
	
Die Funktion, in der die Klasse erstellt wird:

Code: Alles auswählen

	def init_load_bar(self, query):
		
		self.dl_bar = g_load.download_bar(query, self)
		
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Und wie sieht die Klasse aus, in der "rdy_to_show" liegt? Dort tritt der Fehler ja auf.
Das Leben ist wie ein Tennisball.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Code: Alles auswählen

import g_main, g_load
import search
import time
import threading

class mainclass():
	
	def __init__(self):
		self.open_gui()
		self.elapsed = 0
		self.time_elap = 0
		
	
	def open_gui(self):
		self.main_window = g_main.maingui(self)
	
	def init_load_bar(self, query):
		
		self.dl_bar = g_load.download_bar(query, self)
		
	
	def rdy_to_show(self):
		
		self.dl_bar.kill_bar()
		
		self.result_array = self.search_request.get_current_results()
		__buf_count = 0
		for x in range(3):
			__result_pack = [[0 for i in range(2)] for j in range(8)]
			
			for y in range(8):
				__result_pack[y][0] = self.result_array[__buf_count][0]
				__result_pack[y][1] = self.result_array[__buf_count][1]
				__buf_count = __buf_count + 1
				
			self.main_window.show_results(__result_pack)
			
	
	def time_elapsed(self):
		self.elapsed = self.elapsed + 1
		
		while True:
			if self.elapsed == 2:
				self.time_elap = 0
				break
			self.time_elap = self.time_elap + 1
			time.sleep(1)
			print(self.time_elap)
			
	
	
	def send_search_request(self, query):
		
		
		self.main_window.clear_canvas()
		
		self.search_request = search.img_search(self, query)
		

	
	def request_more_results(self):
		self.search_request.get_more_results()
		
		
		


a = mainclass()
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

So wie es im Moment aussieht, würde ich BlackJacks Meinung folgend und vermuten, dass du init_load_bar niemals aufrufst, erst nach rdy_to_show aufrufst oder du auf dem falschen Instanz von mainclass arbeitest. An deiner Stell würde ich ein print in der init_load_bar unterbringen zum zu schauen, ob diese auch wirklich aufgerufen wird. Eigentlich kann nur dort der Fehler liegen.
Das Leben ist wie ein Tennisball.
deets

Und bitte gewoehn dir mal ganz eilig die __ vor *lokalen* Namen ab. Generell sollte man self.__foo eher in Ausnahme bis gar nicht benutzen - es ist zur Vermeidun von Namenskollisionen gedacht, nicht um private zu simulieren.

Aber es ist vollends unsinnig, in lokalen Namensraeumen in einer Funktion oder Methode, wo die Namen eh nicht von aussen zugreifbar sind. Sonder verschlechtert nur die Lesbarkeit. Massiv.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Ok werde die __ zukünftig einfach weglassen.

Wenn ich ein

Code: Alles auswählen

print("### INIT LOAD BAR ###")
In init_load_bar packe, dann sieht die Exception folgendermaßen aus:

Code: Alles auswählen

>pythonw -u "main.py"
### INIT LOAD BAR ###
Exception in thread Thread-23:
Traceback (most recent call last):
  File "C:\Python27\lib\threading.py", line 551, in __bootstrap_inner
    self.run()
  File "C:\Python27\lib\threading.py", line 504, in run
    self.__target(*self.__args, **self.__kwargs)
  File "C:\Users\Marius\Desktop\image drawer\search.py", line 57, in <lambda>
    t = threading.Thread(target = lambda a = self.current_results, b = x: self.download_images(a,b))
  File "C:\Users\Marius\Desktop\image drawer\search.py", line 64, in download_images
    self.rdy_count()
  File "C:\Users\Marius\Desktop\image drawer\search.py", line 72, in rdy_count
    self.mainclass.rdy_to_show()
  File "main.py", line 24, in rdy_to_show
    self.dl_bar.kill_bar()
AttributeError: mainclass instance has no attribute 'dl_bar'

Es gibt nur eine Instanz von mainclass, deshalb kann es eigentlich nicht die falsche sein.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Dann versuchen wir es mal von der anderen Seite: was passiert, wenn du in der __init__-Method von mainclass einfach mal die Zeile

Code: Alles auswählen

self.dl_bar = None
hinzufügst?
Das Leben ist wie ein Tennisball.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Hatte das schon ausprobiert. Dann kommt, dass das None-Object nicht über das Attribut dl_bar verfügt.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ok, das bestätigt eigentlich nur die Hypothese. Wie sieht es mit den Ausgaben diesen Änderungen aus:

Code: Alles auswählen

class mainclass():
    ....
    def init_load_bar(self, query):
        print(id(self))
        self.dl_bar = g_load.download_bar(query, self)

class download_bar():
    ....
    def kill_bar(self):
        print(id(self))
        self.window.destroy()
Das Leben ist wie ein Tennisball.
BlackJack

@moccajoghurt: Es darf nur ein `Tk`-Exemplar geben und man darf nur aus dem Hauptthread aus GUI-Objekte manipulieren. Solange Du das nicht repariert hast, kann nahezu *alles* passieren, denn Dein Programm hat schlicht kein definiertes Verhalten mehr.

Ansonsten: Am Ende von `download_bar.__init__()` rufst Du eine `mainloop()` auf. Dieser Aufruf kehrt erst zurück wenn das zugehörige Widget zerstört wurde. Du bindest also tatsächlich `mainclass.dl_bar` an keinen Wert bevor es benutzt wird, denn der Aufruf kehrt nicht zurück. Ähnlich wie es nur ein `Tk`-Exemplar geben darf, sollte man nur eine `mainloop()` aufrufen. Das heisst auf deutsch *Haupt*schleife.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Ok jetzt verstehe ich!
Ist es denn möglich eine Progressbar in die Mitte des Hauptfensters zu setzen, ohne dass die anderen Widgets berücksichtigt werden müssen? Das war nämlich der Hauptgrund aus dem ich einfach ein neues Tk() erstellt habe. Ich möchte die Progressbar als unabhängige über den Hauptfenster schwebende Leiste. Geht das?

Ansonsten erstmal danke für die hilfreichen Antworten.
BlackJack

@moccajoghurt: `Tk` ist sozusagen das Hauptfenster. Wenn man weitere Fenster haben möchte kann man `Toplevel` dafür verwenden.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

BlackJack hat geschrieben:@moccajoghurt: `Tk` ist sozusagen das Hauptfenster. Wenn man weitere Fenster haben möchte kann man `Toplevel` dafür verwenden.
Hätte ich das vorher gewusst, hätte ich mir viele Stunden rumprobieren gespart. Aber jetzt weiß ich es.
Vielen Dank nochmal an alle, Thema ist gelöst.
moccajoghurt
User
Beiträge: 23
Registriert: Freitag 20. Juli 2012, 15:12

Habe jetzt ein anderes Problem mit der Scrollbar. Alles funktioniert eigentlich wie gewollt, nur wenn die Scrollbar zusammen mit dem Toplevel zerstört werden soll, dann bleibt sie, obwohl das Toplevel zerstört wurde, immernoch sichtbar.

Hier die Klasse:

Code: Alles auswählen

from ttk import Progressbar
import Tkinter


class download_bar():
	
	def __init__(self, main):
		self.mainclass = main
		self.window = Tkinter.Toplevel()
		self.window.overrideredirect(True)
		self.bar = Progressbar(self.window, length=200)
		self.bar.pack(expand=True, fill=Tkinter.BOTH)
		
		self.bar.configure(mode="indeterminate", length=150)
		
		self.loop_bar()
		
	
	
	def loop_bar(self):
		self.bar.start(interval=1)
	
	def kill_bar(self):
		self.bar.stop()
		#~ self.bar.pack_forget()
		#~ self.bar.destroy()
		#~ self.window.withdraw()
		self.window.destroy()
		
	
Wie bekomme ich es hin, dass die Progressbar auch nicht mehr sichtbar ist?
Antworten