python 3.5 ja / nein Abfrage

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.
codemode89
User
Beiträge: 12
Registriert: Mittwoch 26. September 2018, 17:39

__blackjack__ hat geschrieben: Donnerstag 27. September 2018, 10:54 Das ist eben idiomatisches Python. Dazu mal zwei Einträge aus dem Glossar der Python-Dokumentation:
EAFP
Easier to ask for forgiveness than permission. This common Python coding style assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false. This clean and fast style is characterized by the presence of many try and except statements. The technique contrasts with the LBYL style common to many other languages such as C.

[…]

LBYL
Look before you leap. This coding style explicitly tests for pre-conditions before making calls or lookups. This style contrasts with the EAFP approach and is characterized by the presence of many if statements.
Darauf gehe ich nicht weiter ein mehr als 2 mal zu bestätigen das ihr Recht habt, das es nicht der pythonic way ist kann ich auch nicht tun ^^
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

codemode89 hat geschrieben: Donnerstag 27. September 2018, 11:47
DRY Prinzip verletzt in dem ich eine Variable initialisiere? Ich denke nicht.
Nochmal DRY Prinzip nachlesen und verstehen was da der ausschlaggebende Punkt ist.
Das bezog sich auf deine drei Input-Methoden, die alle bis auf Text & Praedikat das gleiche machen. Und in einer Sprache, in der Funktionen Mitbuerger erster Klass sind, ist beides problemlos loesbar ohne sich so viel zu wiederholen. Und die while-Schleife ist nun schon mehrfach nicht wegen DRY, sondern unidiomatischem Python-Code angesprochen worden. Auch anderswo stehen augenscheinlich Waende rum.
codemode89
User
Beiträge: 12
Registriert: Mittwoch 26. September 2018, 17:39

__deets__ hat geschrieben: Donnerstag 27. September 2018, 11:53 Und die while-Schleife ist nun schon mehrfach nicht wegen DRY, sondern unidiomatischem Python-Code angesprochen worden. Auch anderswo stehen augenscheinlich Waende rum.
siehe mein letzten Post
__deets__ hat geschrieben: Donnerstag 27. September 2018, 11:53 Das bezog sich auf deine drei Input-Methoden, die alle bis auf Text & Praedikat
und return type.
__deets__ hat geschrieben: Donnerstag 27. September 2018, 11:53 das gleiche machen.
Jede get_input Methode hat eine eigene condition sowie ein anderen return value das einzige was sie sich teilen ist das in jeder eine while schleife läuft

Bitte einmal Code Beispiel wie das sonst zu erreichen ist. Ich bin ja lernwillig
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@codemode89: Der Aufbau war in deinem praktischen Beispiel

Code: Alles auswählen

condition = [code und daten die die bedingung ausmachen]
while (!condition)
    do something
    condition = [code und daten die die bedingung ausmachen]
Und das in den eckigen Klammern stand da zweimal identisch im Code und war nicht nur ein trivialer Aufruf, müsste also auch an beiden Stellen gleich geändert werden, wenn man etwas Ändern oder einen Fehler beheben möchte, und verletzt damit das DRY-Prinzip.

`askPositiveInteger()` macht genau eine Sache: Den Benutzer solange nach einer Eingabe fragen bis der eine positive, ganze Zahl eingegeben hat. Das ist auf dieser Abstraktionsebene ein Schritt. Ansonsten würde dieses Argument ja gegen jede Methode sprechen die mehr als eine Operation beinhaltet und `main()`-Methoden komplett verboten machen, denn die machen ja *alles*!

Eine Methode die eine ganze Zahl mit Unter- und Obergrenze abfragt, würde in der Tat so ähnlich aussehen. Ungefähr so:

