Seite 1 von 1

TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 16:44
von Nobuddy
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

Re: TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 17:06
von pyHoax
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.

Re: TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 17:31
von snafu
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.

Re: TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 17:58
von Nobuddy
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()

Re: TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 18:26
von pyHoax
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. :(

Re: TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 19:35
von pyHoax
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.

Re: TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 19:40
von DasIch
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.

Re: TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 19:47
von pyHoax
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 ;)

Re: TypeError: StringVar

Verfasst: Montag 16. Januar 2017, 21:37
von snafu
@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. ;)

Re: TypeError: StringVar

Verfasst: Mittwoch 18. Januar 2017, 09:36
von Nobuddy
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

Re: TypeError: StringVar

Verfasst: Mittwoch 18. Januar 2017, 09:55
von Sirius3
@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()

Re: TypeError: StringVar

Verfasst: Mittwoch 18. Januar 2017, 11:00
von Nobuddy
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.

Re: TypeError: StringVar

Verfasst: Mittwoch 18. Januar 2017, 13:05
von snafu
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?

Re: TypeError: StringVar

Verfasst: Mittwoch 18. Januar 2017, 13:21
von Sirius3
@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.

Re: TypeError: StringVar

Verfasst: Mittwoch 18. Januar 2017, 14:46
von Nobuddy
@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?

Re: TypeError: StringVar

Verfasst: Mittwoch 18. Januar 2017, 15:14
von Sirius3
@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)