Anfänger: Maximalkraftrechner - Code so in Ordnung?

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
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Ich Grüße Euch

Ich bin noch relativ am Anfang bei Python.

Da man scheinbar wohl in Lehrbüchern auch einige seltsame Dinge findet, habe ich folgende Bitte:

Kann jemand mal kurz über den Code schauen und mir sagen, ob "Grobe" Schnitzer oder Sinnlosigkeiten drin sind.

Lerne die meiste Zeit mit "lernen durch Tun" und habe mich wie man in meinen anderen Beiträgen sehen kann schon mal ein wenig verrannt.

Danke im Voraus

Code: Alles auswählen

def data_input():
    print("Trainingsgewicht:")
    weight = float(input())
    print("Wiederholungen:")
    repetitions = int(input())
    return weight, repetitions


def strength_max_calculation(weight, repetitions):
    strength_max = weight / (1.0278 - (0.0278 * repetitions))
    return strength_max


def strenght_statistics_calculation(strength_max, strength_loop):
    strength_statistic = strength_max * (1.0278 - (0.0278 * strength_loop))
    return strength_statistic


def strength_data_output():
    print()
    print("Maximalkraft:")
    print()


def strength_statistic_output(strength_loop, strength_statistic):
    print("{0:>2}{1:<15}{2:>7.2f}".format(strength_loop, " Wiederholungen:", strength_statistic))


def calculation_method_output():
    print()
    print("Berechnet mit der 'Brzycki' Formel")
    print()


def main():
    weight, repetitions = data_input()
    strength_max = strength_max_calculation(weight, repetitions)
    strength_data_output()
    for strength_loop in (1, 5, 10, 12, 15):
        strength_statistic = strenght_statistics_calculation(strength_max, strength_loop)
        strength_statistic_output(strength_loop, strength_statistic)
    calculation_method_output()


if __name__ == '__main__':
    main()

Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
nezzcarth
User
Beiträge: 1633
Registriert: Samstag 16. April 2011, 12:47

Einige kleinere Anmerkungen, was man vielleicht schöner lösen könnte:

* 1.0278 und 0.0278 sind konkrete Werten, die an mehreren Stellen verwendet werden. Daher würde ich sie als Konstanten definieren, also als globale Namen in Großbuchstaben.

* Die drei *_output Funktion sind meiner
Meinung nach nicht notwendig, insb. die beiden fast gleichen strength_data_output und calculation_method_output, die man ansonsten wenigstens zu einer parametrisierbaren zusammenfassen könnte. Ich würde stattdessen einfach dort, wo jetzt der Funktionsaufruf geschieht print('\nMaximalkraft:\n') schreiben (und da print ja eh einen Zeilenvorschub erzeugt, das 2. \n auch nur, wenn es wirklich gewünscht ist). Bei strength_statistic_output könnte man vielleicht "Wiederholungen:" gleich in den String schreiben; format würde ich nur für veränderliche Werte verwenden.

* Auch die Eingabe data_input würde ich, so wie sie jetzt ist, nicht als Funktion definieren. Wenn du allerdings eine Typprüfung o.Ä. einbauen möchtest, dann schon eher. Die Texte der print-Aufrufe könnte man, wenn man möchte, auch gleich als Parameter an input übergeben.

* Persönlich bevorzuge ich es, wenn die Funktionen keine Nomen, sondern Verben sind, da eine Handlung ausgeführt wird. Also statt z.B. strength_max_calculation calculate_max_strength.
BlackJack

@Schwarzer Wolf: Grundsätzlich okay. Trotzdem ein paar Anmerkungen:

Die Funktionsnamen bezeichnen nicht so wirklich Tätigkeiten, beziehungsweise sind sie passiv formuliert, lassen sich damit also mit eher passiven Werten verwechseln.

Einen Wert an einen Namen zu binden der dann gleich in der nächsten Zeile nur bei einem ``return`` verwendet wird, kann man sich sparen.

Magische Zahlen, die zu dem noch mehr als einmal im Quelltext vorkommen, zieht man üblicherweise als Konstanten heraus. Wobei die Formel in den beiden Funktionen nahezu gleich ist, man könnte die Rechnung auch nur *einmal* hinschreiben und entweder aus den beiden Funktionen heraus aufrufen oder eine Funktion die andere Aufrufen lassen.

Unter einer „Statistik“ stelle ich mir üblicherweise mehr als einen Wert vor. Keine Ahnung ob das nur an mir liegt.

Aus dem Tupel über das die ``for``-Schleife geht, würde ich eine Liste machen. Das ist keine technische Sache sondern die Unterscheidung zwischen einer Liste als Sequenz von gleichwertigen Dingen (im Sinne von „duck typing“) und einem Tupel als Sequenz wo die Elemente an den unterschiedlichen Positionen unterschiedliche Bedeutungen haben.