Code: Alles auswählen

    private static int askAge(int lowerLimit, int upperLimit) {
        if (lowerLimit > upperLimit) {
            throw new IllegalArgumentException("lower limit must not be greater than upper limit");
        }
        while (true) {
            try {
                final var result = Integer.parseInt(askTrimmedNonEmptyString("Alter eingeben: "));
                if (lowerLimit <= result && result <= upperLimit) {
                    return result;
                }
                System.out.printf(
                        "Bitte eine Zahl zwischen %d und %d (inklusive) eingeben!",
                        lowerLimit, upperLimit);
            } catch (NumberFormatException e) {
                System.out.println("Bitte eine ganze Zahl eingeben!");
            }
        }
    }
Was ich da jetzt extrahieren soll ist mir allerdings nicht klar‽ Und warum?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ein Beispiel wie man eine wiederholte Eingabe ohne Code-Duplizierung und mit klarer Abtrennung von Fehlerbedingung gestalten koennte:

Code: Alles auswählen

def ask_until_correct(message, errormessage, conversion):
    while True:
        try:
            return conversion(input(message))
        except ValueError:
            print(errormessage)


def alterspraedikat(value):
    alter = int(value)
    if 1 <= alter <= 120:
        return alter
    raise ValueError


def namenspraedikat(value):
    if not len(value):
        raise ValueError
    return value


def ja_nein_frage(value):
    try:
        return dict(j=True, y=True, n=False)[value.lower()[0]]
    except (IndexError, KeyError):
        raise ValueError


def main():
    alter = ask_until_correct(
        "Bitte Alter angeben: ",
        "Falsche Eingabe, Alter zwischen 1 und 120",
        alterspraedikat,
    )
    name = ask_until_correct(
        "Bitte Namen angeben: ",
        "Falsche Eingabe. Ein Name kann nicht leer sein",
        namenspraedikat,
    )
    raucher =  ask_until_correct(
        "Sind Sie Raucher?",
        "Bitte mit Ja/ja/yes/j/y oder Nein/nein/no/n antworten",
        ja_nein_frage,
    )
    print("hallo", name, "im Alter von", alter)
    print("Rauchen ist ungesund" if raucher else "Nichtraucher haben bessere Zaehne")


if __name__ == '__main__':
    main()
Zuletzt geändert von __deets__ am Donnerstag 27. September 2018, 13:13, insgesamt 1-mal geändert.
Grund: Fehler in namens-test repariert
codemode89
User
Beiträge: 12
Registriert: Mittwoch 26. September 2018, 17:39

__blackjack__ hat geschrieben: Donnerstag 27. September 2018, 12:58 @codemode89: Der Aufbau war in deinem praktischen Beispiel

Code: Alles auswählen

condition = [code und daten die die bedingung ausmachen]
while (!condition)
    do something
    condition = [code und daten die die bedingung ausmachen]
Und das in den eckigen Klammern stand da zweimal identisch im Code und war nicht nur ein trivialer Aufruf
Nein es war beides mal eine Zuweisung mittels eines trivialen Aufrufs der Input Methode und das do somethin wird durch diese Zuweisung ersetzt nicht erweitert.
hier nochmal von Seite 1

Code: Alles auswählen

def nochmal():
    eingabe_von_console = input('Nochmal y oder n : ').lower()
    while eingabe_von_console != "y" and eingabe_von_console != "n":
        eingabe_von_console = input('Nochmal y oder n : ').lower()
    return eingabe_von_console == "y"
Was daran nicht so schön ist das die input Methode nicht gekapselt ist.
__blackjack__ hat geschrieben: Donnerstag 27. September 2018, 12:58 `askPositiveInteger()` macht genau eine Sache: Den Benutzer solange nach einer Eingabe fragen bis der eine positive, ganze Zahl eingegeben hat. Das ist auf dieser Abstraktionsebene ein Schritt. Ansonsten würde dieses Argument ja gegen jede Methode sprechen die mehr als eine Operation beinhaltet und `main()`-Methoden komplett verboten machen, denn die machen ja *alles*!

