TypeError: StringVar

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
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

beim Versuch, StringVar als Key in einem Dictionary zu verwenden, bekomme ich die Fehlermeldung:

Code: Alles auswählen

TypeError: unhashable type: 'StringVar'
Hier kurz der betreffende Code-Auszug:

Code: Alles auswählen

            self.var2widget = dict()
            for i2column in range(2):
                var = tk.StringVar()
                text = self._dict_[i2column][i]
                if name == 'INHALT' or name == 'BESTELLMENGE':
                    color = 'lightgreen'
                    entry = tk.Entry(self.canvas2, width=self.max_width,
                        textvariable=var, font=(self.txt2), bg=color, bd=1,
                        highlightthickness=1)
                    entry.grid(row=i, column=i2column, padx=self.ipadx,
                        pady=self.ipady)
                    self.xpos += entry.winfo_reqwidth()
                    self.var2widget[var] = entry
Würde ich statt var das Widget entry als Key verwenden, gibt es keine Fehelrmeldung.

Kann mir das vielleicht kurz erläutern, warum das so ist und ob es doch eine Möglichkeit gibt StringVar als Key im Dict zu verwenden?

Grüße Nobuddy
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Nobuddy hat geschrieben:Hallo zusammen,

beim Versuch, StringVar als Key in einem Dictionary zu verwenden, bekomme ich die Fehlermeldung:

Code: Alles auswählen

TypeError: unhashable type: 'StringVar'
Hier kurz der betreffende Code-Auszug:

Code: Alles auswählen

            self.var2widget = dict()
            for i2column in range(2):
                var = tk.StringVar()
                text = self._dict_[i2column][i]
                if name == 'INHALT' or name == 'BESTELLMENGE':
                    color = 'lightgreen'
                    entry = tk.Entry(self.canvas2, width=self.max_width,
                        textvariable=var, font=(self.txt2), bg=color, bd=1,
                        highlightthickness=1)
                    entry.grid(row=i, column=i2column, padx=self.ipadx,
                        pady=self.ipady)
                    self.xpos += entry.winfo_reqwidth()
                    self.var2widget[var] = entry
Würde ich statt var das Widget entry als Key verwenden, gibt es keine Fehelrmeldung.

Kann mir das vielleicht kurz erläutern, warum das so ist und ob es doch eine Möglichkeit gibt StringVar als Key im Dict zu verwenden?

Grüße Nobuddy
Versuch doch mal mit tk.StringVar.get() den eigentlichen Text aus dem mutable TK Objekt zuverwenden.
Das kommt aber mit einer Fallgrube.

Wenn du später den Wert in StringVar änderst .. dann ändert sich nicht der key im Dictonary... (Falls das irgendwie wichtig ist..)

Warum ist das so ?
Der Hash zum einsortieren in ein Dict wird aus den Daten des Datenobjekts berechnet. Ändert sich nun ein Objekt, ändert sich auch der Hash.
Wenn sich aber der Hash ändert, müssten alle Dictonary Einträge in denen dieses Objekt als Key verwendet wird aktualisiert werden. Python Objekte wissen aber nicht in welchen Dicts sie als Key verwendet werden.. und überhaupt wäre das vermutlich recht aufwändig.

Der python string ist nicht veränderbar. (Jede manipulation an einen String ist immer eine komplette Neuzuweisung, kein Inplace Modifikation möglich) und kann als Key verwendet werden, nicht aber das TK Objekt StringVar.



Und bitte .. verwende nicht 'var' als Namen für eine Variable .. das ist eher eine Bezeichnung denn ein Name.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ein GUI Widget als Schlüssel zu verwenden, ist ungewöhnlich. Wenn möglich, dann nutze einen String zur Beschreibung des Widget. Das eigentliche Widget wäre dann eher der Wert.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo pyHoax,

Beispiel:
print(StringVar())
> PY_VAR22

