Zustandszähler, Fehlerzähler oder etwas in der Art... ;-)

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.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Hallo,

wenn ich ein Argument `weekday` (position, weekday) auf Position und Wochentag überprüfe, kann es 6 mögliche Fehler geben:

Code: Alles auswählen

pos       wd
-----------------
ok        err
ok        missing
err       ok
missing   ok
err       err
missing   missing
Jetzt suche ich nach einer Möglichkeit, etwas wie einen Zähler zu implementieren, aus dessen Ergebnis ich dann die passende Fehlermeldung generiere. Zuerst dachte ich daran, schlicht eine Zahl hochzuzählen, was aber dann doch ganz schön tricky ist. Jetzt habe ich mir überlegt, ``ok`` als 11, ``err`` als 01 und ``missing`` als 10 darzustellen

Code: Alles auswählen

pos          wd
------------------------------
ok      11 + err     01 = 1101
ok      11 + missing 10 = 1110
err     01 + ok      11 = 0111
missing 10 + ok      11 = 1011
err     01 + err     01 = 0101
missing 10 + missing 10 = 1010
weiß aber nicht, wie man daraus einen Zähler machen kann.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Kannst Du da mal ein wenig mehr Kontext geben? Mir ist überhaupt nicht klar, was Du erreichen willst!

Meine Standardantwort wäre: Definiere eben Exceptions und wirf die passende (oder eine mit einem Fehlercode und textueller Beschreibung oder so).
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Code: Alles auswählen

def validate_weekday(arg):
    '''`arg`: position + weekday
    position: '-1', '1' till '5'
    weekday: 'mo' till 'su'
    `arg`: e.g. '1mo', '-1fr'... '''
    pos_error = ''
    wd_error = ''
    position, weekday = re.match(r'(-*\d+)?(.*)?', arg).groups()
    if position:
        try:
            position = int(position)
        except ValueError:
            pos_error = '01'
        if 0 < position < 6 and not position == -1:
            # internally position 0=first, 1=second ...
            position -= 1
        else:
            pos_error = '01'
    else:
        pos_error = '10'
    if weekday:
        try:
            weekday = utils.WEEKDAYS.index(weekday[0:2].lower())
        except ValueError:
            wd_error = '01'
    else:
        wd_error = '10'
    error = {
        '1101': 'invalid weekday',
        '1110': 'missing weekday',
        '0111': 'invalid position',
        '1011': 'missing position',
        '0110': 'invalid position and missing weekday',
        '1001': 'missing position and invalid weekday',
        '0101': 'invalid position and weekday',
        '1010': 'missing position and weekday'
    }.get(pos_error + wd_error, None)
    if error:
        raise ValueError(error)
    return position, weekday
Ich suche nach einer Möglichkeit, statt diesem `wd_error` / `pos_error` Irrsinn etwas wie einen Zähler zu installieren.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Führe doch einfach 3 verschiedene Konstanten für die Ergebniswerte ein: OK, MISSING, INVALID (z.B. mit den Zahlen 0-2 belegt). In deiner Funktion wird dann an `result_pos` und `result_weekday` die der Situation entsprechende Konstante gebunden. Die passende Fehlermeldung erzeugst dann, indem du abfragst, welche Konstanten jeweils gesetzt wurden. Die Idee mit den Bitwerten - und dann auch noch als Strings(!) - finde ich recht skurril.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

so?

Code: Alles auswählen

def validate_weekday(arg):
    '''`arg`: position + weekday
   position: '-1', '1' till '5'
   weekday: 'mo' till 'su'
   `arg`: e.g. '1mo', '-1fr'... '''
    errors = []
    position, weekday = re.match(r'(-*\d+)?(.*)?', arg).groups()
    if position:
        try:
            position = int(position)
            if position not in (-1,1,2,3,4,5):
                raise ValueError
            position -= 1
        except ValueError:
            errors.append('invalid position')
    else:
        errors.append('missing position')
    if weekday:
        try:
            weekday = utils.WEEKDAYS.index(weekday[0:2].lower())
        except ValueError:
            errors.append('invalid weekday')
    else:
        errors.append('missing weekday')
    if errors:
        raise ValueError(' and '.join(errors))
    return position, weekday
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier mal eine andere Variante (ungetestet):

Code: Alles auswählen

NO_ERROR = 0
ERROR_INVALID = 1
ERROR_MISSING = 2