Eine Methode die eine ganze Zahl mit Unter- und Obergrenze abfragt, würde in der Tat so ähnlich aussehen. Ungefähr so:

Code: Alles auswählen

    private static int askAge(int lowerLimit, int upperLimit) {
        if (lowerLimit > upperLimit) {
            throw new IllegalArgumentException("lower limit must not be greater than upper limit");
        }
        while (true) {
            try {
                final var result = Integer.parseInt(askTrimmedNonEmptyString("Alter eingeben: "));
                if (lowerLimit <= result && result <= upperLimit) {
                    return result;
                }
                System.out.printf(
                        "Bitte eine Zahl zwischen %d und %d (inklusive) eingeben!",
                        lowerLimit, upperLimit);
            } catch (NumberFormatException e) {
                System.out.println("Bitte eine ganze Zahl eingeben!");
            }
        }
    }
Was ich da jetzt extrahieren soll ist mir allerdings nicht klar‽ Und warum?
Nein deine Methode macht 2 Sachen und die sollen nun beide voneinander getrennt werden.
Welche 2 Sachen? siehe vorangegangen Post ich habe keine Lust in jedem Post exakt das zu wiederholen was ich im vorangegangen geschrieben habe.

Siehe Code von __deets__ dieser macht genau eine Sache pro Methode und ist wohl die beste hier gepostete Lösung in der Hinsicht.
Chapeau! Mr __deets__ ;)

Auch wenn hier als Zahlen als Namen zugelassen werden. Aber dies kann an einer Zentralen stelle gefixed werden genau aus dem Grund, das jede Methode nur eine Aufgabe hat.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

``eingabe_von_console = input('Nochmal y oder n : ').lower()`` kommt in der Funktion zweimal vor. Das ist Code- und Datenwiederholung die man trivial vermeiden kann in dem man das mit ``while True`` und ``break`` oder ``return`` löst.

Die Java-Methode macht genau eine Sache. Wie gesagt, es kommt auf die Abstraktionsebene an was als eine Sache betrachtet wird. Für die gegebene Aufgabe sollte das nicht getrennt werden. Für die gegebene Aufgabe ist allerdings auch die Begrenzung auf Ober- und Untergrenze schon nicht gegeben, eine Begrenzung auf positive Werte macht bei Altersangaben aber schon Sinn.

Mit dem „,macht nur eine Sache“ dürfte man ja schon `input()` in Python schon nicht benutzen weil das Ausgabe *und* Eingabe macht. Und was `print()` so alles macht… Deswegen wird doch jetzt niemand ernsthaft Argumentieren wollen man muss alles über `sys.stdout.write()` und `sys.stdin.readline()` an jeder Stelle wo ein `input()` benutzt wurde, selber regeln muss. Oder man dürfte sich in Java die `askString()`-Methode nicht selber schreiben.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Deine Aufteilung des Codes lässt sich ja mittlerweile auch ganz gut in Java machen, ohne das man dafür Klassen schreiben muss. Den `ValueError` habe ich als `Optional<T>` umgesetzt um weniger Ausnahmebehandlung zu haben:

Code: Alles auswählen

import java.util.Optional;
import java.util.Scanner;
import java.util.function.Function;

final class Main {

    private static final Scanner SCANNER = new Scanner(System.in);

    private Main() {}

    private static <T> T askUntilCorrect(String message, String errorMessage,
                                         Function<String, Optional<T>> converter) {
        while (true) {
            System.out.print(message);
            final var result = converter.apply(SCANNER.nextLine());
            if (result.isPresent()) {
                return result.get();
            }
            System.out.println(errorMessage);
        }
    }

    private static Optional<Integer> convertAge(String value) {
        try {
            final var age = Integer.parseInt(value);
            return 1 <= age && age <= 120 ? Optional.of(age) : Optional.empty();
        } catch (NumberFormatException e) {
            return Optional.empty();
        }
    }

