Was ist Python?

Merkmale der Programmiersprache Python:

Merkmale von CPython:

Grundlagen

Es gibt zwei Sprachversionen, die nicht vollständig kompatibel sind:

Python 2 wird zwar noch “gepflegt”, aber neue Features werden nur noch zu Python 3 hinzugefügt.

Hier: Python 3, ggf. Unterschiede zu Python 2

interaktiver Modus

Rechnen mit Zahlen

Ganzzahlen

  • Typ int

  • können in verschiedenen Formaten angegeben werden:
    • normalerweise Basis 10: 5, 102, -135876
    • Basis 2 (binär) mit Präfix 0b: 0b111 → 7
    • Basis 8 (oktal) mit Präfix 0o: 0o21 → 17
    • Basis 16 (hexadezimal) mit Präfix 0x: 0x2F → 47
  • keine Beschränkung des Werts: 999 ** 999 ist kein Problem (**: Potenz)

Fließkommazahlen

  • Typ float

  • Eingabe mit .: 1.5, -0.123476

  • Eingabe in wissenschaftlicher Notation: 1e3 → 1000.0; 123e-5 → 0.00123

  • Wertebereich und Genauigkeit beschränkt

    >>> 0.1 + 0.2
    0.30000000000000004

    Hier die Erläuterung.

    (Es gibt aber in der Standardbibliothek ein Modul zum exakten Rechnen mit Kommazahlen, z.B. für Finanzen etc.)

  • explizite Umwandlung in Ganzzahl inkl. Runden Richtung 0:

    >>> int(5.3)
    5
    >>> int(5.8)
    5
    >>> int(-5.3)
    -5
  • explizite Umwandlung von Ganzzahl:

    >>> float(3)
    3.0

arithmetische Operatoren

  • ist einer der Operanden eine Fließkommazahl, wird Fließkomma-Arithmetik angewendet (u.U. Rundungsfehler)
  • ansonsten: Ganzzahl-Arithmetik (exakt)
  • +, -, *: wie erwartet
  • /, // s.u.
  • %: Modulo (123 % 103)
  • **: Potenz (3 ** 29)

Es gelten die üblichen Rechenregeln. Im Zweifel oder um die Rangfolge von Operatoren zu überstimmen, können Klammern () verwendet werden. Die Anzahl Leerzeichen um die Operatoren spielt keine Rolle (üblicherweise ein Leerzeichen):

>>> 2 * 3 + 5
11
>>> 2 * (3 + 5)
16
>>> 2 ** 3 * 4
32
>>> 2 ** (3 * 4)
4096

Division

Es gibt zwei Divisionsoperatoren:

  • / ist Fließkommadivision. Ergebnis immer float:
    3 / 50.6

  • // ist Ganzzahldivision: rundet auf nächste kleinere ganze Zahl ab (Richtung −∞). Ergebnis int wenn beide Operanden int, sonst float):
    3 // 50
    -3 // 5-1
    -3.0 // 5-1.0

Unterschied zu Python 2

  • Python 2: / ist Ganzzahldivision mit Abrunden wenn beide Operanden int (→ inkonsistent, einer der Vorteile von Python 3).

Weitere mathematische Funktionen

Das Modul math aus der Standardbibliothek enthält mathematische Konstanten und Funktionen (Trigonometrie, Logarithmen, etc.):

>>> import math  # lädt das Modul
>>> math.pi
3.141592653589793
>>> math.exp(-1)
0.36787944117144233
>>> math.log(2.71)
0.9969486348916096
>>> math.cos(3.14)
-0.9999987317275395

Variablen (Namen)

Meistens will man Zwischenergebnisse speichern, um sie später weiterzuverwenden oder um Ausdrücke zu vereinfachen.

Durch den Zuweisungsoperator = gibt man einem Objekt (momentan kennen wir nur Zahlen) einen Namen:

>>> kapital = 1249
>>> zinssatz = 0.75 / 100  # Prozent

(es sind auch Großbuchstaben in Variablennamen erlaubt, üblicherweise schreibt man sie aber klein)

>>> zinsen = kapital * zinssatz  # hier keine Ausgabe, da kein
                                 # "Ausdruck" (expression) sondern eine
                                 # "Anweisung" (statement)
>>> kapital + zinsen
1258.3675

Objektmodell

In Python ist alles ein Objekt. Jedes Objekt hat

  • eine Identität
  • einen Typ
  • einen Wert