def validate_weekday(arg):
    '''`arg`: position + weekday
   position: '-1', '1' till '5'
   weekday: 'mo' till 'su'
   `arg`: e.g. '1mo', '-1fr'... '''
    pos_error = NO_ERROR
    wd_error = NO_ERROR
    position, weekday = re.match(r'(-*\d+)?(.*)?', arg).groups()
    if position:
        try:
            position = int(position)
        except ValueError:
            pos_error = ERROR_INVALID
        if 0 < position < 6 and not position == -1:
            # internally position 0=first, 1=second ...
            position -= 1
        else:
            pos_error = ERROR_INVALID
    else:
        pos_error = ERROR_MISSING
    if weekday:
        try:
            weekday = utils.WEEKDAYS.index(weekday[0:2].lower())
        except ValueError:
            wd_error = ERROR_INVALID
    else:
        wd_error = ERROR_MISSING
    if pos_error or wd_error:
        raise ValueError(_generate_error_message(pos_error, wd_error))
    return position, weekday

def _generate_error_message(pos_error, wd_error):
    error_types = ['invalid', 'missing']
    error_messages = [
        '{} position'.format(error_types[pos_error - 1]),
        '{} weekday'.format(error_types[wd_error - 1])
    ]
    return ' and '.join(error_messages)
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ok, mit Konstanten und/oder Stringformatierung kann man das natürlich machen, wenn es nur um die `weekday` Überprüfung innerhalb meiner `MonthlyRecurrence` geht ist das wohl auch das Geschickteste. Wenn ich aber zusätzlich noch ein `month` Argument innerhalb einer `YearlyRecurrence` habe, wird das mit den Strings doch ein wenig sperrig, oder?

Darum dachte ich halt eher an "so ein Ding", das einen bestimmten Zustand erhält und fortgeführt werden kann, damit am Ende der ganzen Argumentprüferei quasi ein Zusammenzählen der verschiedenen Fehlerkombinationen zu einer Fehlermeldung a la "Eine jährliche Wiederholung benötigt das Argument `month`. Zudem fehlt dem `weekday` Argument das Positionspräfix." führen kann.

So ähnlich wie unicode codepoints. Eben ein Schlüssel, der am Ende aller Tage auf einen ganz bestimmten Fehler zeigt.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
Balmung
User
Beiträge: 44
Registriert: Sonntag 17. März 2013, 18:36

warum nicht einfach so?

Code: Alles auswählen

def validate_weekday(arg):
    '''`arg`: position + weekday
   position: '-1', '1' till '5'
   weekday: 'mo' till 'su'
   `arg`: e.g. '1mo', '-1fr'... '''
    errors = []
    position, weekday = re.match(r'(-*\d+)?(.*)?', arg).groups()
    if position:
        position = int(position)
        if position not in (-1,1,2,3,4,5):  # oder was immer hier hin muss
            raise ValueError('invalid position')
        # internally position 0=first, 1=second ...
        position -= 1
    else:
        raise ValueError('missing position')
    if weekday:
        weekday = utils.WEEKDAYS.index(weekday[0:2].lower())
    else:
        raise ValueError('missing weekday')
    return position, weekday
Die Exceptions, die auftreten können, kann man doch einfach durchreichen, in dem man sich nicht abfängt. Besonders da es sowieso alles ValueErrors zu sein scheinen..?
Und ich sehe keinen vernünftigen Grund die Fehler zu "sammeln" und zusammen auszugeben.
»Honk Honk«
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Balmung hat geschrieben:Und ich sehe keinen vernünftigen Grund die Fehler zu "sammeln" und zusammen auszugeben.
Ich schon. Wenn ich verschiedene Argumente zu übergeben habe, dann kann es durchaus passieren, dass sich 2 oder sogar mehr Fehler einschleichen. Wenn ich beim ersten Fehler eine Meldung bekomme, verbessere ich den, lasse aber doch meistens den anderen unbemerkt stehen. Somit erscheint nach dem Verbessern wieder eine Fehlermeldung. Mich nervt das. :)

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Hier noch die korrigierte Fassung für die Erzeugung der Fehlermeldung in meinem Codebeispiel:

Code: Alles auswählen

def _generate_error_message(pos_error, wd_error):
    error_types = ['invalid', 'missing']
    pos_error_message = '{} position'.format(error_types[pos_error - 1])
    wd_error_message = '{} weekday'.format(error_types[wd_error - 1])
    if not wd_error:
        return pos_error_message
    elif not pos_error:
        return wd_error_message
    else:
        return '{} and {}'.format(pos_error_message, wd_error_message)
