23. Docstrings

Wir haben sowohl in der compute-Funktion als auch später im Modul compute einen docstring verwendet.

def compute(expr, langDict):
  """computes a pseudo-natural-language expression.

  The expression is passed in the string expr,
  langDict provides a mapping of single words to
  python values and operators.
  """

Ein String, der gleich hinter einem Funktionskopf kommt, wird von Python als Dokumentation der Funktion verstanden und sollte beschreiben, was die Funktion tut, am besten als Verbphrase (Funktionen sollen etwas “tun”).

Das empfohlene Format ist: Drei öffnende Anführungszeichen, ein kurze Beschreibung in einem Satz, eine Leerzeile, weitere Bemerkungen (z.B. Beschreibung der Argumente), abschließend drei Anführungszeichen in einer eigenen Zeile.

Docstrings können zur Laufzeit ausgewertet werden:

>>> print compute.__doc__
  computes a pseudo-natural-language
...
>>> print eval.__doc__
eval(source[, globals[, locals]]) -> value
...

Es gab und gibt etliche Versuche, in Docstrings etwas strukturiertere Information unterzubringen, aus der z.B. Dokumentationsgeneratoren fertige Programmdokumentation erzeugen können. Das “offizielle” pydoc übernimmt die Docstrings allerdings ziemlich wörtlich.

In der Python-Distribution enthalten ist das Modul doctest, das es erlaubt, in docstrings Beispiele für eine korrekte Operation der Funktion einzubetten und – das ist der Kick – das Python-System nachprüfen zu lassen, ob die Funktion auch wirklich tut, was die Beispiele behaupten.

(Weiterführend:) Inspiriert ist das von einer Software Engineering-Technik namens Extreme Programming, die Unit Tests (d.h. einzelne Einheiten des Programms werden getrennt getestet, bis man überzeugt ist, dass sie korrekt arbeiten) und Regression Tests (d.h. man überzeugt sich möglichst oft, dass das Programm nach Änderungen nicht neue Fehler bekommen hat und zumindest das funktioniert, was vorher funktioniert hat) betont. Doctests erlauben die Kombination dieser Techniken mit der dokumentierenden Kraft von Beispielen. Für große und komplizierte Unit Tests gibt es ein eigenes Modul.

Idee von doctest ist im Wesentlichen, einen interaktiven Dialog mit Python in den Kommentar zu schreiben, sowohl Ein- als auch Ausgabe. Der Interpreter kann dann später veranlasst werden, nachzuprüfen, ob dieser Dialog so möglich wäre.

Für unser Beispiel könnte so etwas folgendermaßen aussehen:

def compute(expr, langDict):
  """computes a pseudo-natural-language expression.

  The expression is passed in the string expr,
  langDict provides a mapping of single words to
  python values and operators.

  Examples:
  >>> langDict = {'eins': '1', 'plus': '+', 'zwei': '2'}
  >>> compute("eins", langDict)
  1
  >>> compute("eins plus zwei plus eins", langDict)
  4
  >>> compute("eins plus", langDict)
  Traceback (most recent call last):
  SyntaxError: unexpected EOF while parsing
  """
  pythonExpression = []
  for w in expr.split():
    pythonExpression.append(langDict[w])
  return eval("".join(pythonExpression))

– die oben zitierte Dokumentation von doctest gibt ein paar Tips, was “gute” Tests sind und was nicht. Um Python dazu zu bringen, diese Tests auch durchzuführen (von selbst tut es das nämlich nicht), muss man folgender Schablone folgen:

def _test():
  import doctest, compute
  return doctest.testmod(compute)

Man muss also sowohl das doctest-Modul als auch “sich selbst” importieren (was man besser nur in einer Funktion tut, auch wenn Python schlau genug ist, ein globales import des eigenen Moduls ohne Endlosschleife zu verarbeiten). Der eigentliche Test geschieht dann durch Aufruf der Funktion testmod aus dem doctest-Modul mit dem zu testenden Modul als Argument. Die Funktion _test kann man dann nach Geschmack z.B. beim Modulimport laufen lassen (was aber normalerweise nicht empfehlenswert ist) oder, wenn das Modul als Programm ausgeführt wird. Letzteres (was ich für alle Module empfehle, die nicht direkt als Programm ausgeführt werden sollen) lässt sich so erreichen:

if __name__=="__main__":
  _test()

Ändert man dann etwas im Modul, lässt man den Interpreter das Modul einfach ausführen und sieht bei entsprechend geschickt geschriebenen Doctests auch gleich, ob eine Änderung vielleicht ganz woanders etwas kaputt gemacht hat.

Um zu sehen, was passiert, ruinieren wir den Doctest in compute, der einen SyntaxError auslösen soll:

  >>> compute("eins plus")

(wir haben das zweite Argument gelöscht) und probieren das mal:

examples> python compute.py
*****************************************************************
Failure in example: compute("plus eins")
from line #11 of compute.compute
Exception raised:
Traceback (most recent call last):
  File "/usr/local/lib/python2.3/doctest.py", line 442, in
    _run_examples_inner compileflags, 1) in globs
  File "<string>", line 1, in ?
TypeError: compute() takes exactly 2 arguments (1 given)
*****************************************************************
1 items had failures:
   1 of   5 in compute.compute
***Test Failed*** 1 failures.

Python hat uns gleich ertappt. Wir bringens in Ordnung und ergänzen in einem Editor wieder das zweite Argument:

  >>> compute("eins plus", langDict)

Nochmal versuchen:

examples> python compute.py
examples>

– prima, der Rechner beschwert sich nicht mehr. Wenn man trotzdem sehen will, wie all die Tests erfolgreich laufen, kann man dem Skript die Option -v übergeben:

examples> python compute.py -v
Running compute.__doc__
0 of 0 examples failed in compute.__doc__
Running compute._test.__doc__
... viele Zeilen ...
4 passed and 0 failed.
Test passed.

Übungen zu diesem Abschnitt

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

(1)

Seht durch, was ihr schon alles an Funktionen geschrieben habt. Schreibt docstrings für sie und nach Möglichkeit auch doctests.


Markus Demleitner

Copyright Notice