Die Identität eines Objekts wird festgelegt, wenn das Objekt erzeugt wird und ändert sich danach nie mehr.

Der Typ eines Objekts bestimmt, welche Werte das Objekt annehmen kann und welche Operationen mit dem Objekt möglich sind. Der Typ eines Objekts ändert sich ebenfalls nicht mehr, sobald das Objekt erzeugt wurde.

Abhängig vom Typ kann der Wert eines Objekts verändert werden oder nicht. Objekte bzw. Typen, bei denen das Ändern des Werts möglich ist, heißen mutable, die anderen heißen immutable.

Die verschiedenen Zahlentypen sind z.B. immutable.

(Siehe genaue Erläuterung.)

Die Anweisung kapital = 1249 bedeutet:

Dem Objekt 1249 (vom Typ int) wird der Name kapital gegeben.

Oder: Der Name kapital “verweist” auf das int-Objekt 1249.

Nicht: Der Speicherplatz für ein int mit dem Namen kapital wird mit dem Wert 1249 belegt. (wie in anderen Programmiersprachen, z.B. C)

Konsequenzen:

  • Derselbe Name kann später für ein anderes Objekt (auch mit anderem Typ) neu vergeben werden.
  • Dasselbe Objekt kann unter mehreren Namen bekannt sein.

Beispiel:

>>> kapital = 1249
>>> geld = kapital

Sowohl kapital als auch geld verweisen jetzt auf 1249.

>>> geld
1249
>>> kapital
1249

Es ist dasselbe Objekt (id gibt die Identität eines Objekts aus):

>>> id(kapital)
140331518656464
>>> id(geld)
140331518656464

Wenn man jetzt den Namen kapital neu vergibt:

>>> kapital = 398.76

verweist geld immer noch auf dasselbe int-Objekt, und kapital verweist auf ein anderes float-Objekt:

>>> geld
1249
>>> id(geld)
140331518656464
>>> kapital
398.76
>>> id(kapital)
140331517917136

Merke: Durch den Zuweisungsoperator = wird niemals ein Objekt verändert, sondern es wird ein Name (neu) vergeben.

Mehrfachzuweisung

Man kann auch mehrere Namen gleichzeitig vergeben:

>>> a, b = 3, 5
>>> a
3
>>> b
5
>>> a, b = b, a  # keine "temporäre Variable" nötig
>>> a
5
>>> b
3

nicht-interaktiver Modus

Anstatt die Anweisungen einzeln einzutippen, kann man sie auch in einer Datei speichern und diese als “Skript” oder “Programm” ausführen.

Im Gegensatz zum interaktiven Modus werden Ausdrücke nicht automatisch ausgegeben! Um das Ergebnis der Berechnung zu sehen, muss man es explizit auf die Standardausgabe schreiben. Das geht am einfachsten mit print:

Beispiel: Datei zinsen.py

kapital = 1249
zinssatz = 0.75e-2  # Prozent in wissenschaftlicher Notation
zinsen = kapital * zinssatz

# Leerzeilen und Kommentare werden ignoriert

print(kapital + zinsen)  # keine Ausgabe ohne print

Ruft man von der Shell den Python-Interpreter mit dem Dateinamen als Argument auf, liest er die darin enthaltenen Anweisungen, führt sie aus und beendet sich danach:

$ python3 zinsen.py
1258.3675

Unterschied Python 2:

Zeichenketten (Strings)

Neben den Zahlen ein weiterer grundlegender Datentyp (Verwendung für “Text”): str.

Folgende Dinge sind z.B. Strings:

Eingabe

