Frage bezüglich if-statement Verschachtelung (PEP8)

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.
Benutzeravatar
CrisBee
User
Beiträge: 61
Registriert: Mittwoch 2. Oktober 2013, 10:45
Wohnort: Bielefeld
Kontaktdaten:

Hi Leute,

möchte mich hier auch mal wieder melden. Ich habe hier folgenden Code geschrieben und würde ihn gerne mal (vorzugsweise von BlackJack) auseinanderfleddern lassen.

Code: Alles auswählen

#!/usr/bin/env python

from sys import argv

def add(num1, num2):
    ergebnis = int(num1)+int(num2)
    return ergebnis
    
def sub(num1, num2):
    ergebnis = int(num1)-int(num2)
    return ergebnis
    
def mul(num1, num2):
    ergebnis = int(num1)*int(num2)
    return ergebnis
    
def div(num1, num2):
    if int(num1)==0 or int(num2)==0:
        print "Error: Division throug 0!"
    else:
        ergebnis = int(num1)/int(num2)
        return ergebnis

if len(argv) > 3:
    scriptname, param1, param2, param3 = argv
       
    if param1.isalpha() and param2.isdigit() and param3.isdigit():
        if param1=="add":
            print add(param2, param3)
        elif param1=="sub":
            print sub(param2, param3)
        elif param1=="mul":
            print mul(param2, param3)
        elif param1=="div":
            print div(param2, param3)
        else:
            print "Choose either \'add\', \'sub\', \'mul\' or \'div\'"
            
    else:
        print "You can only use numbers, not letters."
        
else:
    print "You have to start the script like: scriptname operand number1 number2"
    print "Allowed operands are \'add\', \'sub\', \'mul\' or \'div\'"
Vor Allem die verschachtelten if-Anweisungen bereiten mir Bauchschmerzen, habe ich doch mal aufgeschnappt, dass man laut PEP8 nicht tiefer als zwei Ebenen verschachteln sollte?
Da es keine switch-case Anweisung gibt in Python suche ich nach einem guten Workaround. Auf meiner Suche habe ich deshalb schon von der Verwendung eines Dictionaries gelesen. Würde eure Meinung dazu gerne wissen.

Die int(num1)-Geschichte macht mich auch noch stutzig, wahrscheinlich kann ich die Werte direkt am Anfang in Integer umwandeln und muss es nicht bei jeder Verwendung machen.

Ansonsten würde ich mich über ein paar PEP8 Tipps freuen.

Kleine Anmerkung noch von mir:
- Die Division ist nicht ausgereift, sprich es werden keine "Reste" angezeigt.
- Es kann zwar ins negative gerechnet werden, aber nicht mit negativen Zahlen per Parameter.
Zuletzt geändert von CrisBee am Montag 28. Juli 2014, 14:46, insgesamt 1-mal geändert.
Das Reallife ist nur etwas für Leute, die keine Freunde im Internet haben! :P
Meine Fotografie: http://www.cutefeet.de
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ich spiele mal Rebell und kommentiere, auch wenn ich nicht BlackJack bin :-P

- Die einzelnen mathematischen Funktionen kann man auch kompakter schreiben:

Code: Alles auswählen

def add(num1, num2):
    ergebnis = int(num1)+int(num2)
    return ergebnis

# einfacher
def add(num1, num2):
    return num1 + num2
Du brauchst nichts an Namen binden, was Du nicht wieder verwendest - außer, der Name verdeutlicht etwas. Aber hier ist ja klar, was ``ergebnis`` sein soll.

- Laut PEP8 sollte man auch Leerzeichen zwischen Operatoren lassen ;-)

- Namen sollte man nicht durchnummerieren. Das wirkt immer wie eine Liste. In diesem Falle ist das sicherlich nicht so wichtig, aber irgend wie würde ich hier doch stumpf ``a`` und ``b`` wählen (mathematische Gesetze und Formeln greifen ja auch gerne darauf zurück)

