merkwürdiges Verhalten QIntValidator

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

folgendes Prog funktioniert, aber der Validator verhält sich IMHO komisch:

Code: Alles auswählen

#!/usr/bin/env python
# ~*~ coding: utf-8 ~*~

import sys

from PySide import QtGui

class SimpelQt(QtGui.QDialog):
    def __init__(self,parent=None):
        super(SimpelQt, self).__init__(parent)
        self.setWindowTitle('Simpel Qt')
        layout = QtGui.QVBoxLayout()
        self.eingabe = QtGui.QLineEdit()
        self.eingabe_valid = QtGui.QIntValidator(0,1000,self)
        self.eingabe.setValidator(self.eingabe_valid)
        layout.addWidget(self.eingabe)
        self.button = QtGui.QPushButton(u'Quadrieren')
        layout.addWidget(self.button)
        self.label = QtGui.QLabel(u'Ergebnis...')
        layout.addWidget(self.label)
        self.setLayout(layout)
        self.button.clicked.connect(self.rechnen)

    def rechnen(self):
        wert = int(self.eingabe.text())
        return self.label.setText(unicode(wert*wert))

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    simpel_qt = SimpelQt()
    simpel_qt.show()
    sys.exit(app.exec_())
Gibt man z.B. "1.1" oder "1,1" erhält man als Ergebnis 121, weil der Punkt bzw. das Komma einfach "verschwinden"...

Bug oder Feature? Wenn Feature - was ist dann der Sinn des _Int_-Validators?

Pyside + Qt4 Version ist die aus den Ubuntu Oneiric Quellen.

Gruß, noisefloor
Zuletzt geändert von noisefloor am Sonntag 22. Januar 2012, 19:49, insgesamt 1-mal geändert.
BlackJack

@noisefloor: Wie gibst Du denn "1.1" ein? Den '.' oder das ',' kannst Du doch gar nicht eingeben? Und genau das ist der Sinn von dem Validator — der Inhalt muss zu *jeder* Zeit einem `int` im Wertebereich entsprechen. Etwas anderes wird gar nicht erst zugelassen.
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Etwas anderes wird gar nicht erst zugelassen.
Das dachte ich auch. Aber es wird genommen... Ganz regulär über die Tastatur :?:

Buchstaben werden in der Tat ignoriert.

Kann das jemand bei sich nachvollziehen mit dem obigen Programm?

Gruß, noisefloor
BlackJack

@noisefloor: Also ich kann es nicht nachvollziehen. Ich kann nur Ziffern eingeben.
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

strange... Ich kann als _erste_ Eingabe auch keinen Punkt oder Komma machen. Wenn ich zuerst aber eine Zahl eingebe, dann nimmt er . und , ...

Muss das morgen mal unter Windows testen...

Gruß, noisefloor
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Unter Windows habe ich das Gleiche. Erste Ziffer muss eine Zahl sein danach kann man einen Punkt oder ein Komma machen. Bei absenden wird aber beides raus genommen.

€ Nach jeder Zahl kann man ein Punkt oder Komma machen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich hab 's unter Unbuntu (Oneiric) getestet und komme ebenfalls zu dem Ergebnis, dass man entweder Punkte oder Kommata eingeben kann - lustiger Weise nicht beides in einem String!

Man kann das auch ohne GUI drum herum testen:

Code: Alles auswählen

In [19]: from PySide import QtGui

In [20]: v = QtGui.QIntValidator(0, 100, None)

In [21]: v.validate("5.6", 0)
Out[21]: (PySide.QtGui.QValidator.State.Intermediate, u'5.6', 0)

In [22]: v.validate("5,6", 0)
Out[22]: (PySide.QtGui.QValidator.State.Intermediate, u'5,6', 0)

In [23]: v.validate("5,.", 0)
Out[23]: (PySide.QtGui.QValidator.State.Invalid, u'5,.', 0)
Ich vermute mal, dass das irgend wie am `locale` liegt...

Ich würde als parent vom Validator übrigens `self.eingabe`, also das zugehörige QLineEdit, wählen. Ändert natürlich nichts am Problem ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

Bei mir sieht das so aus:

Code: Alles auswählen

In [147]: v.validate("5.6", 0)
Out[147]: (PySide.QtGui.QValidator.State.Invalid, u'5.6', 0)
Aber „locale” scheint eine vernünftige Vermutung, wenn man zum Beispiel in einer angelsächsischen „locale” '50.0' oder "1,000,000" als Schreibweisen für ganze Zahlen zulassen würde. Dann müsste auch '.' und ',' in einer Zahl gehen: "4,711.23". Für eine deutsche „locale” müsste man dann die beiden Trennzeichen vertauschen.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Also bei mir klappt das aber so auch nicht:

