Notes 6

Sie können diese Seite ausdrucken oder mit einem PDF-Drucker in ein PDF umwandeln, um Ihre eigenen Notizen hinzuzufügen.

Willkommen!

  • In den vergangenen Wochen haben Sie grundlegende Bausteine der Programmierung kennengelernt.
  • Sie lernten die Low-Level-Programmiersprache C kennen.
  • Heute werden wir mit einer höheren Programmiersprache namens Python arbeiten.
  • Während Sie diese neue Sprache lernen, werden Sie feststellen, dass Sie besser in der Lage sind, sich selbst neue Programmiersprachen beizubringen.

Python

  • Die Menschen haben im Laufe der Jahrzehnte erkannt, wie frühere Designentscheidungen verbessert werden können.
  • Python ist eine Programmiersprache, die auf dem aufbaut, was Sie bereits in C gelernt haben.
  • Anders als C ist Python eine interpretierte Sprache, bei der Sie Ihr Programm nicht separat kompilieren müssen. Stattdessen führen Sie Ihr Programm im Python Interpreter aus.

Hallo

  • Bis zu diesem Punkt sah der Code in etwa wie folgt aus:

    // Ein Programm, das der Welt "Hallo" sagt
    
    #include <stdio.h>
    
    int main(void)
    {
        printf("Hallo, Welt");
    }
  • Heute ist das Programmieren einfacher.

  • Zum Beispiel wird der obige Code in Python wie folgt dargestellt:

    # Ein Programm, das der Welt Hallo sagt
    
    print("Hallo, Welt")

    Beachten Sie, dass das Semikolon wegfällt und dass keine Bibliothek benötigt wird.

  • Python kann insbesondere das, was in C recht kompliziert war, relativ einfach umsetzen.

Filter

  • Um diese Einfachheit zu verdeutlichen, erstellen Sie eine neue Datei, indem Sie “code blur.py” in Ihr Terminalfenster eingeben, und schreiben Sie den folgenden Code:

    # Verwischt ein Bild
    
    from PIL import Image, ImageFilter
    
    # Bild unscharf machen
    before = Image.open("bridge.bmp")
    after = before.filter(ImageFilter.BoxBlur(10))
    after.save("out.bmp")

    Beachten Sie, dass dieses Programm die Module Image und ImageFilter aus einer Bibliothek namens PIL importiert. Es nimmt eine Eingabedatei und erstellt eine Ausgabedatei.

  • Mit Python können Sie bei der Programmierung noch mehr von den Details abstrahieren als in C und anderen niedrigeren Programmiersprachen.

CS50-Bibliothek

  • Wie in C kann auch in Python eine cs50-Bibliothek verwendet werden, um den Einstieg zu erleichtern.

  • Die folgenden Funktionen sollten Ihnen bekannt vorkommen:

      get_float
      get_int
      get_string
  • Anders als in C können Sie in Python nur die tatsächlich benötigten Funktionen aus der CS50-Bibliothek importieren:

    from cs50 import get_float, get_int, get_string

Strings

  • In C erinnern Sie sich vielleicht an diesen Code:

    // get_string und printf mit %s
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        string answer = get_string("Wie ist Ihr Name?");
        printf("Hallo, %s\n", answer);
    }
  • Dieser Code sieht in Python so aus:

    # get_string und print, mit Format-Strings (f-Strings)
    
    from cs50 import get_string
    
    answer = get_string("Wie ist Ihr Name?")
    print(f"hallo, {answer}")

    Beachten Sie, wie die geschweiften Klammern es der Funktion print ermöglichen, dass answer darin erscheint. Das f ist erforderlich, um answer im String richtig zu formatieren (sog. f-String). Das funktioniert nicht nur in print, sondern überall, wo Strings verwendet werden.

  • Ohne CS50-Stützräder:

    answer = input("Wie ist Ihr Name? ")
    print(f"hallo, {answer}")

Variablen

  • Auch die Variablendeklaration wird vereinfacht. In C könnten Sie int counter = 0; angeben. In Python würde dieselbe Zeile lauten: counter = 0. Sie müssen den Typ der Variablen nicht angeben, weil Python eine dynamisch typisierte Sprache ist (C ist statisch typisiert).
  • Python nutzt counter += 1, um um eins zu erhöhen. Die aus C bekannte Möglichkeit mit counter++ gibt es in Python nicht.