- Das explizite Casten in Integer empfinde ich als überflüssige Einschränkung. Wieso sollte man nicht jeden Datentypen verrechnen können, der diese Operationen anbietet? Duck Typing eben :-)

- Du kannst (solltest!) Dir die Definitionen auch sparen und stattdessen diese einfach aus dem ``operator``-Modul importieren ;-)

- Die Bauchschmerzen hast Du quasi berechtigter Weise, denn diese Problematik verschwindet, wenn man das Dispatching über eine Datenstruktur wie ein Dictionary abbildet!

Code: Alles auswählen

# unsere Dispatching-Map:
operations = {"add": operator.add, "sub": operator.sub, ...}
# bessere Namen für die Parameter finden! Die Reihenfolge spielt doch für die Logik keine Rolle mehr!
# wie wäre es hiermit:
# command, a, b = param1, param2, param3
try:
    # hier die eigentliche "Magie":
    operations[command](a, b)
except KeyError:
    print "Choose either \'add\', \'sub\', \'mul\' or \'div\'"
- Anstelle des selbst gebastelten Kommandozeilen-Parsings nutze doch das ``argparse``-Modul! Damit bekommst Du die Hilfe automatisch geschenkt und insgesamt eine robustere Lösung.

- Schreibe nicht allen Code auf Modul-Ebene. Nutze folgendes Idiom (am besten in all Deinen Scripten, die ausführbar sind):

Code: Alles auswählen

def main():
    # der Einstiegspunkt für Deinen Code beginnt hier.

if __name__ == "__main__":
    main()
Ich hoffe das war dennoch hilfreich :mrgreen:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
CrisBee
User
Beiträge: 61
Registriert: Mittwoch 2. Oktober 2013, 10:45
Wohnort: Bielefeld
Kontaktdaten:

Hallo Hyperion,

ich wollte natürlich niemanden auf den Schlips treten. Auch deine Beiträge schätze ich sehr!!!

Vielen Dank für die schnelle und sehr ausführliche Antwort.

Die Operanden hatte ich ursprünglich mit Leerzeichen geschrieben, dachte aber gelesen zu haben, dass es laut PEP8 nicht erwünscht ist und es geändert, damit ihr stolz seid! :D

Ich werde deine Tipps beherzigen und versuche sie direkt umzusetzen, vielen Dank nochmal! :)
Das Reallife ist nur etwas für Leute, die keine Freunde im Internet haben! :P
Meine Fotografie: http://www.cutefeet.de
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Einige Dinge hast du ja schon selbst festgestellt, die hättest du auch gleich umsetzen können. Besonders die Umwandlung der Werte in Integer fällt hier auf. Da du das in jeder Funktion machst, in der Funktion zur Division sogar doppelt, sind die ganzen int-Aufrufe dort falsch und gehören nach außen. Zudem schränkst du dich damit auch unnötig ein, denn deine ganzen Funktionen können auch mit beliebigen anderen Zahlen oder Objekten arbeiten, welche die implementierten Operationen unterstützen. Wenn du alles in Integer presst, dann nimmst du dir diese Möglichkeit. Damit arbeitet du ein ein wenig gegen die Sprache, da das gerade ein Vorteil von so dynamischen Sprachen ist.

Das ganze hat aber noch einen Nachteil: Eine Funktion sollte genau eine Aufgabe haben. Eine add-Funktion sollte also einfach nur zwei Werte addieren. Zusätzlich wandelst du aber die Werte noch von Strings in Integer, bzw. von einem beliebigem Objekt in einen Integer. Diese Regel gilt ganz allgemein, unabhänig von der Sprache.

Dann fällt sofort auf, dass du die Ergebnisse der Berechnungen an "ergebnis" bindest und in der nächsten Zeile diesen Wert dann mittels return zurückgibst. Dass kannst du dir sparen und solltest besser gleich ``return num1 + num2`` schreiben. Stat num1 und num2 könntest du auch bessere Bezeichner wählen, dann ist deren Aufgabe gleich klar. Allerdings musst du die ganzen Operationen gar nicht selbst entwickeln, das operator-Modul besitzt diese bereits.