Code: Alles auswählen

In [61]: from PySide import QtCore

In [62]: loc = QtCore.QLocale()

In [63]: loc.groupSeparator()
Out[63]: u'.'

In [64]: loc.toInt("12")
Out[64]: (12, True)

In [65]: loc.toInt("1.2")
Out[65]: (0, False)

In [66]: loc.numberOptions()
Out[66]: 0
Genau das Verhalten würde ich erwarten - aber der `QIntValidator` verhält sich da ja anders :-(

Auch das Einstellen mittels `setNumberOptions` funzt nicht :-(

Code: Alles auswählen

In [67]: loc.setNumberOptions(QtCore.QLocale.OmitGroupSeparator)

In [68]: loc.toInt("1.2")
Out[68]: (0, False)

In [69]: loc.numberOptions()
Out[69]: 1

In [70]: loc.setNumberOptions(QtCore.QLocale.RejectGroupSeparator)

In [71]: loc.toInt("1.2")
Out[71]: (0, False)

In [72]: loc.numberOptions()
Out[72]: 2
Das wäre jetzt eine Erklärung gewesen, wie der Validator evtl. vorgehen könnte... ist aber leider auch nicht so.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@Hyperion: Was wolltest Du damit jetzt zeigen? Wenn man den „group separator” an einer ungültigen Stelle setzt, dann ist das natürlich auch eine ungültige Zahl. Setz ihn doch mal korrekt.

Code: Alles auswählen

In [155]: loc.groupSeparator()
Out[155]: u','

In [156]: loc.toInt("1,2")
Out[156]: (0, False)

In [157]: loc.toInt('1,200')
Out[157]: (1200, True)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

*patsch* OMG... ja, manchmal hat man ein Brett vorm Kopf :-D

Code: Alles auswählen

In [83]: loc.toInt(u"1.200")
Out[83]: (1200, True)

In [84]: loc.setNumberOptions(QtCore.QLocale.RejectGroupSeparator)

In [85]: loc.toInt(u"1.200")
Out[85]: (0, False)
Ok, das passt dann ja. Damit wäre es wohl eine Lösung, die `NumberOptions` entsprechend zu setzen, wenn man keine Punkte oder Kommata akzeptiert haben will.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
JonasR
User
Beiträge: 251
Registriert: Mittwoch 12. Mai 2010, 13:59

Ich kann bei mir auch Sachen wie 1,1,1, eingeben... natürlich das gleiche mit Punkten
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

bleibt aber die Frage: Ist das ein Bug, den man mal melden sollte?

Gruß, noisefloor
lunar

@Alle: Dieses Verhalten ist kein Fehler, sondern Feature, und im Übrigen auch dokumentiert. Nur hat hier offenbar niemand die Dokumentation gelesen. Der Sinn von "QIntValidator" ist nicht, einfach nur Ziffern als Eingabe zu akzeptieren. Das wäre trivial (siehe Ende dieses Beitrags). "QIntValidator" ermöglicht eine den Spracheinstellungen entsprechende Eingabe von Zahlen, und mithin auch Tausender-Trennzeichen.

Grundsätzlich kann man in "QIntValidator" dabei sowohl das den Spracheinstellungen entsprechende Eingabeformat nutzen also auch dasjenige der Standard-C-Lokalisierung:
[...] QIntValidator uses its locale() to interpret the number. For example, in Arabic locales, QIntValidator will accept Arabic digits. In addition, QIntValidator is always guaranteed to accept a number formatted according to the "C" locale.
Mithin kann man bei deutscher Spracheinstellung sowohl den Punkt als auch das Komma als Tausender-Trennzeichen nutzen, nicht aber beides gleichzeitig. Im ersten Fall parst Qt die Zeichenkette gemäß der deutschen Spracheinstellung, im letzteren Fall gemäß den Regeln der POSIX-Lokalisierung.

Fehlerhaft platzierte Tausender-Trennzeichen werden in "QIntValidator.fixup()" wieder entfernt, damit die Eingabe als Zahl geparst werden kann. Anders ließe sich eine Eingabe von Zahlen mit Tausender-Trennzeichen innerhalb der Möglichkeiten von "QValidator" gar nicht umsetzen, da man von vorne herein nie sicher sein kann, ob nun noch Ziffer nach dem Trennzeichen eingegeben werden oder nicht. Du kannst zwar von "QIntValidator" ableiten und ".fixup()" überschreiben, musst dann aber auch ".validate()" so verändern, dass "Invalid" zurückgegeben wird, wenn das Tausender-Trennzeichen an falscher Stelle steht. Das ist nicht trivial.

Zumal Dein Quelltext ohnehin fehlerhaft ist, da Du "int()" verwendest, um die validierte Eingabe zu parsen. "int()" ignoriert die Spracheinstellungen der Umgebung, die validierte Eingabe ist aber abhängig von der Lokalisierung. Wenn Du "QIntValidator" verwendest, musst Du die Eingabe mit "QLocale" parsen.

In Deinem Fall bist Du allerdings wohl besser bedient mit einem eigenen, ziemlich trivialen "QValidator":

Code: Alles auswählen

class StrictIntValidator(QValidator):
    def __init__(self, min, max, parent=None):
        QValidator.__init__(self, parent)
        self.min = min
        self.max = max

    def validate(self, input, pos):
        try:
            value = int(input.strip())
            state = QValidator.Acceptable if self.min <= value <= self.max else QValidator.Invalid
            return state, input, pos
        except ValueError:
            return QValidator.Invalid
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

@lunar: Ah, Danke für die Erklärung. Jetzt habe ich es auch verstanden. :-)

Wobei ich selber nie auf die Idee kommen würde, ein Zahl mit Tausender-Separatoren einzutippen - weder in Qt noch in Excel noch in ... aber egal, anderes Thema.

Gruß, noiselfoor
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@lunar: Doch ich habe die Doku schon durchgelesen - nur hatte ich keine zunächst keine Ahnung, was man mit diesen locale-Sachen alles anstellen kann. Und speziell die `fixup`-Methode ist eher schlecht dokumentiert! Durch BlackJacks Anmerkung ist mir dann schon klar geworden, dass sich die Trennzeichen nur an bestimmten Stellen stehen dürfen; dass `QIntValidator` die entsprechend rausnimmt, wenn eine gültige Zahl noch möglich wäre, war doch dann imho klar?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@noisefloor: Ich habe gerade auf uu.de Deinen Blog-Eintrag gelesen. Mir fiel dabei auf, dass Du eine Variante mit `argparse` nicht aufgeführt hast. Meiner Meinung nach wäre das doch die simpelste Lösung vom Code her:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import argparse

parser = argparse.ArgumentParser(u"Programm zum Quadrieren von Integerzahlen.")
parser.add_argument("value", type=int)

args = parser.parse_args()
print args.value * args.value
Nach Deiner Zählweise wären das fünf Zeilen Code - deutlich kürzer als das selbst gebaute Command Line Parsing ;-)