Typen

  • Datentypen müssen in Python nicht explizit deklariert werden. Sie haben zum Beispiel oben gesehen, dass Antwort eine Zeichenkette ist, aber wir mussten dem Interpreter nicht sagen, dass dies der Fall ist: Er wusste es von selbst; den Datentyp hat er sich selbst erschlossen.

  • In Python werden häufig folgende Typen verwendet:

      bool
      float
      int
      str

    Beachten Sie, dass long und double fehlen. Python nutzt für größere Zahlen automatisch Repräsentationen mit ausreichender Genauigkeit.

  • Sie können das selbst ausprobieren, indem Sie den Python-Interpreter in einem Terminal durch Eingabe von python (oder python3) im interaktiven Modus starten und folgendes eingeben: 10**100 – das Ergebnis dieser Potenzierung von 10 mit der Zahl 100 ist eine Zahl mit 101 Stellen – und wäre in C nicht ohne weiteres in einer Variablen speicherbar.

  • Einige andere Datentypen in Python sind:

      range (Sequenz von Zahlen)
      list (Listen, deren Größe automatisch angepasst wird)
      tuple (Tupel, also nicht veränderbare Listen)
      dict (Dictionaries mit Key-Value-Paaren)
      set (Mengen ohne Duplikate)
  • Jeder dieser Datentypen kann auch in C implementiert werden, aber in Python können sie einfacher implementiert werden.

Rechner

  • Vielleicht erinnern Sie sich an calculator.c von früher im Kurs:

    // Addition mit int
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Aufforderung an den Benutzer zur Eingabe von x
        int x = get_int("x: ");
    
        // Aufforderung an den Benutzer zur Eingabe von y
        int y = get_int("y: ");
    
        // Addition durchführen
        printf("%i\n", x + y);
    }
  • Wir können einen einfachen Taschenrechner genauso implementieren, wie wir es in C getan haben. Geben Sie code calculator.py in das Terminalfenster ein und schreiben Sie folgenden Code:

    # Addition mit int [mittels get_int]
    
    from cs50 import get_int
    
    # Benutzer zur Eingabe von x auffordern
    x = get_int("x: ")
    
    # Benutzer zur Eingabe von y auffordern
    y = get_int("y: ")
    
    # Addition durchführen
    print(x + y)

    Beachten Sie, wie die CS50-Bibliothek importiert wird. Dann werden “x” und “y” vom Benutzer abgefragt. Schließlich wird das Ergebnis ausgegeben. Beachten Sie, dass die Funktion “main”, die in einem C-Programm zu sehen wäre, völlig fehlt! Obwohl man eine “main”-Funktion verwenden könnte, ist sie nicht erforderlich.

  • Wir versuchen auch hier wieder, die Stützräder der CS50-Bibliothek zu entfernen. Ändern Sie Ihren Code wie folgt:

    # Addition mit int [mit input]
    
    # Benutzer zur Eingabe von x auffordern
    x = input("x: ")
    
    # Benutzer zur Eingabe von y auffordern
    y = input("y: ")
    
    # Addition durchführen
    print(x + y)

    Beachten Sie, dass die Ausführung des obigen Codes zu einem sehr seltsamen Programmverhalten führt. Woran mag das liegen?

  • Wenn Sie an dieser Stelle vermutet haben, dass der Python-Interpreter x und y als Strings betrachtet und daher die Konkatenation von x und y ausgibt, dann liegen Sie damit richtig. Sie können den Code korrigieren, indem Sie die Funktion int zur Typumwandlung wie folgt verwenden:

    # Addition mit int [mit Eingabe]
    
    # Benutzer zur Eingabe von x auffordern
    x = int(input("x: "))
    
    # Benutzer zur Eingabe von y auffordern
    y = int(input("y: "))
    
    # Addition durchführen
    print(x + y)

    Beachten Sie, wie die Eingabe für x und y an die Funktion int übergeben wird, die sie in eine Ganzzahl umwandelt. Ohne die Umwandlung von “x” und “y” in Ganzzahlen werden die Zeichen aneinandergereiht (konkateniert).