Ansonsten ist es sehr kleinteilig auf Funktionen aufgeteilt, was an sich erst einmal nicht schlecht ist, aber um eine Zeile per `print()` auszugeben oder gar einfach nur eine kurze konstante Zeichenkette auszugeben würde ich keine Funktion schreiben.

Code: Alles auswählen

def ask_for_data():
    print('Trainingsgewicht:')
    weight = float(input())
    print('Wiederholungen:')
    repetitions = int(input())
    return weight, repetitions
 
 
def calculate_max_strength(weight, repetitions):
    return weight / (1.0278 - 0.0278 * repetitions)
 
 
def calculate_strength_statistic(max_strength, strength_loop):
    return calculate_max_strength(1 / max_strength, strength_loop)
 
 
def output_strength_data():
    print()
    print('Maximalkraft:')
    print()
 
 
def output_strength_statistic(strength_loop, strength_statistic):
    print(
        '{0:>2}{1:<15}{2:>7.2f}'.format(
            strength_loop, ' Wiederholungen:', strength_statistic
        )
    )
 
 
def output_calculation_method():
    print()
    print("Berechnet mit der 'Brzycki' Formel")
    print()
 
 
def main():
    weight, repetitions = ask_for_data()
    max_strength = calculate_max_strength(weight, repetitions)
    output_strength_data()
    for strength_loop in [1, 5, 10, 12, 15]:
        strength_statistic = calculate_strength_statistic(
            max_strength, strength_loop
        )
        output_strength_statistic(strength_loop, strength_statistic)
    output_calculation_method()
 
 
if __name__ == '__main__':
    main()
Benutzeravatar
snafu
User
Beiträge: 6738
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Für Anfängercode nicht komplett schlecht, finde ich. Trotzdem gibt's natürlich was zu meckern:

Funktionen, die im Grunde nur ein Einzeiler sind, kann man meistens auch direkt in den Code integrieren. Mit anderen Worten: Die verschiedenen Formeln hätte ich einfach direkt in main() geschrieben. Mit den ganzen Ausgabefunktionen kann man es genau so machen. Ausgaben mit Leerzeilen können dabei auch so erreicht werden:

Code: Alles auswählen

print("\nMaximalkraft:\n")
Einzig die data_input() wäre es demnach "wert", als Funktion geschrieben zu werden. Und auch die kann man noch etwas verbessern, wenn das Programm bei ungültigen Eingaben nicht komplett aussteigen soll, indem man mit einer Schleife solange fragt bis eine gültige Eingabe gegeben wurde.

Komplett runtergeschrieben sähe das Programm dann in etwa so aus:

Code: Alles auswählen

def get_user_data(data_type, prompt=''):
    while True:
        try:
            return data_type(input(prompt))
        except ValueError:
            print('Ungültige Eingabe!')

def main():
    weight = get_user_data(float, 'Trainingsgewicht: ')
    repetitions = get_user_data(int, 'Wiederholungen: ')
    max_strength = weight / (1.0278 - (0.0278 * repetitions))
    print('\nMaximalkraft:\n')
    for strength_loop in (1, 5, 10, 12, 15):
        strength_statistic = max_strength * (1.0278 - (0.0278 * strength_loop))
        print('{:>2} Wiederholungen: {:>7.2f}'.format(strength_loop, strength_statistic))
    print("\nBerechnet mit der 'Brzycki' Formel\n")

if __name__ == '__main__':
    main()
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Code: Alles auswählen

def ask_for_data():
    print('Trainingsgewicht:')
    weight = float(input())
    print('Wiederholungen:')
    repetitions = int(input())
    return weight, repetitions
Print und Input kann man auch direkt zusammenfassen:

Code: Alles auswählen

def ask_for_data():
    weight = float(input('Trainingsgewicht:'))
    repetitions = int(input('Wiederholungen:'))
    return weight, repetitions

Code: Alles auswählen

def output_strength_data():
    print()
    print('Maximalkraft:')
    print()
 
 
def output_calculation_method():
    print()
    print("Berechnet mit der 'Brzycki' Formel")
    print()
 
 
def main():
    output_strength_data()
    output_calculation_method()
Hier würde ich ebenfalls kürzen:

Code: Alles auswählen

def main():
    strength_data = '\nMaximalkraft:\n'
    calculation_method = "\nBerechnet mit der 'Brzycki' Formel\n"
    print(strength_data)
    print(calculation_method)
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

BlackJack hat geschrieben:Einen Wert an einen Namen zu binden der dann gleich in der nächsten Zeile nur bei einem ``return`` verwendet wird, kann man sich sparen.
Bei nicht trivialen Funktionen kann dies dennoch sinnvoll sein. Zum einen lässt sich durch die Wahl des Labelnamens noch einmal explizit angeben, was zurückgegeben wird, insofern dies nicht schon ausreichend dokumentiert wurde. Zum anderen eignet sich eine Zusatzzeile zum einfügen eines Breakpoints, was in Verbindung mit dem zusätzlichen Label beim debuggen hilfreich sein kann.
BlackJack

@kbr: Wenn man dem Rückgabewert direkt vor dem ``return`` einen Namen geben muss damit der Leser weiss um was es sich handelt, dann ist entweder der Funktionsname nicht gut genug oder die Funktion braucht dringend einen Docstring. Falls spätestens der nicht ausreicht, ist grundsätzlich etwas falsch mit der Funktion. IMHO.

Breakpoints lasse ich nicht gelten weil ich die nicht brauche und auch der Meinung bin, dass man etwas falsch macht wenn man Debugger braucht die im Einzelschritt durch den Code gehen. Der Wert wird doch dann gleich in der nächsten Zeile zurückgegeben, warum brauche ich denn einen Breakpoint an der Stelle, wenn ich doch einfach schauen kann was die Funktion zurück gibt?

Zu den Einzelschrittdebuggern: Es ist schon sehr lange her das ich so einen mal in Python benötigt habe und da auch nur um einen Fehler in Code zu finden der 1:1 aus einer anderen Sprache ”portiert” wurde und entsprechend „unpythonisch“ aussah. Konkret: Viel mit Laufvariablen und Indexgefrickel in tief verschachtelten Schleifen wo man durch die Indices über den Daumen gepeilt doppelt so viele Namen in der Funktion hat und die typischen „off by one“-Fehler suchen muss, die bei Indexgefrickel so beliebt sind. Python-Code schreibe ich wo es geht, und das geht fast überall, ohne Indirektion über Indices auf einer deutlich höheren Abstraktionsebene, und mit Testbarkeit der Funktionen im Hinterkopf. Damit geht die Nützlichkeit eines Einzelschrittdebuggers gegen Null, weil es a) schwierig wird überhaupt sinnvolle Ansatzpunkte für Breakpoints zu finden, und b) interaktives und automatisiertes Testen bei der Fehlersuche hilft/reicht und gegebenenfalls ein bisschen Logging mit dem `q`-Modul, womit man auch Werte mitten in Ausdrücken einfach ausgeben lassen kann ohne dafür einen Namen in einer eigenen Zeile zu brauchen.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@BlackJack: Sicher, wenn man dem Rückgabewert vor der Rückgabe einen Namen geben *muss*, dann stimmt was nicht. Wenn am Ende einer Funktion aber ein Rückgabewert zurückgegeben wird, der auch so heißt wie das, was zurückgegeben werden soll, dann ist das nicht grundsätzlich verkehrt. Ob und wann man dies einsetzt, lässt sich ja frei entscheiden.
Auf einen Debugger der reinen Lehre wegen zu verzichten halte ich für falsch; ich setze ihn bisweilen ein, zumeist dann, wenn es um eine Drittbibliothek geht und ich wissen will, was da wirklich passiert; das kommt zum Glück nur selten vor. Dabei geht es dann auch weniger um Einzelschritte, sondern mehr um Objektinspektion.
Bei Anwendungen die über kleine Skripts hinausgehen schreibe ich zumeist erst einen rudimentären Test, dann die Funktion um anschließend beides zu erweitern. Das ist zwar Anfangs etwas aufwendiger, zahlt sich später aber immer aus und gibt einem bei Änderungen und Refactorings auch ein gutes Gefühl.
BlackJack

@kbr: Ich verzichte nicht auf den traditionellen Debugger wegen einer reinen Lehre, sondern weil man den wie gesagt in Python a) weniger braucht und b) bei einer Sprache auf dem Abstraktionsniveau von Python weniger Punkte bestehen, wo man den Debugger ansetzen kann. Wo hast Du da reine Lehre rausgelesen? Mir würden da jetzt nur Indizes und tief verschachtelte Schleifen einfallen, das würde ich aber nicht als reine Lehre sehen auf so etwas zu verzichten, sondern ganz einfach als ordentliches, normales, also idiomatisches Programmieren mit den Sprachmitteln die einem zur Verfügung stehen.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

BlackJack hat geschrieben:Breakpoints lasse ich nicht gelten weil ich die nicht brauche und auch der Meinung bin, dass man etwas falsch macht wenn man Debugger braucht die im Einzelschritt durch den Code gehen.
@BlackJack: Dann habe ich obiges wohl überinterpretiert. Sorry.
Gruß kbr
Antworten