Strings werden mit Anführungszeichen eingegeben (einfache ' oder doppelte ").

Beispiele:

>>> print('Hallo Welt!')
Hallo Welt!
>>> print("Wie geht's?")
Wie geht's?
>>> print('Er heißt "Fritz".')
Er heißt "Fritz".

Bestimmte Sonderzeichen werden als Kombination mit \ beschrieben:

>>> print('Ein\nZeilenumbruch')
Ein
Zeilenumbruch
>>> print('Ein\tTabulator')
Ein     Tabulator
>>> print('Erste\tZweite Spalte')
Erste   Zweite Spalte

Für längere Texte mit mehreren Zeilen kann man auch dreifach-Anführungszeichen (''' oder """) benutzen:

>>> print('''Erste Zeile)
... Zweite Zeile''')
Erste Zeile
Zweite Zeile

(... ist der Prompt, falls eine Eingabe über eine Zeile hinaus geht)

Mehrere Strings können mit + aneinandergehängt werden:

>>> a = 'Hallo'
>>> b = 'Welt'
>>> a + ' ' + b
'Hallo Welt'

Ein String kann mit * vervielfältigt werden:

>>> 3 * 'Ha'
'HaHaHa'

Objekte vom Typ str sind immutable. Daher wird beim aneinanderhängen oder “multiplizieren” von Strings immer ein neues Objekt erzeugt, nicht das bestehende Objekt verändert.

Ausgabe

Die Funktion print wandelt Objekte automatisch in Strings um, falls möglich. Außerdem fügt sie zwischen mehrere Objekte (mit Komma getrennt) automatisch Leerzeichen ein (und am Ende einen Zeilenumbruch):

>>> kapital = 238475
>>> print('Das Kapital ist', kapital, '.')
Das Kapital ist 238475 .

Für mehr Kontrolle über die Formatierung:

>>> print('Das Kapital ist {}.'.format(kapital))
Das Kapital ist 238475.
>>> print('Pi ist ungefähr {:.2f}.'.format(math.pi))
Pi ist ungefähr 3.14.

Die Dokumentation von format ist hier.

Auf einzelne Zeichen und Teilstrings zugreifen

Mit dem []-Operator greift man auf einzelne Zeichen eines Strings an einer bestimmten Position (Index) zu. Das erste Zeichen hat den Index 0:

>>> s = 'Hallo Welt'
>>> s[0]
'H'
>>> s[1]
'a'

Auf eine Position zuzugreifen, die es nicht gibt, erzeugt eine Fehlermeldung:

>>> s[15]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: string index out of range

Die Anzahl der Zeichen (“Länge”) eines Strings bekommt man mit der len-Funktion:

>>> len(s)
10

Der größte gültige Index wäre also 9 (nicht 10).

(Ein “Zeichen” ist auch ein String mit der Länge 1.)

Mit negativen Indizes wird von hinten gezählt:

>>> s[-1]   # letztes Zeichen
't'
>>> s[-10]  # erstes Zeichen
'H'
>>> s[-11]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: string index out of range

Abschnitte eines Strings bekommt man auf ähnliche Weise:

>>> s[:5]   # die ersten 5 Zeichen
'Hallo'
>>> s[-4:]  # die letzten 4 Zeichen
'Welt'
>>> s[2:7]  # die ersten 7 ohne die ersten 2
'llo W'
>>> s[:]    # alles
'Hallo Welt'

Optional: Schrittweite

>>> s[::2]  # jedes 2. Zeichen
'HloWl'
>>> s[::-1] # rückwärts
'tleW ollaH'

Zeichen und Teilstrings finden

Ob ein String in einem anderen enthalten ist, bekommt man mit dem in-Operator heraus:

>>> 'a' in s
True
>>> 'Welt' in s
True
>>> 'x' in s
False

Speziell: Am Anfang oder am Ende?

>>> s.startswith('Hallo')
True
>>> s.endswith('Hallo')
False
>>> s.endswith('Welt')
True

Wo befindet sich ein Teilstring?

>>> s.index('H')
0
>>> s.index('l')  # der erste Treffer zählt
2
>>> s.index('Welt')
6
>>> s.index('Erde')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: substring not found

(startswith, endswith und index sind “Methoden” eines String-Objekts)

Zusammengesetzte Anweisungen

(compound statements)

Bisher wurde eine Anweisung durch das Ende der Zeile abgeschlossen, z.B. eine Zuweisung eines Namens mit = oder print.

Durch bestimmte Schlüsselwörter können mehrere Anweisungen über mehrere Zeilen gruppiert werden:

Bedingte Ausführung

Mit if wird eine Gruppe von Anweisungen (oder nur eine einzelne Anweisung) nur ausgeführt, wenn eine Bedingung erfüllt ist:

if x >= 3:
    print('erstens')

print('zweitens')

if x > 10:
    print('drittens')
    print('viertens')

Syntax-Regeln

  • die Zeile mit if muss mit einem : enden
  • die gruppierten Anweisungen müssen weiter eingerückt sein als die if-Zeile
  • alle gruppierten Anweisungen müssen gleich weit eingerückt sein
if 5 > 3:
print('hallo')        # Syntax-Fehler

if True:
   x = 10
  print(x)            # Syntax-Fehler

if 'hallo':
    test = 3
     print(10 * test) # Syntax-Fehler

Üblicherweise werden 4 Leerzeichen für “einmal” Einrücken verwendet.

Vorsicht mit der Tabulator-Taste: Je nach Einstellung des Texteditors fügt sie ein echtes Tabulator-Zeichen (\t) oder eine bestimmte Anzahl Leerzeichen ein.

Das echte Tabulator-Zeichen zum Einrücken zu verwenden ist in Python unüblich.

Tabulator-Zeichen und Leerzeichen zu mischen ist möglicherweise ein Syntax-Fehler.

Im interaktiven Modus ändert sich der Prompt zu ... bis die zusammengesetzte Anweisung abgeschlossen ist:

>>> if 10 > 3:
...     print('Das stimmt.')
...

Man muss noch einmal mehr die Eingabetaste drücken, um dem Interpreter mitzuteilen, dass die Anweisung abgeschlossen ist.

Vergleichsoperatoren

  • ==: gleicher Wert (z.B. bei Zahlen, Strings, …)
  • !=: verschiedener Wert
  • >, >=, <, <=:
    • Vergleich von Objekten, deren Typ eine Sortierung unterstützt (z.B. Zahlen nach Wert, Strings alphabetisch)
    • das geht auch: 3 < x < 10 (x > 3 “und” x < 10)
  • is: gleiche Identität
    • zwangsweise dann auch gleicher Wert, aber nicht umgekehrt: verschiedene Objekte können den gleichen Wert haben
    • x is y äquivalent zu id(x) == id(y)
    • oft im Zusammenhang mit speziellem Objekt: None (“nichts”)

Wahrheitswerte

  • True ist “wahr”
  • False ist “falsch”
  • None ist “falsch”
  • 0 und 0.0 sind “falsch”, positive und negative Zahlen sind “wahr”
  • ein String der Länge 0 ('') ist “falsch”, andere sind “wahr”

Verknüpfung von Wahrheitswerten

Mit den booleschen Operatoren kann man mit Wahrheitswerten “rechnen”:

  • not x: “wahr” wenn x “falsch” ist
  • statt not x in y auch: x not in y
  • x and y: “wahr”, wenn beide “wahr” sind
  • x or y: “wahr”, wenn einer der beiden oder beide “wahr” sind

Genaugenommen:

  • x and y: y wenn x “wahr”, sonst x

    >>> 0 and 5
    0
    >>> 3 and 5
    5
  • x or y: x wenn x “wahr”, sonst y

    >>> 0 or 5
    5
    >>> 3 or 5
    3

Außerdem: “ternärer Operator”

  • x if c else y: x wenn c “wahr”, sonst y

Verschachtelung und Verzweigung

Zusammengesetzte Anweisungen können wiederum aus anderen zusammengesetzten Anweisungen bestehen:

if x > 3:
    if x < 10:
        print('mindestens 4, höchstens 9')
    print('10 oder mehr')

Optional können mit elif weitere Bedingungen der Reihe nach abgefragt werden. Die Anweisungen, deren Bedingung zuerst erfüllt sind, werden ausgeführt, die anderen nicht.

Ebenfalls optional können unter else Anweisungen angegeben werden, die ausgeführt werden, wenn keine der Bedingungen unter if und ggf. elif erfüllt sind:

if x > 100:
    print('groß')
elif x > 10:        # 10 < x <= 100
    print('mittel')
else:               # x <= 10
    print('klein')

Wiederholung

Mit while werden Anweisungen wiederholt (“While-Schleife”), solange eine Bedingung erfüllt ist:

platz = 100
koffer = 30
anzahl = 0

while platz > koffer:
    platz -= koffer  # anstatt "platz = platz - koffer"
    anzahl += 1      # anstatt "anzahl = anzahl + 1"

print('Es passen {} Koffer rein, {} Platz bleibt übrig.'
      .format(anzahl, platz))

(Wiederholung: -= und += “ändern” nicht ein Zahlenobjekt, sondern setzen die Namen auf andere Objekte)

(Solange noch Klammern geöffnet sind, kann eine Anweisung über mehrere Zeilen verteilt werden, die Einrückung spielt dabei dann keine Rolle. Üblicherweise versucht man, Zeilen nicht länger als ca. 80 Zeichen zu machen.)

(Das geht auch eleganter, siehe z.B. divmod)

Mit der continue-Anweisung werden die restlichen Anweisungen übersprungen und mit der nächsten Wiederholung weitergemacht:

x = 0
while x < 10:  # continue springt hierher
    x += 1
    if x % 2:  # ungerade
        continue
    print(x)   # 2, 4, 6, 8, 10

Mit der break-Anweisung wird die Schleife komplett abgebrochen:

x = 0
while True:
    x += 1
    if x >= 10:
        break
    print(x)   # 1, 2, ..., 9
# break springt hierher

Komplexe Datentypen

Es gibt in Python mehrere Datentypen, die “Sammlungen” von Objekten darstellen:

Listen

Grundlagen

Eine Liste wird mit [] und , eingegeben. In einer Liste können beliebige Objekte enthalten sein (auch andere Listen):

>>> x = []                # leere Liste
>>> x = [3, 'haus', None]
>>> x = [[1, 2], [3, 4]]  # "Matrix"
  • Die Elemente in einer Liste haben eine definierte Reihenfolge.
  • Auf einzelne Elemente und Teillisten greift man genauso wie bei Strings mit dem Index-Operator [] zu.
  • Auch len funktioniert genauso.
  • Auch in funktioniert genauso.

Unterschied zu Strings:

>>> x = [1, 2, 3]
>>> x[1] = 'hallo'
>>> x
[1, 'hallo', 3]

Listen sind im Gegensatz zu Strings mutable.

Erinnerung Zuweisungsoperator =: x[1] ist auch ein “Name” für den String hallo.

Durch = kann doch kein Objekt verändert werden, jetzt also doch!?

Nein: x[1] war vorher das int-Objekt 2. Dieses wurde nicht verändert. Der “Name” x[1] wurde von 2 auf 'hallo' umgesetzt.

Umwandlung String/Liste

Ein String kann mit der split-Funktion in eine Liste von Teilstrings umgewandelt werden:

>>> 'hallo welt'.split()  # standardmäßig wird an Leerzeichen aufgeteilt
['hallo', 'welt']
>>> 'asdf|qwer|cvbn'.split('|')
['asdf', 'qwer', 'cvbn']

Umgekehrt kann eine Liste von Strings mit join in einen einzelnen String umwandeln:

>>> names = ['Peter', 'Paul', 'Mary']
>>> ', '.join(names)  # join ist "Methode" des Strings ', '
'Peter, Paul, Mary'

Iterieren

Um eine Gruppe von Anweisungen für jedes Element einer Liste auszuführen, kann man eine for-Schleife benutzen (meistens einer while-Schleife vorzuziehen):

>>> for name in names:
...     print(name)

Es gibt verschiedene Funktionen um komplexere Iterationen zu ermöglichen:

>>> for i, name in enumerate(names, 1):
...     print('{}. {}'.format(i, name))
1. Peter
2. Paul
3. Mary
>>> hobbies = ['Tennis', 'Klavier', 'Lesen']
>>> for name, hobby in zip(names, hobbies):
...     print(name, hobby)
Peter Tennis
Paul Klavier
Mary Lesen

Mit for kann man ebenso über Strings iterieren:

>>> for c in 'hallo':
...     print(c)
h
a
l
l
o

Sortieren, Umkehren

Falls die Elemente einer Liste untereinander vergleichbar sind (< etc.):

>>> for name in sorted(names):
...     print(names)

Gibt die Elemente in sortierter Reihenfolge aus, die Liste bleibt aber unverändert.

>>> names
['Peter', 'Paul', 'Mary']
>>> names.sort()
>>> names
['Mary', 'Paul', 'Peter']

Das list-Objekt wird dadurch verändert.

Analog mit reversed und reverse.

reversed und sorted funktionieren auch mit Strings, aber reverse und sort nicht! Strings können nicht verändert werden (“immutable”), nur eine sortierte bzw. umgekehrte Kopie erzeugt werden.

Erweitern

Neue Elemente können an eine Liste angehängt oder an einer anderen Stelle eingefügt werden (die Liste wird dabei verändert):

>>> names.append('John')  # hängt ein einzelnes Element an
>>> names
['Mary', 'Paul', 'Peter', 'John']
>>> names.extend(['Ringo', 'George'])
>>> names
['Mary', 'Paul', 'Peter', 'John', 'Ringo', 'George']

Vorsicht:

>>> names.extend('Fred')
>>> names
[..., 'F', 'r', 'e', 'd']

An beliebiger Stelle einfügen:

>>> names.insert(2, 'Albert')
['Mary', 'Paul', 'Albert', 'Peter', ...]

Einträge löschen

Den Eintrag an einer bestimmten Position löschen:

>>> del names[1]
>>> names
['Mary', 'Albert', 'Peter', ...]

Ein Element mit einem bestimmten Wert löschen (falls es mehrfach vorkommt: nur das erste löschen):

>>> names.remove('Mary')
>>> names
['Albert', 'Peter', ...]

Vorsicht: Keine Einträge zu einer Liste hinzufügen oder aus ihr entfernen, während gleichzeitig über sie iteriert wird. Das führt zu unerwarteten Ergebnissen.

1. Beispiel: Liste scheinbar “verdoppeln”:

>>> for name in names:
...     names.append(name)  # Endlosschleife, falls Liste nicht leer

Lösung:

>>> for name in list(names):
...     names.append(name)

list() erzeugt eine neue Liste, über die iteriert wird.

Noch besser: s.u.

2. Beispiel: Scheinbar alle Namen mit “P” löschen:

>>> names = ['Mary', 'Paul', 'Peter', 'John', 'Ringo', 'George']
>>> for name in names:
...    if name.startswith('P'):
...        names.remove(name)
>>> names
['Mary', 'Peter', 'John', 'Ringo', 'George']   # ???

Besser: for name in list(names) (Kopie der Liste)

Noch besser:

Neue Listen aus Listen erzeugen

Liste verdoppeln:

>>> names = 2 * names

Listen aneinanderhängen:

>>> x = [1, 2, 3]
>>> y = [10, 11, 12]
>>> x + y
[1, 2, 3, 10, 11, 12]

List comprehensions

(auf deutsch: ???)

Ähnlich zur Mengen-Schreibweise in der Mathematik:

{x2|x ∈ A, 2x < 10}

>>> A = list(range(10))  # [0, 1, ..., 9]
>>> [x**2 for x in A if 2 * x < 10]
[0, 1, 4, 9, 16]

Der if-Teil ist optional.

Äquivalent zu:

>>> result = []
>>> for x in A:
...     if 2 * x < 10:
...         result.append(x**2)

Dictionaries

Dictionaries (dict) sind “assoziative Arrays”, also eine Sammlung von (Schlüssel → Wert)-Paaren.

Unterschiede zu Listen und Strings:

Gemeinsamkeiten mit Listen und Strings:

Eingabe mit {}, : und ,:

>>> si_prefixes = {'k': 1e3, 'M': 1e6, 'm': 1e-3, 'n': 1e-9}
>>> si_prefixes['M']
1000000.0
>>> bayern_spieler = {1: 'Neuer', 17: 'Boateng', 14: 'Alonso'}
>>> bayern_spieler[17]
'Boateng'

Neue Einträge werden mit dem []-Operator angelegt:

>>> len(bayern_spieler)
3
>>> bayern_spieler[27] = 'Alaba'
>>> len(bayern_spieler)
4

Alle Schlüssel und alle Werte jeweils als Listen:

>>> list(bayern_spieler.keys())
[1, 17, 14, 27]
>>> list(bayern_spieler.values())
['Neuer', 'Boateng', 'Alaba', 'Alonso']

Iterieren

>>> for nummer in bayern_spieler:
...     print(nummer, bayern_spieler[nummer])
17 Boateng
14 Alonso
[...]
>>> for nummer, name in bayern_spieler.items():
...     print(nummer, name)
[...]

Tupel

Tupel (tuple) sind ähnlich wie Listen Sammlungen von Objekten, die eine bestimmte Reihenfolge haben.

Wesentlicher Unterschied: Sie sind immutable und daher als Schlüssel in Dictionaries geeignet.

Eingabe mit , (die Klammern () sind nur optional)

z.B. in einem Spiel mit “Schatzkarte”:

>>> karte = {}              # leeres Dictionary
>>> karte[1, 2] = 'schatz'  # "1, 2" ist das Tupel
>>> for ort in [(5, 8), (10, 2)]:  # Klammern zur Abgrenzung
...     karte[ort] = 'falle'

Erinnerung Mehrfachzuweisung:

>>> a, b = 3, 5

3, 5 war eigentlich ein Tupel. Es wurde darüber “iteriert” und der Reihe nach die Namen vergeben.

Das funktioniert genauso mit allem anderen, worüber man iterieren kann (Listen, Strings, Dictionaries).

>>> a, b = 'xy'
>>> a
'x'

Mengen

Mengen (set) haben keine Reihenfolge und jeder Eintrag kann nur einmal enthalten sein.

>>> s = {1, 2, 3}  # keine :
>>> d = {}         # leeres Dictionary
>>> s2 = set()     # leere Menge
>>> s.add(6)
>>> len(s)
4
>>> s.add(1)  # schon enthalten
>>> len(s)
4

Strukturieren und Wiederverwerten von Programmen

Um eine Gruppe von Anweisungen wiederzuverwenden und Programme zu strukturieren, kann man eigene Funktionen definieren (und bereits vorhandene benutzen).

Funktionen sind ähnlich wie “Befehle” in der Linux-Shell:

Allerdings sind beliebige Objekte als Argumente und Rückgabewerte möglich.

Ein paar vordefinierte Funktionen:

Manche Funktionen haben scheinbar keinen Rückgabewert, z.B. print:

>>> x = print('hello')
hello
>>> x
>>>

Was ist x?

Funktionen “ohne” Rückgabewert geben in Wirklichkeit das Objekt None zurück.

>>> x is None
True

(Wiederholung: is vergleicht die Identität von Objekten und sollte nur in bestimmten Fällen benutzt werden. Normalerweise muss man den Vergleichsoperator == benutzen, um den Wert von Objekten zu vergleichen.)

“Methoden” sind eine spezielle Art von Funktion. Sie “gehören” zu einem Objekt und erhalten dieses automatisch als ein weiteres Argument:

Funktionen definieren

Eine Funktionsdefinition (def) ist wie if, while und for eine zusammengesetzte Anweisung mit den gleichen Syntax-Regeln.

Wenn die Funktion später aufgerufen wird, werden die enthaltenen Anweisungen ausgeführt.

Die Funktion ist beendet, wenn alle Anweisungen ausgeführt worden sind (dann wird automatisch None zurückgegeben), oder wenn die return-Anweisung ausgeführt wurde, durch die auch optional der Rückgabewert festgelegt wird.

>>> def berechne_zinsen(kapital, zinssatz_prozent):
...     zinssatz = zinssatz_prozent * 1e-2
...     return kapital * zinssatz
...
>>> k = 12376
>>> for p in [0.75, 1.0, 1.25]:
...     print(p, berechne_zinsen(k, p))
0.75 92.82
1.0 123.76
1.25 154.7

Eine Funktion ist auch ein Objekt, dem durch die def-Anweisung auch ein Name (hier: berechne_zinsen) zugewiesen wird, und kann daher wie jedes andere Objekt in einer Liste oder einem Dictionary enthalten sein (sogar als Schlüssel):

>>> berechne_zinsen
<function berechne_zinsen at 0x7f1710001668>
>>> funktionssammlung = {'z': berechne_zinsen}
>>> funktionssammlung['z'](12983746, 2.3)
298626.158
>>> trig = [math.sin, math.cos, math.tan]
>>> for f in trig:
...     print(f(0.5))
...
0.479425538604
0.87758256189
0.546302489844

Sichtbarkeit von Namen

Namen, die innerhalb einer Funktion vergeben werden, sind außerhalb der Funktion nicht sichtbar.

>>> def bla():
...     x = 5
...     return x
...
>>> bla()
5
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

Docstrings

Es ist empfehlenswert, zu einer Funktion einen “Docstring” hinzuzufügen, der beschreibt, was die Funktion macht, und wie man sie benutzt.

def berechne_zinsen(kapital, zinssatz_prozent):
    """Berechne die Zinsen, die auf das Kapital beim angegebenen Zinssatz
    (in Prozent) anfallen.
    """
    zinssatz = zinssatz_prozent * 1e-2
    return kapital * zinssatz

Das besondere dabei ist, dass der Docstring vom Python-Hilfesystem verarbeitet werden kann:

>>> help(zinsen)
Help on function berechne_zinsen in module __main__:

berechne_zinsen(kapital, zinssatz_prozent)
    Berechne die Zinsen, die auf das Kapital beim angegebenen Zinssatz
    (in Prozent) anfallen.

Noch besser ist es, Beispiele für die Benutzung der Funktion in den Docstring zu integrieren. Diese können vom Modul doctest automatisch ausgeführt und überprüft werden!

def berechne_zinsen(kapital, zinssatz_prozent):
    """Berechne die Zinsen, die auf das Kapital beim angegebenen Zinssatz
    (in Prozent) anfallen.

    >>> berechne_zinsen(12983746, 2.3)
    298626.158
    """
    zinssatz = zinssatz_prozent * 1e-2
    return kapital * zinssatz

Angenommen, die Funktion berechne_zinsen ist in der Datei zinsen.py gespeichert, geschieht das von der Shell aus folgendermaßen:

$ python3 -m doctest zinsen.py

Module

Eine Datei, in der Python-Code gespeichert ist, kann nicht nur als Skript ausgeführt werden (python3 <dateiname>), sondern auch als Modul importiert werden.

Die Funktionen, die in der Datei (mit der Endung .py) enthalten sind, werden dadurch verfügbar gemacht. Der Name des Moduls ist gleich dem Dateinamen ohne die Endung .py:

>>> import zinsen
>>> zinsen.berechne_zinsen(12983746, 2.3)
298626.158

Um einzelne Funktionen zu importieren und das Voranstellen des Modulnamens zu vermeiden, gibt es folgende Variante:

>>> from zinsen import berechne_zinsen
>>> berechne_zinsen(12983746, 2.3)
298626.158

Argumente von der Shell

Die Argumente, die beim Ausführen eines Python-Programms von der Shell aus mitgegeben werden, sind innerhalb des Programms als eine Liste Strings namens argv im Modul sys verfügbar.

argv[0] ist der Name des aufgerufenen Skripts, die eigentlichen Argumente sind argv[1], argv[2], etc.

Beispiel: Datei zinsen.py

import sys

def berechne_zinsen(kapital, zinssatz_prozent):
    """Berechne die Zinsen, die auf das Kapital beim angegebenen Zinssatz
    (in Prozent) anfallen.

    >>> zinsen(12983746, 2.3)
    298626.158
    """
    zinssatz = zinssatz_prozent * 1e-2
    return kapital * zinssatz

k = float(sys.argv[1])
z = float(sys.argv[2])
print(berechne_zinsen(k, z))

Dann:

$ python3 zinsen.py 12983746 2.3
298626.158

Problem:

Beim Importieren wird immer der gesamte Inhalt der Datei ausgeführt. Um vermeiden, dass ein Skript ausgeführt wird, wenn es auch als Modul verwendet werden soll, gibt es folgende Technik:

def zinsen(kapital, zinssatz_prozent):
    """Berechne die Zinsen, die auf das Kapital beim angegebenen Zinssatz
    (in Prozent) anfallen.

    >>> zinsen(12983746, 2.3)
    298626.158
    """
    zinssatz = zinssatz_prozent * 1e-2
    return kapital * zinssatz

if __name__ == '__main__':
    # dieser Teil wird nur beim Ausführen als Skript ausgeführt,
    # nicht beim Importieren als Modul
    import sys
    k = float(sys.argv[1])
    z = float(sys.argv[2])
    print(berechne_zinsen(k, z))

Die Docstrings kann man auch von der Shell aus mit dem Befehl pydoc3 <Modul> aufrufen.

Ein- und Ausgabe mit Dateien

Die einfachste Möglichkeit, Informationen aus einem Programm auszugeben, ist die print-Funktion zu benutzen.

Sie schreibt normalerweise ihre Argumente auf die Standardausgabe (siehe Einführung Linux).

Die Standardeingabe wird vom Objekt stdin im Modul sys repräsentiert. Um zeilenweise davon zu lesen, gibt es folgende Technik:

import sys
for line in sys.stdin:
    # Eine gelesene Zeile ist nun ein String mit dem Namen `line`
    # und kann verarbeitet werden.

Dateien öffnen und schließen

Um anstelle der Standardeingabe eine Datei zum Lesen zu verwenden, muss sie erst geöffnet werden. Das zeilenweise Lesen funktioniert analog. Anschließend muss die Datei wieder geschlossen werden:

f = open('datei.txt')
for line in f:
    # ...
f.close()  # so nicht angewöhnen, besser s.u.

Falls zwischen open und close ein Fehler passiert, durch den das Programm abgebrochen wird, wird die Datei u.U. nicht richtig geschlossen. Um das zu vermeiden, gibt es folgenden Mechanismus, den man sich angewöhnen sollte:

with open('datei.txt') as f:
    for line in f:
        # ...

In Dateien schreiben

Um in eine Datei zu schreiben, muss beim Öffnen das Argument 'w' angegeben werden (Vorsicht: eine Datei, die bereits existiert, wird so durch eine leere Datei ersetzt).

Am einfachsten schreibt man zeilenweise in die Datei, indem man die geöffnete Datei an die print-Funktion übergibt:

with open('datei.txt', 'w') as f:
    print('hallo', file=f)

Um an eine bestehende Datei anzuhängen, kann man 'a' anstatt 'w' benutzen. (Analog zu den Umleitungsoperatoren >> und > in der Shell.)