Bedingte Anweisungen

  • Aus unseren C-Vorlesungen kommt Ihnen vielleicht ein Programm wie dieses vertraut vor

    // Bedingte Anweisungen, boolesche Ausdrücke, relationale Operatoren
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Aufforderung zur Eingabe von Ganzzahlen
        int x = get_int("Wie groß ist x? ");
        int y = get_int("Wie lautet y? ");
    
        // Ganzzahlen vergleichen
        if (x < y)
        {
            printf("x ist kleiner als y\n");
        }
        else if (x > y)
        {
            printf("x ist größer als y\n");
        }
        else
        {
            printf("x ist gleich y\n");
        }
    }
  • In Python würde das wie folgt aussehen:

    # Bedingte Anweisungen, boolesche Ausdrücke, relationale Operatoren
    
    from cs50 import get_int
    
    # Benutzer zur Eingabe von Ganzzahlen auffordern
    x = get_int("Wie groß ist x? ")
    y = get_int("Wie lautet y? ")
    
    # Ganzzahlen vergleichen
    if x < y:
        print("x ist kleiner als y")
    elif x > y:
        print("x ist größer als y")
    else:
        print("x ist gleich y")

    Beachten Sie, dass es, erstens, keine geschweiften Klammern mehr gibt. Stattdessen werden Einrückungen verwendet. Zweitens wird ein Doppelpunkt in der “if”-Anweisung verwendet. Außerdem wird else if durch elif ersetzt. Auch Klammern sind in den Anweisungen if und elif nicht mehr erforderlich.

  • Anders als in C lassen sich Strings genauso einfach vergleichen:

    # Benutzer zur Eingabe von Strings auffordern
    s = input("s? ")
    t = input("t? ")
    
    if s == t:
        print("same")
    else:
        print("different")
  • Betrachten Sie den bereits bekannten C-Code, um weitere Vergleiche anzustellen:

    // Logische Operatoren
    
    #include <cs50.h>
    #include <stdio.h>
    
    int main(void)
    {
        // Aufforderung an den Benutzer, zuzustimmen
        char c = get_char("Sind Sie einverstanden?");
    
        // Prüfen, ob einverstanden
        if (c == 'Y' || c == 'y')
        {
            printf("Einverstanden.\n");
        }
        else if (c == 'N' || c == 'n')
        {
            printf("Nicht einverstanden.\n");
        }
    }
  • Dies kann in Python wie folgt umgesetzt werden:

    # Logische Operatoren
    
    from cs50 import get_string
    
    # Aufforderung an den Benutzer zuzustimmen
    s = get_string("Sind Sie einverstanden?")
    
    # Prüfen, ob einverstanden
    if s == "Y" or s == "y":
        print("Einverstanden.")
    elif s == "N" or s == "n":
        print("Nicht einverstanden.")

    Beachten Sie, dass die beiden Senkrechtstriche, die in C verwendet werden, durch “or” ersetzt werden. In der Tat bevorzugen viele Menschen Python auch deswegen, weil es für Menschen besser lesbar ist. Beachten Sie auch, dass es in Python kein char gibt. Stattdessen wird auch für einzelne Zeichen der Datentyp “str” verwendet; die in C erforderliche Unterscheidung (einfache oder doppelte Anführungszeichen) gibt es hier nicht.

  • Eine “pythonischere” (more pythonic) Herangehensweise an das Problem würde wie folgt aussehen. Hier wird der Datentyp Liste verwendet (Notation in eckigen Klammern):

    # Logische Operatoren, die Listen verwenden
    
    from cs50 import get_string
    
    # Aufforderung an den Benutzer zuzustimmen
    s = get_string("Sind Sie einverstanden?")
    
    # Prüfen, ob einverstanden
    if s in ["y", "yes"]:
        print("Einverstanden.")
    elif s in ["n", "nein"]:
        print("Nicht einverstanden.")

    Beachten Sie, dass wir mehrere Schlüsselwörter wie “y” und “ja” in einer “Liste” ausdrücken können.

  • Etwas unelegant ist, dass wir für alle Varianten von Groß- und Kleinschreibung nun entsprechende Elemente in den Listen benötigen. In C würden wir die Zeichen in s mit tolower(…) aus der Bibliothek ctype.h in Kleinbuchstaben umwandeln (Zeichen für Zeichen in einer Schleife).