    private static Optional<String> convertName(String value) {
        return (value.isEmpty()) ? Optional.empty() : Optional.of(value);
    }

    private static Optional<Boolean> convertYesNo(String value) {
        if (value.isEmpty()) {
            return Optional.empty();
        } else {
            switch (value.toLowerCase().charAt(0)) {
                case 'y':
                    case 'j': return Optional.of(true);
                case 'n': return Optional.of(false);
                default: return Optional.empty();
            }
        }
    }

    public static void main(String... args) {
        final var age = askUntilCorrect("Bitte Alter angeben: ",
                "Falsche Eingabe, Alter zwischen 1 und 120", Main::convertAge);
        final var name = askUntilCorrect("Bitte Namen angeben: ",
                "Falsche Eingabe. Ein Name kann nicht leer sein.", Main::convertName);
        final var doesSmoke = askUntilCorrect("Sind sie Raucher? ",
                "Bitte mit Ja/ja/yes/j/y oder Nein/nein/no/n antworten", Main::convertYesNo);

        System.out.printf("Hallo %s im Alter von %d.\n", name, age);
        System.out.println((doesSmoke) ? "Rauchen ist ungesund." : "Nichtraucher haben bessere Zähne.");
    }
}
Was mir an der Aufteilung nicht so gefäll,t ist das Wissen was in der Fehlernachricht *und* in den Konvertierungsmethoden steckt, also das man beispielsweise um die Altersgrenzen zu ändern sowohl die `convertAge()` als auch an ganz anderer Stelle die Zahlen in der Fehlermeldung anpassen muss.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

@__blackjack__ das Altersprädikat kann man ja auch problemlos als closure definieren, welcher die Grenzen an der Einsatzstelle bekommt. Somit liegen Definition und passende Fehlermeldung nahe beieinander. Und dann gibt’s ja auch noch zu guter letzt Konstanten, die man zentral definiert für beides nutzen kann.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Na dann mal in Kotlin, da kann man die Prädikatsfunktion auch als anonyme Funktion beim Aufruf angeben und ist so ziemlich nah an der Zeichenkette mit der Fehlermeldung:

Code: Alles auswählen

private tailrec fun <T> askUntilCorrect(message: String, errorMessage: String,
                                        convert: (s: String) -> T?): T {
    print(message)
    val result = convert(readLine() ?: error("Can't read from stdin"))
    return if (result == null) {
        println(errorMessage)
        askUntilCorrect(message, errorMessage, convert)
    } else result
}

fun main(args: Array<String>) {
    val age = run {
        val minAge = 1
        val maxAge = 120
        askUntilCorrect(
                "Bitte Alter angeben: ",
                "Falsche Eingabe, Alter zwischen $minAge und $maxAge."
        ) { string -> string.toIntOrNull()?.takeIf { it in minAge..maxAge } }
    }

    val name = askUntilCorrect(
            "Bitte Namen angeben: ",
            "Falsche Eingabe. Ein Name kann nicht leer sein."
    ) { it.trim().takeUnless(String::isEmpty) }

    val doesSmoke = askUntilCorrect(
            "Sind sie Raucher? ",
            "Bitte mit Ja/ja/yes/j/y oder Nein/nein/no/n antworten"
    ) {
        when (it.getOrNull(0)?.toLowerCase()) {
            'y', 'j' -> true
            'n' -> false
            else -> null
        }
    }

    println("Hallo $name im Alter von $age.")
    println(if (doesSmoke) "Rauchen ist ungesund." else "Nichtraucher haben bessere Zähne.")
}
Ist auch keine ``while (true)``-Schleife mehr drin, das ist durch Endrekursion ersetzt. Closures wären auch möglich wenn man eine Prädikatsfunktion als benannte Funktion erstellen wollen müsste. :-)
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
gonzo123
User
Beiträge: 3
Registriert: Montag 24. September 2018, 10:57

Vielen Dank für die vielen Hinweise, Anregeungen und Lösungen.
Antworten