Also, wenn ich Dich richtig verstehe, verbirgt sich hinter 'PY_VAR22' ein Hashwert.
Deine Begründung dazu, verstehe ich.

Leider ist in diesem Fall 'StringVar().get()', keine Lösung für das was ich vorhatte.
In einer anderen Funktion möchte ich die Werte aus StringVar() abfragen.
Entsprechen die Werte nicht der Vorgabe, möchte ich in das Eingabefeld springen, um eine Korrektur vornehmen zu können.
Beispiel:

Code: Alles auswählen

for var in self.data_vars:
    wert = var.get()
    try:
        int(wert)
    except ValueError:
        self.var2widget[var].set_focus()
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Also, wenn ich Dich richtig verstehe, verbirgt sich hinter 'PY_VAR22' ein Hashwert.
Ja ich glaub da ist definitiv ein Hashwert bereits berechnet.. ich vermute Python verwendet es selbst bereits in seinen Interna.

Der 'is' Operator prüft ob zwei Objekte identisch sind (also nicht einfach nur gleich)

>>> "123" is "123"
True

wärend bei Mutable Objects der Liste:
>>> [1,2,3] is [1,2,3]
False

Und ausserdem:
>>> "123" is "1234"[:3]
False

Ohh .. Feierabend.. tschüss. :(
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

So ... das mit dem 'is' operator und den Beispielen war nicht zielführend. (Meine Vermutung ist das string Literale im Sourcecode bereits beim Übersetzen einen Hash erhalten... weil gleiche Stringliterale im selben Objekt gespeichert werden ... ist aber Spekulation)

Der Hashwert wird nur dann Berechnet wenn er benötigt wird... und ich vermute dann auch nur einmal.
Python besitzt einen Speicherbereich in dem er die Strings speichert und erstellt dann für jedes Stringobjekt eine Datenstruktur in der steht wo im Speicher der Text anfängt, wie lang er ist .. und vermutlich wenn einmal berechnet wie der Hashwert dieses Strings lautet.

Benötigt wird der Hashwert eines Objektes dann wenn dieses Objekt in bestimmten Container verwendet werden soll.
In einen Dictionary benötigen die Objekte für den Key (nicht aber die Werte) einen Hashwert.


>>> s = my_var.get() # "PY_VAR22"
Hier ist noch kein Hash nötig (ausser den für den Variablennamen selber)
Wenn du jetzt aber ein dict wie folgt anlegst.
>>> d = { s: my_var }
.. dann muss Python für den Wert von 's' den Hash berechnen (weil der wird vom Dict zumm Speichen von 'my_var' benötigt)
Das geht auch per Hand in python mit.. flas es für dich von interesse ist ;)
>>> print (hash(s))

Um jetzt wieder an dein my_var Objekt aus dem dict heranzukommen schreibst du
>>> my_var = d['PY_VAR22']

Ich war mir nicht sicher ob du begriffen hast wie ein dict funkioniert, wenn ja, dann hab ich dein Problem noch nicht verstanden.
Zuletzt geändert von pyHoax am Montag 16. Januar 2017, 19:52, insgesamt 1-mal geändert.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Der is Operators hat überhaupt nichts mit hashes zu tun. Unveränderbare Objekte werden einfach unter bestimmten Umständen wiederverwendet. Das spart Speicher und erhöht unter Umständen die Geschwindigkeit.
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

Wie wäre es damit ?:

Du verwendest kein Dict in dem du die Werte speicherst sondern eine Liste in den du deine Widgets speicherst.

Bsp:

Code: Alles auswählen

my_widgets = [ tk_widget_1, tk_widget_2....]

for widget in my_widgets:
    try:
        int(widget.StringVar())
    except ValueError:
        self.widget.set_focus()
Wenn eines deiner Widgets aus der Liste my_widgets keinen durch int() akzeptablen Wert enthällt bekommt es den Focus.

Allerdings solltest du überlegen ein break einzubauen. Wenn mehrer Widgets keinen int wert haben, springt der Focus durch alle diese Elemente durch und verbleibt dann beim letzten in der Liste (ohne int wert)

Ich hab noch nicht mit Tk gearbeitet, doch mich würde es nicht wurden wenn das mit dem <FocusOut> Event noch besser zu erledigen ist.
Prüfe nur dann den Wert deiner Widgets wenn dieser das <FocusOut> Event senden.. und setzt den Focus wieder auf das Widget zurück wenn der User da kein Int eingetragen hat. (Ich setzte voraus das du keine falschen Werte in die Widgets geschrieben hast.. sonder der Anwender)

Der is Operators hat überhaupt nichts mit hashes zu tun.
Absolut.. Notiz an mich, brain storming auf papier oder am whiteboard nicht im Forum ;)
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@pyHoax: Deinen Einsatz in Ehren, aber wildes Raten hilft dem Fragesteller nicht wirklich weiter, sondern sorgt eher noch für zusätzliche Verwirrung. Befasse dich mal mit __repr__ bzw __str__, dann geht dir vielleicht ein Licht auf, warum es mal ein nackter String ist und beim anderen Mal nicht. ;)
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen

pyHoax, Du hattest mich nicht richtig verstanden, daher lagen Deine Lösungen auch falsch.
Trotzdem Danke für Deine Hilfe!

snafu, hat mich da auf den richtigen Weg gebracht, Danke!

Da der Key-Wert von StrinVar() in einem Dictionary zu einem TypeError führt, habe ich es mit __repr__ versucht und siehe da, es funktioniert genauso wie ich es mir vorgestellt habe.
Nun fragt man sich, warum es gerade StrinVar() als Key-Wert sein muss, möchte ich dafür ein kurzes Beispiel nehmen.
self._bestellmenge_ = StrinVar()

Code: Alles auswählen

        try:
            bestellmenge = int(self._bestellmenge_.get())
        except ValueError:
            t0 = 'Abbruch, falscher Wert bei Bestellmenge!'
            MB().ok(t0)
            var = self._bestellmenge_.__repr__()
            widget = self.view.widget2var[var]
            widget.focus_set()
Grüße Nobuddy
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: __repr__ wird nicht direkt aufgerufen, sondern die Funktion repr, aber statt der Repräsentation solltest Du die ID verwenden, die ist auf jeden Fall eindeutig: widget2var[id(self.bestellmenge)]. Alternativ kann man einfach das zugehörige Widget als Attribut von StringVar setzen. Die Unterstriche hinter den Attributennamen gehören weg.

Warum benutzt Du überhaupt StringVar und nicht gleich das Widget:

Code: Alles auswählen

[...]
self.bestellmenge = tk.Entry([...])
[...]
        try:
            bestellmenge = int(self.bestellmenge.get())
        except ValueError:
            MessageBox().ok('Abbruch, falscher Wert bei Bestellmenge!')
            self.bestellmenge.focus_set()
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

Hallo snafu,

ich erstelle eine tkinter-GUI, die aus unveränderlichen Datenfeldern tk.Label und veränderlichen Datenfeldern tk.Entry bestehen.
Poste hier mal kurz einen Auszug:

Code: Alles auswählen

            for i2column in range(2):
                var = tk.StringVar()
                text = self._dict_[i2column][i]
                if name == 'INHALT' or name == 'BESTELLMENGE':
                    color = 'lightgreen'
                    entry = tk.Entry(self.canvas2, width=self.max_width,
                        textvariable=var, font=(self.txt2), bg=color,
                        bd=1, highlightthickness=1)
                    entry.grid(row=i, column=i2column, padx=self.ipadx,
                        pady=self.ipady)
                    entry.bind("<FocusIn>", self.activate)
                    entry.bind("<FocusOut>", self.deactivate)
                    self.xpos += entry.winfo_reqwidth()
                    self.widget2var[var.__repr__()] = entry
