Tim Tepaße: (Python) Zuweisung in IF

Beitrag lesen

Hallo Harlequin,

Wie kann ich trotzdem so etwas in der Art elegant lösen:

Eleganz bedeutet manchmal für unterschiedliche Leute unterschiedliche Dinge. Manchmal ist es möglich nah dran am konkreten Ablauf der Dinge dran zu sein wie in Deiner Schleife, manchmal bedeutet es, das eigene Denken im Programmcode wiedergespiegelt zu sehen. Ich neige in den letzten Jahren instinktiv immer zu einem etwas funktionaleren Programmierstil. Python kommt mir da sehr entgegen, auch mit den nicht ganz so funktionalen Bestandteilen wie Generatoren. Aber das ist eine Sichtweise von Eleganz in Python; es gibt auch andere.

Ich würde so vorgehen:

• Du hast offenkundig einen Iterator, der Zeilen ausspuckt.
• Für jede dieser Zeilen möchtest Du eine _Liste_ von Mustern ausprobieren.
• Vom ersten Muster aus dieser Liste, das passt, möchtest Du bestimmte _benannte Submuster_ zurück geben.

Um das ganze mal umgekehrt aufzubauen: Das erste Stichwort ist „benanntes Submuster“. Man kann ja in Regexps mit (?P<name>...) Gruppen benennen. Das kann man toll nutzen, in Deinem zweiten Regexp z.B. so:

re.compile(r'^\s*Text [(?P<text>\w*)] (?P<content>\w)*\s*{\w*$')

Das Schöne daran ist, dass man aus dem Match-Object mit der Methode m.groupdict() ein Dictionary mit den gefundenen Sub-Mustern herausbekommt. Beachte auch das r vor dem Regexp-String. Das zeigt an, dass der String ein sogenannter Rawstring ist, was heisst, dass in diesem String Maskierungen mit dem Backslash nicht interpretiert werden. Man erspart sich dadurch die Verdoppelung der Backslashs; Rawstrings sind also sehr für Regexps beliebt.

Zweiter Punkt: Du hast eine „Liste“ von Suchmustern. Es macht oftmals mehr Spaß, tatsächlich eine Liste zu benutzen als die verschiedenen gleichen Teile an unterschiedlichen Quellen im Code zu verstreuen. Unter anderem, weil man auf dieser Liste mit anderem Code operieren kann. Mal beispielhaft:

~~~python patterns = [
    ('string', r'^\sString (?P<identifier>\w)\s*{\w*$'),
    ('text'  , r'^\sText [(?P<text>\w)] (?P<content>\w)\s{\w*$'),
    ('...'   , r'...')
  ]

  
Ich hab hier eine Liste von Zweier-Tupeln, das erste Element ist ein kurzes Label, das zweite das Suchmuster. Das Label ist eigentlich unnötig; ich pack's trotzdem mal rein zur Selbstdokumentation und zum anderen, falls man das Label später noch mal braucht, z.B. als der erkannte Typ der zum Suchmuster passenden Zeile. Ich verwandel diese Datenstruktur gleich mal daraufhin mit einer list comprehension etwas Praktischeres:  
  
  `patterns = [(label, re.compile(pattern)) for label, pattern in patterns]`{:.language-python}  
  
Warum? Kompilierte Regexps sind etwas schneller, also sollte man gleich am Anfang das kompilieren, besonders wenn man sie öfter verwendet. Ist aber nervig zu tippen, deswegen als zweiter, kleinerer Schritt, der die Liste nur in diesem Aspekt verändert bzw. hier neu erstellt. Die Verwendung der Datenstruktur lohnt sich schon allein aus tipp-ökonomischen Gründen. ;)  
  
Jetzt kommt der spassige Teil, eine Funktion, die eine Zeile Text bekommt, der Reihe nach die Suchmuster drauf anwendet und beim ersten passenden Muster deren gefundene Submuster als Dictionary zurück gibt:  
  
  ~~~python
def first_match(line):  
      for label, regex in patterns:  
          match = regex.search(line)  
          if match:  
              return (label, match.groupdict())  
      # Und wenn wir nix finden?  
      else:  
          raise SomeErrorThingy

Die Fehlerbehandlung sollte natürlich ausgebaut werden. ;) Ich geb hier wieder ein Tupel mit dem Label zurück, man weiß ja nie, ob man's nicht noch brauchen kann.

Der Rest ist klassisch. Man kann es so verwenden ...

~~~python for line in open("file.txt"):
      label, data = first_match(line)
      doSomething()

  
... oder so für eine schöne Liste der herausgeholten Daten ...  
  
  `data = map(first_match, open("file.txt"))`{:.language-python}  
  
... oder etwas unnötigerweise ausführlicher so ...  
  
  ~~~python
def parse(filepath):  
      with open(filepath) as f:             # Python 2.5  
          for label, data in itertools.imap(first_match, f):  
              yield (label, data)  
      raise StopIteration  
  
  for label, data in parse("file.txt"):  
      yadayadayada(data)

(Für ein anderes, fast gleiches aber schöneres Beispiel siehe Chapter 17: Dynamic functions aus Mark Pilgrims sehr empfehlenswerten freiem Buch „Dive into Python“.)

Tim

--
Ich hab's nicht getestet; vermutlich ist irgendwo ein Bug. Siehe Uhrzeit.