Objektorientierte Programmierung

  • Unter Designgesichtspunkten ist das Anbieten einer Funktion wie tolower(…) nicht besonders elegant. Nach dem Include steht sie im Programm einfach so zur Verfügung wie jede andere Funktion. Bei vielen Includes wird es schnell unübersichtlich und es könnte sogar zu Namenskonflikten kommen, wenn z.B. mehrere Bibliotheken gleichzeitig included werden, die die gleichen Funktionen anbieten. Bisher haben wir das so hingenommen.

  • Wäre es nicht schöner, wenn die Funktionen, die bestimmte Daten verarbeiten, näher bei den Daten definiert wären, auf die sie anwendbar sind?

  • In der Realität sind wir es gewohnt, dass Objekte zum einen Eigenschaften mit sich bringen und zum anderen bestimmtes Verhalten haben. Ein Auto hat vier Räder und eine bestimmte Farbe. Es kann beschleunigen und bremsen.

  • Es bietet sich an, dieses Konzept auch bei der Programmierung zu verwenden und dadurch den Code besser zu organisieren.

  • Das ist die Idee der objektorientierten Programmierung, in der wir nicht mit unstrukturierten Daten arbeiten, sondern mit Objekten.

  • Mit C können wir das nicht umsetzen. Wir können lediglich eine struct erstellen, um mehrere Variablen innerhalb eines einzigen, selbst erstellten Datentyps miteinander zu verbinden. In Python können wir hingegen objektorientiert programmieren – und haben das von Anfang an getan, weil in Python alles ein Objekt ist!

  • Beim objektorientierten Programmieren können wir nicht nur mehrere Werte zu einem Objekt zusammenfassen, sondern auch Funktionen auf den Objekten aufrufen, die zu Objekten von einem bestimmten Typ (einer Klasse) gehören. Solche Funktionen nennen wir Methoden. Methoden werden durch den Punktoperator auf ein Objekt angewendet.

  • Zum Beispiel haben str-Objekte in Python eine eingebaute Methode, mit denen wir alle Großbuchstaben in Kleinbuchstaben umwandeln können Daher könnten Sie Ihren Code wie folgt ändern:

    # Objektorientiertes Programmieren
    
    from cs50 import get_string
    
    # Aufforderung an den Benutzer zuzustimmen
    s = get_string("Sind Sie einverstanden?").lower()
    
    # Prüfen, ob zugestimmt wurde
    if s.lower() in ["y", "yes"]:
        print("Einverstanden.")
    elif s.lower() in ["n", "nein"]:
        print("Nicht einverstanden.")

    Man beachte, wie dadurch anstelle des Werts von s das Ergebnis von s.lower(), einer eingebauten Methode von str, verwendet wird.

  • Mehr über String-Methoden erfahren Sie in der Python-Dokumentation

  • Bitte schauen Sie sich die Short-Videos zum Objektorientierten Programmieren an. Dort zeigen wir nicht nur, wie man Objekte nutzt, die es schon gibt, sondern auch wie man selbst Klassen erstellt und Probleme objektorientiert modelliert.

Schleifen

  • while-Schleifen in Python funktionieren auf den ersten Blick recht ähnlich wie Schleifen in C. Sie erinnern sich vielleicht an den folgenden Code in C:

    // Demonstriert die while-Schleife
    
    #include <stdio.h>
    
    int main(void)
    {
        int i = 0;
        while (i < 3)
        {
            printf("miau\n");
            i++;
        }
    }
  • In Python sieht dieser Code wie folgt aus:

    # Demonstriert die while-Schleife
    
    i = 0
    while i < 3:
        print("miau")
        i += 1
  • Die for-Schleifen wird in Python hingegen anders implementiert:

    # Besseres Design
    
    for i in range(3):
        print("miau")

    Beachten Sie, dass i gar nicht benötigt wird. Python erhöht aber natürlich trotzdem in jedem Durchlauf (unnötigerweise) den Wert von i. Wenn man ausdrücken will, dass man den Wert gar nicht benötigt, kann man einen Unterstrich _ statt des i schreiben. Das macht den Code besser lesbar, weil es nicht zu Missverständnissen kommen kann (andernfalls könnte man sich irgendwann darüber wundern, dass i nirgends verwendet wird)

  • In ähnlicher Weise könnte man den obigen Code wie folgt ausdrücken:

    # Abstraktion mit Parametrisierung
    
    # Miau eine bestimmte Anzahl von Malen
    def miau(n):
        for _ in range(n):
            print("miau")
    
    
    miau(3)

    Beachten Sie, dass nun eine Funktion verwendet wird, um das Miauen auszulagern.

  • Um unser Verständnis von Schleifen und Iteration in Python zu vertiefen, erstellen wir eine neue Datei mit dem Namen uppercase.py, die sich an C-Code anlehnt, den wir früher schon einmal geschrieben haben:

    # Zeichenkette mit Großbuchstaben, ein Zeichen nach dem anderen
    
    before = input("Vorher: ")
    print("Nachher: ", end="")
    for c in before:
        print(c.upper(), end="")
    print()

    Beachten Sie, wie der benannte Parameter end= verwendet wird, um einen Parameter an die Funktion print zu übergeben. Wir brauchen hier einen benannten Parameter, weil print(…) für mehr Komfort so konstruiert wurde, dass es alle Positions-basierten Parameter mit Leerzeichen getrennt ausgibt. Dadurch gibt es keinen natürlichen Ort, wo wir einen Parameter zur Veränderung des Verhaltens platzieren können – und eine separate Funktion print_without_newline wäre nicht besonders elegant. Mit benannten Parametern können wir einer Methode unabhängig von der Position Parameter übergeben, auf die in der Methode zugegriffen werden kann. Bei print können wir dadurch das Standardverhalten verändern. Es gibt dann keinen Zeilenumbruch aus.

  • Bei der Lektüre der Dokumentation stellen wir fest, dass die upper()-Methode von str immer die gesamte Zeichenkette verarbeitet und wir daher weniger Code benötigen:

    # Zeichenkette auf einmal großschreiben
    
    before = input("Vorher: ")
    after = before.upper()
    print(f"Nachher: {after}")

    Beachten Sie, dass “upper” auf die gesamte Zeichenkette angewendet wird.

  • Oder noch kürzer

    # Zeichenkette auf einmal großschreiben
    
    before = input("Vorher: ")
    print(f"Nachher: {before.upper()}")

Abstraktion mit Funktionen

  • Wie wir heute schon angedeutet haben, können wir unseren Code verbessern, indem wir Funktionen verwenden und die Implementierungsdetails in Funktionen abstrahieren. Ändern Sie Ihren zuvor erstellten miau.py Code wie folgt:

    # Abstraktion
    
    def main():
        for i in range(3):
            miau()
    
    # Einmal miauen
    def miau():
        print("miau")
    
    
    main()

    Beachten Sie, dass die Funktion miau die Anweisung print abstrahiert. Beachten Sie auch, dass die Funktion main am Anfang der Datei definiert ist und am Ende der Datei aufgerufen wird. Dies ist eine gängiges Programmiermuster in Python. Funktionen können erst aufgerufen werden nachdem sie definiert wurden. Weil es das Konzept der Funktionsprototypen nicht gibt, rufen wir einfach main() ganz am Ende der Datei auf – zu diesem Zeitpunkt hat der Python-Interpreter die ganze Datei eingelesen und kennt alle Funktionen, die main() kann.

  • Wie aus C bekannt können wir Variablen zwischen Funktionen übergeben:

    # Abstraktion mit Parametrisierung
    
    def main():
        miau(3)
    
    
    # Miau eine bestimmte Anzahl von Malen
    def miau(n):
        for i in range(n):
            print("miau")
    
    
    main()

    Beachten Sie, dass miau jetzt eine Variable n benötigt. In der Funktion main können Sie miau aufrufen und ihm einen Wert wie 3 übergeben. Dann verwendet “miau” den Wert von “n” in der “for”-Schleife.

  • Wenn Sie den obigen Code lesen, werden Sie feststellen, dass Sie als C-Programmierer in der Lage sind, den obigen Code ganz leicht zu verstehen. Zwar sind einige Konventionen anders, aber die Bausteine, die Sie zuvor gelernt haben, sind für Sie in dieser neuen Programmiersprache nun schneller nachvollziehbar.

Keine Trunkierung bei Division und Fließkomma-Ungenauigkeit

  • Erinnern Sie sich, dass wir in C die Erfahrung gemacht haben, dass die Division einer ganzen Zahl durch eine andere zu einem ungenauen, trunkierten Ergebnis führen kann.

  • Schauen wir uns an, wie Python eine solche Division handhabt, indem wir den Code für calculator.py ändern:

    # Division mit ganzen Zahlen, Nachweis der fehlenden Trunkierung
    
    x = int(input("x: "))
    
    y = int(input("y: "))
    
    z = x / y
    print(z)

    Beachten Sie, dass automatisch ein Fließkomma-Wert ausgegeben wird – in C wäre das Ergebnis der Division hingegen auf die nächste Ganzzahl abgerundet (trunkiert) worden. Wenn Sie sich mehr Ziffern nach .333333 ausgeben lassen würden, würden Sie sehen, dass wir auch in Python mit Gleitkomma-Ungenauigkeit leben müssen.

  • Wir können diese Ungenauigkeit aufdecken, indem wir unsere Codes leicht abändern:

    # Fließkomma-Ungenauigkeit
    
    # Benutzer zur Eingabe von x auffordern
    x = int(input("x: "))
    
    # Benutzer zur Eingabe von y auffordern
    y = int(input("y: "))
    
    # x durch y dividieren
    z = x / y
    print(f"{z:.50f}")

    Hier sehen Sie ein Beispiel für eine weiterführende Syntax von Format-Strings, die so ähnlich funktioniert wie bei printf in C. Wir geben die Zahl mit 50 Nachkommastellen aus. Dadurch sehen wir die Ungenauigkeit von Fließkommazahlen.

Exceptions

  • Sehen wir uns noch einmal folgenden Code an:

    from cs50 import get_int
    
    def main():
    
        # Benutzer zur Eingabe von x auffordern
        x = get_int("x: ")
    
        # Benutzer zur Eingabe von y auffordern
        y = get_int("y: ")
    
        # Addition durchführen
        print(x + y)
    
    
    main()

    Wenn Sie bei diesem Programm statt einer Zahl einen String eingeben (z.B. cat), dann werden Sie erneut aufgefordert eine Zahl einzugeben. Die CS50-Bibliothek kümmert sich intern um die Fehlerbehandlung. Wie macht sie das?

  • Das beobachtete Verhalten ist nicht das Standardverhalten von Python. Das sehen wir, wenn wir versuchen, get_int nachzubauen.

  • Ändern Sie calculator.py wie folgt:

    # Implementiert get_int
    
    def get_int(prompt):
        return int(input(prompt))
    
    
    def main():
    
        # Benutzer zur Eingabe von x auffordern
        x = get_int("x: ")
    
        # Benutzer zur Eingabe von y auffordern
        y = get_int("y: ")
    
        # Addition durchführen
        print(x + y)
    
    
    main()

    Beachten Sie, dass die Eingabe von falschen Daten zu einem Fehler führt (ValueError) und das Programm abgebrochen wird.

  • In Python und anderen modernen Programmiersprachen gibt es ein leistungsfähiges Konstrukt zur Fehlerbehandlung, sogenannte Exceptions.

  • Die Idee ist folgende: Anstatt für alle möglichen Fehlerfälle explizit mit bedingten Anweisungen Prüfschritte einzubauen, „versuchen“ (engl. to try), wir den Code auszuführen und behandeln (man sagt: “fangen”, engl. to catch) dabei möglicherweise auftretende Ausnahmezustände. Dazu ändern wir unseren Code wie wie folgt:

    # Implementiert get_int mit einer Schleife
    
    def get_int(prompt):
        while True:
            try:
                return int(input(prompt))
            except ValueError:
                print("Not an integer")
    
    
    def main():
    
        # Benutzer zur Eingabe von x auffordern
        x = get_int("x: ")
    
        # Benutzer zur Eingabe von y auffordern
        y = get_int("y: ")
    
        # Addition durchführen
        print(x + y)
    
    
    main()

    Beachten Sie, dass der obige Code wiederholt versucht, den richtigen Datentyp abzurufen, und bei Bedarf zusätzliche Eingabeaufforderungen liefert. So ähnlich arbeitet auch get_int im cs50-Modul.

Listen

  • list ist eine sehr häufig genutzte Datenstruktur in Python.

  • Für die Verarbeitung von Listen gibt es in Python einige nützliche Funktionen – und Listen haben zusätzlich viele nützliche Methoden.

  • Betrachten Sie zum Beispiel den folgenden Code:

    # Durchschnitt von drei Zahlen in einer Liste
    
    # Punkte
    scores = [72, 73, 33]
    
    # Durchschnitt ausgeben
    average = sum(scores) / len(scores)
    print(f"Durchschnitt: {average}")

    Beachten Sie, dass Sie die eingebaute Funktion sum verwenden können, um den Durchschnitt zu berechnen.

  • Man könnte sich fragen, wieso man sum(scores) schreibt und nicht scores.sum() – gleichermaßen bei len(…). Das liegt unter anderem daran, dass Listen nicht die einzigen Objekte sind, für die man eine Summe berechnen oder die Anzahl ermitteln können will. Diese Operationen könnten auch bei Tupeln oder Mengen nützlich sein. Tatsächlich sind sie bei allen Objekten sinnvoll, die man zählen kann bzw. die mehrere aufsummierbare Werte haben. Anstatt solche Funktionen in allen Klassen, in denen es Sinn macht, neu zu definieren, gibt es sie daher in Python global. Klassen, die sich summieren oder zählen lassen wollen, müssen diese Eigenschaft dann durch vordefinierte Methoden (sog. Dunder-Methods von „Double-Underscore“) anbieten. Mehr dazu erfahren Sie im Short-Video zum Objektorientierten Programmieren und zu Vererbung und Duck Typing.

  • Sie können die folgende Syntax verwenden, um Werte vom Benutzer zu erhalten:

    # Durchschnitt von drei Zahlen mit Hilfe einer Liste und einer Schleife
    
    from cs50 import get_int
    
    # Spielstände abrufen
    scores = []
    for i in range(3):
        score = get_int("Score: ")
        scores.append(score) # Alternative: scores + [score]
    
    # Durchschnitt ausgeben
    average = sum(scores) / len(scores)
    print(f"Durchschnitt: {average}")

    Beachten Sie, dass dieser Code die eingebaute Methode append für Listen verwendet.

  • Sie können mehr über Listen in der Python-Dokumentation erfahren.

  • Sie können auch mehr über len in der Python-Dokumentation erfahren.

  • Schauen Sie sich das Short-Video zu Collections und zu Vererbung und Duck Typing an. Dadurch werden Sie besser verstehen, wie Listen funktionieren und wie man sie in Python geschickt verwendet, um Probleme zu lösen.

Suche und Wörterbücher

  • Wir haben uns in C auch angesehen, wie man in Datenstrukturen nach einem bestimmten Wert sucht.

  • Betrachten wir ein Programm namens “phonebook.py”:

    # Implementiert lineare Suche nach Namen mittels Schleife
    
    # Eine Liste von Namen
    names = ["Carter", "David", "John"]
    
    # Nach dem Namen fragen
    name = input("Name: ")
    
    # Suche nach Namen
    for n in names:
        if name == n:
            print("Gefunden")
            break
    else:
        print("Nicht gefunden")

    Beachten Sie, dass dies eine lineare Suche nach dem Namen implementiert. Das else bei einer for-Schleife ist eine Python-Besonderheit, die wie folgt funktioniert. Wird die Schleife mit break abgebrochen, wird der else-Zweig nicht ausgeführt. Wird die Schleife erfolglos bis zum Ende durchlaufen (es wurde kein break aufgerufen), wird der else-Zweig ausgeführt. Praktisch, oder?

  • Die obige Vorgehensweise ist nah am C-Code. Wir können uns langsam angewöhnen, mit Listen zu arbeiten – und dadurch Python die mühsamen Arbeiten für uns erledigen lassen. Wir müssen nämlich nicht von Hand über eine Liste iterieren. In Python können wir die gleiche lineare Suche stattdessen wie folgt erreichen:

    # Implementiert die lineare Suche nach Namen mit "in".
    
    # Eine Liste von Namen
    names = ["Carter", "David", "John"]
    
    # Nach dem Namen fragen
    name = input("Name: ")
    
    # Suche nach Name
    if name in names:
        print("Gefunden")
    else:
        print("Nicht gefunden")

    Beachten Sie, wie “in” zur Umsetzung der linearen Suche verwendet wird.

  • Dieser Code ist zwar elegant, aber nicht effizient.

  • Sie erinnern sich vielleicht noch daran, dass ein Wörterbuch (Pythons Datentyp dict, der mit geschweiften Klammern definiert wird) eine Sammlung von Schlüssel- und _Wert-_Paaren ist und dass Wörterbücher mit Hash-Tabellen sehr effizient implementiert werden können (konstante Laufzeit für die Suche bei Wahl einer geeigneten Hash-Funktion).

  • Ein Telefonbuch können Sie in Python wie folgt implementieren (in C hatten wir es so ähnlich mit einer struct umgesetzt):

    # Implementiert ein Telefonbuch als eine Liste von Wörterbüchern
    
    from cs50 import get_string
    
    # Eine Liste, die aus drei Dictionaries besteht
    people = [
        {"name": "Carter", "num": "+1-617-495-1000"},
        {"name": "David",  "num": "+1-617-495-1000"},
        {"name": "John",   "num": "+1-949-468-2750"},
    ]
    
    # Suche nach Name
    name = get_string("Name: ")
    # je Durchlauf bekommen wir in "person" das aktuelle dict
    for person in people:
        if person["name"] == name:
            print(f"Gefunden {person['num']}")
            break
    else:
        print("Nicht gefunden")

    Beachten Sie, dass das Telefonbuch als Liste aus Wörterbüchern implementiert ist, wobei jedes Wörterbuch zwei Key-Value-Paar hat: je eines für “name” und eines für “num”.

  • Wenn es uns um einen effizienten Zugriff geht, ist dieser Code allerdings nicht besonders gut designed – wir müssen ja, wie zuvor, alle Einträge mit linearer Suche abwandern, d.h. wir profitieren fast gar nicht von der Suche mit konstanter Laufzeit, die uns die Hash-Tabelle bietet. Der schnelle Zugriff, der die Vorteile von dict ausnutzt, erfolgt ja lediglich beim Zugriff auf nameund num, aber eben nicht bei der Suche nach dem richtigen Eintrag.

  • Für unser einfaches Telefonbuch benötigen wir streng genommen aber weder name noch num! Wir können diesen Code daher wie folgt vereinfachen:

    # Implementiert ein Telefonbuch mit dict
    
    from cs50 import get_string
    
    people = {
        "Fuhrmann": "+1-617-495-1000",
        "David": "+1-617-495-1000",
        "John": "+1-949-468-2750",
    }
    
    # Suche nach Name
    name = get_string("Name: ")
    if name in people:
        print(f"Nummer: {people[name]}")
    else:
        print("Nicht gefunden")

    Beachten Sie, dass people nun ein dict ist und mit geschweiften Klammern definiert wird. Die Anweisung if name in people prüft, ob name als Key im Wörterbuch people enthalten ist. Beachten Sie auch, dass wir in der Anweisung print mit dem Wert von name im People-Wörterbuch indexieren können. Sehr nützlich!

  • Python gibt sein Bestes, um bei der Suche eine konstante Zeit zu erreichen, indem es seine eingebauten Suchfunktionen verwendet.

  • Sie können mehr über Wörterbücher in der Python-Dokumentation erfahren.

Kommandozeilen-Argumente

  • Diesen Abschnitt haben wir aus Zeitgründen nicht in der Präsenzvorlesung behandelt.

  • Wie bei C können Sie auch in Python Kommandozeilen-Argumente verwenden. Betrachten Sie den folgenden Code:

    # Druckt ein Kommandozeilenargument
    
    von sys import argv
    
    if len(argv) == 2:
        print(f"hallo, {argv[1]}")
    else:
        print("hallo, Welt")

    Beachten Sie, dass argv[1] unter Verwendung einer formatierten Zeichenkette gedruckt wird, erkennbar an dem f in der print-Anweisung.

  • Sie können alle Argumente in argv wie folgt ausgeben:

    # Kommandozeilenargumente ausgeben, Indizierung in argv
    
    von sys import argv
    
    for i in range(len(argv)):
        print(argv[i])

    Beachten Sie, dass der obige Code das Wort “python” (den Python-Interpreter selbst) nicht ausgibt. Das erste Argument ist der Name der Datei, die Sie ausführen.

  • Sie können Teile von Listen wegschneiden (engl. to slice). Betrachten Sie den folgenden Code:

    # Kommandozeilenargumente drucken
    
    from sys import argv
    
    for arg in argv[1:]:
        print(arg)

    Beachten Sie, dass die Ausführung dieses Codes dazu führt, dass der Name der Datei, die Sie ausführen, weggeschnitten wird. Es werden alle Argumente ab dem ersten (nullindiziert, also eigtl. ab dem zweiten) ausgegeben.

  • Sie können mehr über die sys-Bibliothek in der Python-Dokumentation erfahren.

Exit-Code

  • Diesen Abschnitt haben wir aus Zeitgründen nicht in der Präsenzvorlesung behandelt.

  • Die sys-Bibliothek hat auch eingebaute Methoden. Wir können sys.exit(i) verwenden, um das Programm mit einem bestimmten Exit-Code zu beenden:

    # Beendet mit explizitem Wert, importiert sys
    
    import sys
    
    if len(sys.argv) != 2:
        print("Fehlendes Kommandozeilenargument")
        sys.exit(1)
    
    print(f"hallo, {sys.argv[1]}")
    sys.exit(0)

    Beachten Sie, dass die Punkt-Notation verwendet wird, um die eingebauten Funktionen von “sys” zu nutzen, da wir das ganze sys-Modul importieren.

Bibliotheken von Drittanbietern

  • Diesen Abschnitt haben wir aus Zeitgründen nicht in der Präsenzvorlesung behandelt.

  • Einer der Vorteile von Python ist seine riesige Benutzerbasis und die ebenso große Anzahl von Bibliotheken von Drittanbietern.

  • Beispiel: pip install qrcode.

      # Generates a QR code
      # https://github.com/lincolnloop/python-qrcode
    
      import os
      import qrcode
    
      # Generate QR code
      img = qrcode.make("https://youtu.be/xvFZjo5PgG0")
    
      # Save as file
      img.save("qr.png", "PNG")

Zusammenfassend

In dieser Vorlesung haben Sie gelernt, wie die Bausteine der Programmierung aus früheren Vorlesungen in Python genutzt werden können. Außerdem haben Sie gelernt, wie mit Pythons Syntax einfacher Code geschrieben werden kann und wie Sie gängige Python-Funktion nutzen können.

Vermutlich haben Sie in dieser Vorlesung einige der Konzepte auf Anhieb verstanden. Das liegt nicht nur daran, dass die Syntax von Python leichter zugänglich ist als die Syntax von C. Es liegt auch daran, dass die Fähigkeiten und das Wissen aus den letzten Wochen Ihnen den Einstieg in eine neue Programmiersprache erleichtern. Sie verfügen nun über eine solide Basis, mit der Sie sich selbst neue Programmiersprachen aneignen können!

Vielleicht können Sie jetzt erahnen, wie Sie durch die Herangehensweise, die wir in diesem Kurs verfolgen, eine neue Art des Lernens entdecken – neugierig zu sein (Was passiert eigentlich, wenn ich hier … eingebe?) und nachzuhaken, um den Dingen auf den Grund zu gehen (Wieso ist das eigentlich so?). Diese Art des Lernens wird Ihnen hoffentlich beim Erlernen weiterer Programmiersprachen - und vielleicht auch beim Lernen in anderen Bereichen - von Nutzen sein.

Konkret haben wir besprochen…

  • Python
  • Variablen
  • Konditionale
  • Schleifen
  • Typen
  • Objektorientiertes Programmieren
  • Trunkierung und Fließkomma-Ungenauigkeit
  • Ausnahmen
  • Wörterbücher
  • Befehlszeilenargumente
  • Bibliotheken von Drittanbietern

Bis zum nächsten Mal!