Ich habe bis vor kurzem bei simplen Scripten auch immer auf `argparse` verzichtet; lunar brachte mich mit einem Posting wieder in die "Spur" und zeigte, wie einfach dieses auf den ersten Blick abschreckende Modul sein kann :-)

(Zugegebener Maßen sind die Fehlermeldungen nicht auf deutsch und nicht exakt so, wie Du sie in Deinem Script hast)

Ist es eine bewusste Entscheidung, dass Du auf Syntaxhighlighting in Deinem Blog verzichtest? Ich würde doch dafür plädieren :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
lunar

Zumal sich Vergleich relativiert, wenn man die GUI im Designer erzeugt.
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Ist es eine bewusste Entscheidung, dass Du auf Syntaxhighlighting in Deinem Blog verzichtest? Ich würde doch dafür plädieren
Nein. Nur bin ich durch die Untiefen von eignenen Stylesheets etc. noch nicht durch gestiegen. Zumal das von Blogspot.com erzeugte HTML sehr... komisch ist.

Gruß. noisefloor
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

noisefloor hat geschrieben:Hallo,
Ist es eine bewusste Entscheidung, dass Du auf Syntaxhighlighting in Deinem Blog verzichtest? Ich würde doch dafür plädieren
Nein. Nur bin ich durch die Untiefen von eignenen Stylesheets etc. noch nicht durch gestiegen. Zumal das von Blogspot.com erzeugte HTML sehr... komisch ist.

Gruß. noisefloor
Bei „yacoding.blogspot.com“ wird zum Beispiel erklärt wie man das einrichten kann. ;) Als Hoster bieten sich zum Beispiel Dropbox oder ein Paste-Service (mit Unterstützung für das Anzeigen des „rohen“ Pastes) an. Wobei ich

Code: Alles auswählen

<script type="text/javascript" src="..."></script>
benutzen würde.
Antworten