Und ja: Spätestens wenn ein dritter Fall hinzukommt, wird das äußerst hässlich, alle Kombinationen zu beachten - noch weitaus hässlicher als beim Wörterbuch.

Zur Problematik: Ich glaube, mutella möchte tatsächlich alle aufgetretenen Fehler gesammelt ausgeben.

@mutella: Wie wäre es mit einer von ValueError abgeleiteten eigenen Exception, die als zusätzliches Argument einfach eine Liste der aufgetretenen Fehler führt? Ggf möchte man diese Liste auch visuell darstellen ("Die folgenden Fehler sind aufgetreten:" und dann pro Fehler eine eigene Zeile). Die visuelle Ausgabe wäre bei einer Nutzung des Kommandozeilen-Interface hilfreich, während programmintern das Werfen einer Exception mit der besagten Liste für den Programmierer IMHO besser - weil "dezenter" - wäre.
Benutzeravatar
Balmung
User
Beiträge: 44
Registriert: Sonntag 17. März 2013, 18:36

mutetella hat geschrieben:Ich schon. Wenn ich verschiedene Argumente zu übergeben habe, dann kann es durchaus passieren, dass sich 2 oder sogar mehr Fehler einschleichen. Wenn ich beim ersten Fehler eine Meldung bekomme, verbessere ich den, lasse aber doch meistens den anderen unbemerkt stehen. Somit erscheint nach dem Verbessern wieder eine Fehlermeldung. Mich nervt das. :)
Dein Code ist nun nicht sonderlich lang und dein Vorhaben ist deswegen nicht so problematisch..., aber gegen das Sammeln von Fehlern und "raise" am Ende spricht, dass nicht mehr eindeutig klar ist, wo genau im Code der Fehler aufgetreten ist. Besonders wenn die Fehlermeldungen oder die Dokumentation der Funktion nicht ausführlich genug ist, und man in den Code direkt schauen muss um herauszufinden was man falsch gemacht hat, wird es schwierig die Fehlerquelle ausfindig zu machen, wenn die Exception in allen Fällen nur am Ende der Funktion auftritt.
»Honk Honk«
BlackJack

@mutetella: Man muss dann aber aufpassen das man nicht Situationen haben kann bei denen Folgefehler gemeldet werden die nicht gemeldet würden wenn ein Fehler davor nicht gemacht worden wäre. Das ist mindestens genau so nervig.

Apropos Fehler: Das der reguläre Ausdruck auch so etwas wie '--1' oder '-------42' durchgehen lässt, ist gewollt?
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

@snafu

Code: Alles auswählen

NameError: Unknown "mutella", did you mean "mutetella"?
Das mit der eigenen Exception ist 'ne gute Idee, werd' ich mal ausprobieren...

@Balmung
Im Falle eines API hast Du Recht, mir geht es allerdings um die Fehlerbehandlung innerhalb eines CLI. Sorry, hätte ich dazu sagen sollen...
BlackJack hat geschrieben:Das der reguläre Ausdruck auch so etwas wie '--1' oder '-------42' durchgehen lässt, ist gewollt?
Ja, ich finde "Invalid position präfix '--1' and missing weekday" sinniger als "Invalid position präfix '-' and invalid weekday '-1'".

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

mutetella hat geschrieben:@snafu

Code: Alles auswählen

NameError: Unknown "mutella", did you mean "mutetella"?
Huch, ich hab irgendwie immer an Nutella gedacht, wenn ich deinen Namen gelesen habe... :mrgreen:

@Erschwerte Fehlersuche: Man könnte ja per Option auswählen, ob Fehler sofort geworfen werden oder ob sie erst am Ende gesammelt ausgegeben werden sollen.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

snafu hat geschrieben:Huch, ich hab irgendwie immer an Nutella gedacht, wenn ich deinen Namen gelesen habe... :mrgreen:
Seinen Ursprung hat mutetella tatsächlich in Nutella. Aber das ist eine andere Geschichte aus einer anderen Zeit... ;-)
snafu hat geschrieben:@Erschwerte Fehlersuche: Man könnte ja per Option auswählen, ob Fehler sofort geworfen werden oder ob sie erst am Ende gesammelt ausgegeben werden sollen.
Quasi einen "Debug Modus"?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