Da du auch Anmerkungen zu PEP 8 haben wolltest: Um binäre Operatoren sollten Leerzeichen geschrieben werden. Also ``a + b`` und nicht ``a+b``. Oder ``a == b`` statt ``a==b``.

Dann fällt natürlich gleich deine Division auf. Die ist nicht nur "unausgereift", sonder falsch. Der zähler darf durchaus Null sein, du verbietest das allerdings. Ansonsten gilt by Python das EAFP-Prinzip. Einfach probieren die Operation durchzuführen und dann schauen, ob ein fehler aufgetreten ist. Also: Einfach die Division durch Null versuchen und ggf. die Exception abfangen. Dividiere im Interpreter doch einfach mal durch 0, dann siehst du ja was passiert.

Auch hat eine div-Funktion ein weiteres Problem: Bei einer Division durch 0 versetzt du dein Programm in einen ungültigen Zustand. Einfach nichts zurückgeben im Fehlerfall ist nicht die feine Art. Wenn ein Fehler auftritt, dann wirf eine Exception, welche dann weiter oben behandelt werden kann. Auch bei deiner Divisions-Funktion gilt wieder, dass du nicht mehrere Aufgaben vermischen solltest. Du vermischt aber Division und die Fehlerausgabe. Stelle dir folgende Frage: In wie weit müsstest du deine Logik, also die Divisions-Funktion, ändern, wenn du eine GUI verwendest und keine Textausgabe? Die Antwort sollte sein: gar nicht. In deinem Fall sieht es aber noch nicht so aus. Du solltest dir daher sofort angewöhnen Logik und Ausgabe zu trennen.

Der ganze Code von 24 bis zum Ende sollte dort nicht stehen. Code, abgesehen von Konstanten, Funktionen und Klassen, hat auf modulebene nichts zu suchen. Damit handelst du dir nur probleme ein und kannst dein Modul nicht wiederverwenden. Packe den Code also in entsprechende Funktionen. Üblich ist eine main-Funktion, welche am Ende des Codes mittels

Code: Alles auswählen

if __name__ == "__main__":
    main()
aufgerufen wird. Dann ist auch sichergestellt, dass dein Programm nur ausgeführt wird, wenn es direkt aufgerufen wird.

Zeile 25 enthält gleich mehrere Problemchen. Dinge, welche du nicht an einen Namen bindest, solltest du einfach an den einfachen Unterstrich binden, damit drückst du eine Verwerfung des Werts aus:

Code: Alles auswählen

_, param1, param2, param3 = argv
Dann fällt sofort wieder die schlechte Namenswahl auf. param2 und param3 sind Operanden, param1 ist der Operator. Das solltest du also auch entsprechend benennen.

Zeile 27 ist wieder unnötig und teilweise falsch. Warum darf der Operator und aus Buchstaben bestehen? atan2 ist zum Beispiel ein sehr üblicher Bezeichner. Ansonsten ist auch der Test auf Ziffern mindestens unnötig. Wenn eine Eingabe nicht in einen Integer umgewandelt werden kann, dann fliegt dir schon eine Exception um die Ohren. Die solltest du behandeln. Wie gesagt: EAFP. Und natürlich schränkst du dich damit wieder weiter auf Integer ein, welches unnötig ist.

Die ganze if-Kaskade kannst du, wie du schon vermutet hast, als Dictionary darstellen:

Code: Alles auswählen

{"add":add, "sub":sub, ...}[operator](op1, op2)
Nicht vorhandenen Operationen kannst du wieder mittels Exception-Handling abfangen.

Die Backslashes in Zeile 37 sind überflüssig, genau so wie in Zeile 44.

Schau dir das argparse-Modul an.
Das Leben ist wie ein Tennisball.
Benutzeravatar
CrisBee
User
Beiträge: 61
Registriert: Mittwoch 2. Oktober 2013, 10:45
Wohnort: Bielefeld
Kontaktdaten:

Danke EyDu,

werde auch deine Hinweise berücksichtigen und versuchen umzusetzen. :)
Das Reallife ist nur etwas für Leute, die keine Freunde im Internet haben! :P
Meine Fotografie: http://www.cutefeet.de
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

Um doppelte Angaben der Operatornamen zu verhindern, hier nochmals der Teil, der die Funktionen aufruft:

Code: Alles auswählen

operations = {"add": operator.add, "sub": operator.sub, ...}
try:
    operation = operations[command]
except KeyError:
    names = sorted(operations)
    print "Choose either '%s' or '%s'" % ("', '".join(names[:-1]), names[-1])
else:
    print operation(a,b)
BlackJack

Ist fast nichts für mich übrig geblieben. :-) Mir sind noch die "… \'foo\' …" aufgefallen wo die \ unnötigig sind.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

BlackJack hat geschrieben:Ist fast nichts für mich übrig geblieben. :-) Mir sind noch die "… \'foo\' …" aufgefallen wo die \ unnötigig sind.
Keine Angst, die wurden nicht vergessen ;-)
EyDu hat geschrieben:Die Backslashes in Zeile 37 sind überflüssig, genau so wie in Zeile 44.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Haben wir für diese Standard-Aufgabe eigentlich irgend wo eine "Musterlösung", auf die man bei Bedarf verweisen könnte?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
BlackJack

@EyDu: Verdammt, habe ich übersehen. Ich schiebe das mal auf das furchtbare Wetter. :-)
Benutzeravatar
CrisBee
User
Beiträge: 61
Registriert: Mittwoch 2. Oktober 2013, 10:45
Wohnort: Bielefeld
Kontaktdaten:

Haha @BlackJack, wer nicht kommt zur rechten Zeit, der muss zerfleddern was übrig bleibt! :P

Sollte niemand eine adequate Abarbeitung dieser Standardaufgabe finden, dann kann ich sie ja - sofern ich sie vernünftig angepasst bekomme - bereitstellen! *g*
Das Reallife ist nur etwas für Leute, die keine Freunde im Internet haben! :P
Meine Fotografie: http://www.cutefeet.de
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hyperion hat geschrieben:Haben wir für diese Standard-Aufgabe eigentlich irgend wo eine "Musterlösung", auf die man bei Bedarf verweisen könnte?
Ich kann mich an keine erinnern, du hast also freie Bahn ;-) Sehr praktisch wäre aber auch ein Tool für Standardantworten auf Threads, à la:
  • Kein Code auf modulebene + Beispiel
  • Hinweis auf PEP 8
  • Code-Tags
  • Python nicht Phyton
  • etc.
Für die Hälfte der Antworten müsste man sich wohl nur schnell durch den Baum an möglichen Antworten wuseln.
BlackJack hat geschrieben:@EyDu: Verdammt, habe ich übersehen. Ich schiebe das mal auf das furchtbare Wetter. :-)
Auf dem Rennrad hatte ich gerade zwei Stunden feinstes Sonnenwetter. Ich vermute als Ursache den gestrigen Neumond :) Oder du wurdest gegen jemand anderen ausgetauscht...
Das Leben ist wie ein Tennisball.
Benutzeravatar
CrisBee
User
Beiträge: 61
Registriert: Mittwoch 2. Oktober 2013, 10:45
Wohnort: Bielefeld
Kontaktdaten:

EyDu hat geschrieben:Auf dem Rennrad hatte ich gerade zwei Stunden feinstes Sonnenwetter. Ich vermute als Ursache den gestrigen Neumond :) Oder du wurdest gegen jemand anderen ausgetauscht...
Ich denke BlackJack geht es da wie mir: Heißes Wetter = Schlechtes Wetter! :D

Deinen Vorschlag mit dem Tool klingt gut! :D
Das Reallife ist nur etwas für Leute, die keine Freunde im Internet haben! :P
Meine Fotografie: http://www.cutefeet.de
BlackJack