Da ich aus tk.StringVar() in einem späteren Prozess, Werte auslese und verarbeite, kenne ich dann den Namen von tk.StringVar(), aber nicht das betreffende Widget.
Beispiel:

Code: Alles auswählen

for var in entry_vars:
    print(var)
    > PY_VAR22 # Namen
    print(var.get())
    > 'abc' # Wert
    .... usw.
Wenn ich also den Namen von tk.StringVar() für das Feld xy kenne, kenne ich von meinem Dictionary ({Namen_StringVar : Widget} auch das Widget und kann somit auf das Widget direkt zugreifen.

Beim Versuch widget2var[id(self.bestellmenge)] und beim späteren Aufruf id(self.bestellmenge), sind die id_Werte nicht identisch.
Auch self.bestellmenge.focus_set(), funktioniert nicht.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Ich hatte zwar nicht wirklich vorgeschlagen, die Rückgabe der __repr__()-Methode als Schlüssel zu verwenden, aber schön wenn es hilfreich war. Übrigens sollte man, wie schon erwähnt wurde, besser repr() verwenden anstatt direkt die Methode aufzurufen.

Dass eine Objekt-ID nicht einzigartig ist, kann nur dann passieren wenn zwischendurch Objekte vom GC abgeräumt wurden, da die Garantie der Einzigartigkeit nur für Objekte gegeben wird, die noch existieren. Das halte ich diesem Szenario aber für sinnfrei, denn warum sollte man im Dictionary auf ein Objekt verweisen, das nicht mehr existiert? Vermutlich liegt da eher ein konzeptioneller Fehler in deinem Programm vor. Welche Fehlermeldung wird denn angezeigt bzw woran machst du fest, dass es mit id() nicht funktioniert?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: bisher hast Du noch nicht plausibel gemacht, warum Du ein StringVar-Objekt brauchst. Das Problem würde nicht bestehen, wenn Du direkt mit Entry-Objekten arbeitest.
Nobuddy
User
Beiträge: 994
Registriert: Montag 30. Januar 2012, 16:38

@snafu, Danke für Deinen Hinweis, repr() statt __repr__ zu verwenden.
Das mit id() funktioniert auch, war wohl ein Fehler meinerseits.

@ Sirius3, habe hier noch mal ein Beisspiel:

Code: Alles auswählen

self.entry_vars = list()
self.widget2var = dict()
for text in bestellliste:
    var = tk.StringVar()
    entry = tk.Entry(self.canvas2, width=self.max_width, textvariable=var,
        font=(self.txt2), bd=1, highlightthickness=1)
    entry.grid(row=i, column=i2column, padx=self.ipadx, pady=self.ipady)
    var.set(text)
    self.entry_vars.append(var)
    self.widget2var[id(var)] = entry

    # Print-Ausgabe
    print(var)
    >> PY_VAR38
    print(id(var))
    >> 139880276731496
    print(entry)
    >> 139880276646096.139880276701312.139880276701592.139880276731552
    print(id(entry))
    >> 139880276731552

## In andrerer Funktion.
for var in self.entry_vars:
    # Print-Ausgabe
    print(var.get())
    >> 125.99
    print(var)
    >> PY_VAR38
    print(id(var))
    >> 139880276731496
    widget = self.widget2var[id(var)]
    print(widget)
    >> 139880276646096.139880276701312.139880276701592.139880276731552
    widget.focus_set()
Vielleicht kannst Du mir anhand diesem Beispiel, erklären, wie Du Dir das mit 'direkt an Entry-Objekten arbeiten' vorstellst?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Nobuddy: so

Code: Alles auswählen

font = self.txt2
self.entries = list()
for text in bestellliste:
    entry = tk.Entry(self.canvas2, width=self.max_width, font=font, bd=1, highlightthickness=1)
    entry.grid(row=i, column=i2column, padx=self.ipadx, pady=self.ipady)
    entry.insert(0, text)
    self.entries.append(entry)
Antworten