mutetella hat geschrieben:
snafu hat geschrieben:Huch, ich hab irgendwie immer an Nutella gedacht, wenn ich deinen Namen gelesen habe... :mrgreen:
Seinen Ursprung hat mutetella tatsächlich in Nutella. Aber das ist eine andere Geschichte aus einer anderen Zeit... ;-)
Vielleicht bist du ja auch eine "stille Ella" (muted Ella) in etwas anderer Schreibweise - wer weiß... ;)
mutetella hat geschrieben:
snafu hat geschrieben:@Erschwerte Fehlersuche: Man könnte ja per Option auswählen, ob Fehler sofort geworfen werden oder ob sie erst am Ende gesammelt ausgegeben werden sollen.
Quasi einen "Debug Modus"?
Ja, genau.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

mutetella hat geschrieben:Darum dachte ich halt eher an "so ein Ding", das einen bestimmten Zustand erhält und fortgeführt werden kann, damit am Ende der ganzen Argumentprüferei quasi ein Zusammenzählen der verschiedenen Fehlerkombinationen zu einer Fehlermeldung a la "Eine jährliche Wiederholung benötigt das Argument `month`. Zudem fehlt dem `weekday` Argument das Positionspräfix." führen kann.
Und was bitte schön an meiner Lösung widerspricht Deinen Anforderungen :evil:
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Sirius3 hat geschrieben:Und was bitte schön an meiner Lösung widerspricht Deinen Anforderungen :evil:
Nichts, aber das wusste ich noch nicht... :twisted:

Ich hab' mal damit begonnen, Deine Lösung mit dem Vorschlag von snafu zu verbinden:

Code: Alles auswählen

DEBUG = False

class ErrorRake(Exception):
    def __init__(self, values):
        self.values = values

def test(a, b):
    errors = []
    try:
        int(a)
    except ValueError:
        if DEBUG:
            raise
        else:
            errors.append('{!r} fails'.format(a))
    try:
        int(b)
    except ValueError:
        if DEBUG:
            raise
        else:
            errors.append('{!r} fails'.format(b))
    if errors:
        raise ErrorRake(errors)

def run(*args):
    errors = []
    for arg in args:
        try:
            test(*arg)
        except ErrorRake as err:
            errors.extend(err.values)
    if errors:
        print 'You have these problems:\n{}'.format('\n'.join(errors))

Code: Alles auswählen

>>> run(('a', 'b'), ('1', ''))
You have these problems:
'a' fails
'b' fails
'' fails
Geht das in die richtige Richtung? Eine Fehlerklasse, die letztlich nur als Transportbehälter dient?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Nee, ich meinte eher eine Fehlermeldung mit allgemein gehaltenem Text und dann halt ein Attribut wie `.errors`, damit bei Bedarf sozusagen die Details von der Exception abgefragt werden können. Das Darstellen für die CLI-Ebene würde ich in eine Hilfsfunktion auslagern (`get_error_message(exc)` oder sowas in der Art). Diese Hilfsfunktion würde halt die spezielle Exception erwarten und einen hübschen Fehlertext und Zuhilfenahme des `.errors`-Attributs generieren, welcher als mehrzeiliger String zurückgeliefert wird. Anwendung für die CLI-Ebene wäre dann:

Code: Alles auswählen

try:
    funktionsaufruf(foo, bar)
except MyException as exc:
    print(get_error_message(exc))
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Das letztendliche Auslösen der exception ist glaube ich nicht mein Problem, sondern das Einsammeln der Fehler über die verschiedenen Namensräume hinweg. Wenn `errors` ein Attribut meiner Fehlerklasse sein soll, wie befülle ich das dann bzw. wo erstelle ich die Instanz, damit ich deren `errors` Attribut in den diversen ``try:...except:...`` Blöcken ansprechen kann?

Code: Alles auswählen

DEBUG = False

class ErrorRake(Exception):
    def __init__(self):
        self.errors = []

    def __nonzero__(self):
        return bool(self.errors)

def test(a, b):
    err = ErrorRake()
    try:
        int(a)
    except Exception:
        if DEBUG:
            raise
        else:
            err.errors.append('{!r} fails'.format(a))
    try:
        int(b)
    except Exception:
        if DEBUG:
            raise
        else:
            err.errors.append('{!r} fails'.format(b))
    if err:
        raise err

def run(*args):
    err = ErrorRake()
    for arg in args:
        try:
            test(*arg)
        except Exception as e:
            err.errors.extend(e.errors)
    if err:
        print 'You have these problems:\n{}'.format('\n'.join(err.errors))
Das ist doch alles Quatsch... :( Aber wie bekomme ich eben die eventuellen Fehler aus den `test()` Aufrufen in meine Fehlerklasse?

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten