28. Tupel

Wir bauen unseren Regelleser mit Exceptions:

def readRules(inFileName):
  rules = {}
  for rawRule in open(inFileName).readlines():
    try:
      key, val = [s.strip() for s in
        rawRule.split("->")]
      try:
        rules[key].append(val)
      except KeyError:
        rules[key] = [val]
    except ValueError:
      sys.stderr.write("Syntaxfehler in "+
        rawRule.strip()+"\n")
  return rules

Anmerkung: Das try-except-Konstrukt um den Eintrag im Dictionary herum ist hier nur zur Illustration. Wenn man (wie hier) damit zu rechnen hat, dass eine Exception in der Regel ausgelöst wird und nur im Ausnahmefall (“as an exception”) nicht ausgelöst wird, ist es besser, den “Ausnahmefall” manuell zu behandeln (oder eben wie in der Vorfassung von readRules Konstrukte zu verwenden, die den Ausnahmefall gleich korrekt behandeln).

Was tut die Zeile mit key, val = ...?

Die rechte Seite werden wir uns auf der nächsten Folie ansehen – zunächst kommt da normalerweise eine zweielementige Liste raus:

>>> [s.strip() for s in
...         "a -> b".split("->")]
['a', 'b']

Tupel

Ein Tupel ist eine nichtveränderbare Sequenz; in Python wird sie mit Kommata getrennt und optional in Klammern geschrieben. Tupeln kann man auch zuweisen, wobei automatisch ein- und ausgepackt wird, auch aus anderen Sequenzen:

>>> b = (2+3, 4*3)
>>> z1, z2 = b
>>> z1, z2
(5, 12)
>>> z1, z2 = "ab"
>>> z1, z2
('a', 'b')
>>> t = ("a",)
>>> t, ()
(('a',), ())

In den lezten beiden Zeilen steht ein einelementiges Tupel und das leere Tupel.

Wenn man versucht, Tupel falscher Größe zu entpacken, kommt ein ValueError – eben der, der oben als Syntaxfehler abgefangen wird:

>>> z1, z2 = [1, "zwei", ()]
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ValueError: unpack list of wrong size

Warum Tupel?

Ein Tupel ist also eine inhomogene Sequenz (es können also Werte verschiedener Typen in ihm stehen). Damit unterscheidet es sich nicht wesentlich von Listen. Warum also nicht gleich Listen nehmen?

Der Unterschied ist technisch nur die Unveränderbarkeit. Sie ist für den Interpreter von Vorteil, weil er so nicht damit rechnen muss, dass sich die Daten plötzlich ändern. Wann immer sich die Daten in einer Sequenz nicht ändern werden (z.B. bei den Zuweisungen, aber auch bei der items-Methode von Dictionaries), setzt Python Tupel ein (es sei denn, der/die AutorIn des betreffenden Codes hätte Mist gebaut).

(Weiterführend:) Guido van Rossum, immer noch Spiritus Rector Pythons (der Fachbegriff lautet BDFL, Benevolent Dictator For Life), besteht darauf, dass die Vorstellung, Tupel seien einfach unveränderbare Listen, irreführend ist. Seine Einlassung ist, dass Listen für homogene Daten (also Sequenzen gleichen Typs), Tupel dagegen für inhomogene Daten (“records”, wenn man so will) verwendet werden sollten. Diese Auskunft (vgl. etwa die FAQ dazu) ist allerdings nicht unumstritten. Meine persönliche Meinung dazu ist, dass die radikale Ablehnung der Verwendung von Tupeln als immutable lists wohl etwas zelotisch ist, dass am aber wohl gut daran tut, die Meinung des BDFL wenigstens im Kopf zu haben.

Durch das automatische Ein- und Auspacken ergeben sich einige nette syntaktische Möglichkeiten. So kann man bequem “mehrere Werte” aus einer Funktion zurückgeben:

>>> def analyze(verb):
...     numerus = {"bist": "sing", "seid": "plur", "sind": "plur"}
...     person = {"bist": 2, "seid": 2, "sind": 3}
...     return numerus[verb], person[verb]
...
>>> num, pers = analyze("bist")
>>> print num
sing
>>> num, pers = analyze("seid")
>>> print pers
2

Natürlich würde man eine morphologische Analyse in Wirklichkeit eher nicht über hartkodierte Dictionaries machen, die Schnittstelle der Funktion könnte aber durchaus so aussehen.

Das geht sogar bei Schleifen:

>>> def enumerate(seq):
...     res = []
...     for i in range(len(seq)):
...             res.append((i, seq[i]))
...     return res
...
>>> for index, word in enumerate("hallo welt".split()):
...     print "%s ist an %d-ter Stelle"%(word, index+1)
...
hallo ist an 1-ter Stelle
welt ist an 2-ter Stelle

Die Funktion enumerate mit der von uns verwendeten Schnittstelle ist übrigens in neueren Pythons bereits eingebaut – allerdings in einer etwas effizienteren Implementation, die den Aufbau einer eigenen Liste (die Zeit und Speicher in Anspruch nimmt) vermeidet. Wie sowas geht, werden wir später sehen.

items

Wie oben schon angekündigt, gibt die items-Methode von Dictionaries eine Liste von Tupeln zurück. Mensch könnte erwarten, dass man aus so einer Liste von Tupeln dann auch wieder ein Dictionary machen könnte. Das ist tatsächlich so (der Ehrlichkeit halber sollte gesagt werden, dass es beliebige Sequenzen der Länge zwei statt der Tupel auch tun würden):

>>> dict([(1, "eins"), (2, "zwei"), (3, "drei")])
{1: 'eins', 2: 'zwei', 3: 'drei'}

Hier ist das natürlich nicht so interessant, aber es ist bequem, wenn man z.B. Attribut-Wert-Paare folgendermaßen in einer Datei gespeichert hat:

total: 27
adjs: 2
noms: 12
verbs: 8
parts: 5

In aller Kürze bekommt man sowas in ein Dictionary durch eine Funktion wie

import string

def readDict(fName):
  """reads the contents of fName and interprets each line as
  colon-separated attribute-value pair.

  Returns this mapping as a dictionary.  No error checking, will
  raise all kinds of funky exceptions if you mess anything up.
  """
  def parseLine(ln):
    return tuple([s.strip() for s in  ln.split(":")])
  return dict(
    [parseLine(ln) for ln in
      open(fName).readlines()])

Anmerkung: Ja, es ist legal, innerhalb von Funktionen weitere Funktionen zu definieren. Diese “gibt” es dann auch nur innerhalb der Elterfunktion.

Das automatische Ein- und Auspacken bringt übrigens mit sich, dass die bequemste Art, über Schlüssel und Werte eines Dictionaries zu iterieren, so aussieht:

>>> d = {1: "eins", 2: "zwei", 27: "siebenundzwanzig"}
>>> for num, word in d.items():
...     print "%d=%s"%(num, word)
...
1=eins
2=zwei
27=siebenundzwanzig

Beachtet die items-Methode – wenn eure Dictionaries größer werden, empfiehlt sich hier übrigens die Verwendung der iteritems-Methode, die ähnlich zu items funktioniert, aber wieder den Aufbau einer temporären Liste vermeidet.

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Eine Konsequenz der Veränderbarkeit von Listen ist, dass sie nicht als Schlüssel in Dictionaries taugen (warum?). Probiert, ob Tupel Schlüssel in Dictionaries abgeben.


Markus Demleitner

Copyright Notice