@EyDu: Feinstes Sonnenwetter klingt nach Oxymoron. Ich will'n Kellerbüro. Und/oder 'ne Klimaanlage. :-)
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ich bin doch nicht zum Spaß draußen in der Sonne und bekomme so ekelhaft braune Extremitäten (und hübsche Streifen an den Übergängen zur Kleidung :D). Das hat einen ernsten Hintergrund! Irgendwie muss der Keller doch mit dem eigenen Duft markiert werden, sonst fällt er noch einem anderen Informatiker in die Hand. Noch seltener als einmal im Jahr für einen stärkeren Geruch kann ich nicht duschen, das musste ich versprechen. Daher muss ich an einer anderen Schraube drehen :mrgreen:
Das Leben ist wie ein Tennisball.
Benutzeravatar
CrisBee
User
Beiträge: 61
Registriert: Mittwoch 2. Oktober 2013, 10:45
Wohnort: Bielefeld
Kontaktdaten:

Haha, so ein schöner "farmer's tan" @EyDu? Bekomme ich auch ständig. Nachträgliches, oberkörperfreies nachbräunen -> nutzlos! :D
Das Reallife ist nur etwas für Leute, die keine Freunde im Internet haben! :P
Meine Fotografie: http://www.cutefeet.de
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Das ist eine hart erarbeitete Profikante :mrgreen: Bei ca. 3 Ausfahrten/Woche mit 4 bis 5 Stunden länge ist Nachbräunen in der Tat hoffnunglos und zeitlich unmöglich. Wenn man ab und zu die Handschuhe weglässt, dann geht es zum Glück. Sonst hat es irgendwie etwas von einer Katze auf weißen Samtpfoten :D

Nun müssen wir aber aufpassen, sonst werden wir noch mit irgendeinem seltsamen Threadtitel ins Offtopic-Forum verschoben :D
Das Leben ist wie ein Tennisball.
Benutzeravatar
CrisBee
User
Beiträge: 61
Registriert: Mittwoch 2. Oktober 2013, 10:45
Wohnort: Bielefeld
Kontaktdaten:

:D

Okay, um den Thread mal wieder in die richtige Bahn zu lenken:

Habe mich jetzt erstmal intensiv mit argparse auseinandergesetzt. Nettes Modul!!! Habe ein schönes Tutorial auf Youtube gefunden. Bestimmt kennt ihr "TheUrbanPenguin" sogar...
Jetzt muss ich den ganzen Wulst nur auf ein Dictionary anwenden...^^

Ihr macht mich fertig! XD

Kleine Frage dazu am Rande...sollte ich das argparse in der def main() verwenden oder eine eigene Funktion daraus machen?
Das Reallife ist nur etwas für Leute, die keine Freunde im Internet haben! :P
Meine Fotografie: http://www.cutefeet.de
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ich mache meist eine eigene Funktion draus. Ordentlich formatiert werden bereits bei wenigen Parametern so einige Zeilen erreicht.

Das argparse-Modul hat in der Dokumentation übrigens schon ein eigenes Tutorial, die Beispiele in der Dokumentation sind auch recht umfangreich und verständlich. Damit solltest du schnell in die Materie eintauchen können, immerhin brauchst du nur die Basisfeatures.
Das Leben ist wie ein Tennisball.
Benutzeravatar
CrisBee
User
Beiträge: 61
Registriert: Mittwoch 2. Oktober 2013, 10:45
Wohnort: Bielefeld
Kontaktdaten:

Jaaaa, ich denke die Doc hatte ich als erstes auf dem Schirm...habe mich schwer damit getan. Ich glaube ich brauche noch ein Tutorial für die Docs! XD

Würde dann gerne eine eigene def für argparse machen. Bekomme ich wieder haue von euch, wenn ich sie getArgs() nenne? :D
Das Reallife ist nur etwas für Leute, die keine Freunde im Internet haben! :P
Meine Fotografie: http://www.cutefeet.de
Antworten