Vorlesungsskript 17

Einfache CLIs und TUIs mit Python

Ein Programm mit einem gut gestalteten Benutzerinterface ist leichter und angenehmer zu benutzen. Das gilt nicht nur für grafische, sondern auch für textbasierte Benutzerinterfaces. Ein Programm mit einem guten Kommandozeileninterface (CLI) erlaubt es der Benutzerin zum Beispiel, den Input wahlweise über den Standard-Input oder in Dateien zur Verfügung zu stellen. Und mit Hilfe von textbasierten Benutzerinterfaces (TUIs) lassen sich einfache interaktive Programme realisieren. In diesem Vorlesungsskript lernen Sie einige Werkzeuge kennen, die Python zur Verfügung stellt, um Ihre Programme mit guten CLIs und TUIs zu versehen.

Kommandozeilenargumente verarbeiten

Bisher haben wir alle unsere Python-Programme ohne Argumente aufgerufen. Ihr Output wurde ausschließlich von den Daten beeinflusst, die wir den Programmen über den Standard-Input mitgegeben haben oder die sie aus Dateien gelesen haben. Aber so wie die Unix-Standardprogramme können auch Python-Programme Kommandozeilenargumente verarbeiten, die ihr Verhalten beeinflussen.

Für diesen Zweck gibt es im Modul sys die Variable argv (für argument values). Sie enthält eine Liste mit den Kommandozeilenargumenten, die der Benutzer eingegeben hat. Am Index 0 steht dabei der Name des Python-Programms selbst, an den folgenden Indizes die eigentlichen Argumente. Hier ist ein Testprogramm, myprog.py, das das demonstriert:

import sys

print(sys.argv)

Wenn wir es von der Kommandozeile aus aufrufen, gibt es die Argumentliste aus:

$ python3 myprog.py
['myprog.py']
$ python3 myprog.py myfile.txt
['myprog.py', 'myfile.txt']
$ python3 myprog.py dog cat mouse
['myprog.py', 'dog', 'cat', 'mouse']

Wir werden sys.argv nun benutzen, um unser Beispielprogramm linenum.py zu verbessern. In der vorigen Version hatte es seinen Input immer aus der Datei moby-dick.txt gelesen. Nun erwartet es stattdessen einen Pfad zu einer Datei als Argument und liest den Input dann aus der angegebenen Datei:

import sys

infile = open(sys.argv[1], 'r')
i = 0
for line in infile:
    i += 1
    print(i, line, end='')
infile.close()

So kann eine Benutzerin es aufrufen:

$ python3 linenum.py moby-dick.txt | tail
23856       And King of the boundless sea."
23857   --WHALE SONG.
23858 
23859 
23860 
23861 
23862 
23863 End of The Project Gutenberg Etext of Moby Dick, by Herman Melville
23864 
23865 
$ python3 linenum.py alice-in-wonderland.txt | tail
3844 she would keep, through all her riper years, the simple and
3845 loving heart of her childhood:  and how she would gather about
3846 her other little children, and make THEIR eyes bright and eager
3847 with many a strange tale, perhaps even with the dream of
3848 Wonderland of long ago:  and how she would feel with all their
3849 simple sorrows, and find a pleasure in all their simple joys,
3850 remembering her own child-life, and the happy summer days.
3851 
3852                              THE END
3853 

Wir können das Programm auch mit Hilfe einer Schleife in die Lage versetzen, mehrere Dateien als Argumente zu nehmen und zu lesen:

import sys

i = 0
for path in sys.argv[1:]:
    infile = open(path, 'r')
    for line in infile:
        i += 1
        print(i, line, end='')
    infile.close()

So kann ein Benutzer das Programm jetzt aufrufen:

$ python3 linenum.py moby-dick.txt alice-in-wonderland.txt | tail
27709 she would keep, through all her riper years, the simple and
27710 loving heart of her childhood:  and how she would gather about
27711 her other little children, and make THEIR eyes bright and eager
27712 with many a strange tale, perhaps even with the dream of
27713 Wonderland of long ago:  and how she would feel with all their
27714 simple sorrows, and find a pleasure in all their simple joys,
27715 remembering her own child-life, and the happy summer days.
27716 
27717                              THE END
27718 

Variabler Input mit fileinput

Wir können der Benutzerin auch die Wahl geben, ob sie als Input einen oder mehrere Dateipfade oder den Standard-Input angeben möchte, so wie viele Standardprogramme das tun. Dazu steht im Modul fileinput die Funktion input zur Verfügung. Diese Funktion prüft selbständig, ob Argumente übergeben wurden. Wenn ja, liest sie die entsprechenden Dateien und gibt ein Iterable über deren Zeilen zurück. Wenn nein, liest sie den Standard-Input und gibt ein Iterable über dessen Zeilen zurück. Somit können wir unser Programm wie folgt umschreiben:

import fileinput

i = 0
for line in fileinput.input():
    i += 1
    print(i, line, end='')

Jetzt kann die Benutzerin dem Programm seinen Input auf beide Weisen übergeben: entweder per Standard-Input (z.B. mit einer Pipe) oder als Datei(en):

$ cat moby-dick.txt alice-in-wonderland.txt | python3 linenum.py | tail
27709 she would keep, through all her riper years, the simple and
27710 loving heart of her childhood:  and how she would gather about
27711 her other little children, and make THEIR eyes bright and eager
27712 with many a strange tale, perhaps even with the dream of
27713 Wonderland of long ago:  and how she would feel with all their
27714 simple sorrows, and find a pleasure in all their simple joys,
27715 remembering her own child-life, and the happy summer days.
27716 
27717                              THE END
27718 
$ python3 linenum.py moby-dick.txt alice-in-wonderland.txt | tail
27709 she would keep, through all her riper years, the simple and
27710 loving heart of her childhood:  and how she would gather about
27711 her other little children, and make THEIR eyes bright and eager
27712 with many a strange tale, perhaps even with the dream of
27713 Wonderland of long ago:  and how she would feel with all their
27714 simple sorrows, and find a pleasure in all their simple joys,
27715 remembering her own child-life, and the happy summer days.
27716 
27717                              THE END
27718 

Interaktive Eingaben mit input

Die eingebaute Funktion input (nicht zu verwechseln mit fileinput.input) ermöglicht es, Eingaben vom Benutzer abzufragen, während das Programm bereits läuft. Hiermit lassen sich also interaktive Programme mit TUIs (text user interfaces) schreiben.

Die Funktion input wird mit einem String als Argument aufgerufen. Diesen benutzt sie als „Prompt“, um die Benutzerin zu einer Eingabe aufzufordern. Dann wartet input darauf, dass die Benutzerin etwas eingibt und Enter drückt. Schließlich gibt input die Eingabe als String zurück.

Hier ist ein Beispielprogramm (add.py), das die Benutzerin um die Eingabe zweiter Zahlen bittet und diese dann addiert:

number1 = input('Please enter a number: ')
number1 = int(number1)
number2 = input('Please enter another number: ')
number2 = int(number2)
total = number1 + number2
print('The sum is:', total)

So wird es benutzt:

$ python3 add.py
Please enter a number: 2 
Please enter another number: 3
The sum is: 5

while-Schleifen

Interaktive Programme bitten den Benutzer häufig mehrfach um eine Eingabe – in vielen Fällen immer wieder, so lange, bis der Benutzer das Programm beendet. Beispiele sind die Unix-Shell und der Python-Interpreter: beide zeigen immer wieder ein neues Prompt an und erwarten eine neue Eingabe, bis der Benutzer sie beendet.

In Python können wir dieses Verhalten mit while-Schleifen umsetzen. Wie for-Schleifen sind while-Schleifen Blockanweisungen mit einem Kopf und einem Körper, der mehrfach ausgeführt wird. Der Kopf einer while-Schleife lautet im einfachsten Fall wie folgt:

while True:

Der Körper einer solchen Schleife wird immer wieder wiederholt, bis die Anweisung break ausgeführt wird.

Hier ist ein Beispielprogramm (even.py), das den Benutzer wiederholt um die Eingabe einer Zahl bittet und anschließend ausgibt, ob die Zahl gerade oder ungerade ist:

while True:
    number = input('Please enter an integer: ')
    if not number:
        break
    number = int(number)
    if number % 2 == 0:
        print(number, 'is even.')
    else:
	print(number, 'is odd.')

Das sieht dann zum Beispiel so aus:

$ python3 even.py
Please enter a number: 3
3 is odd.
Please enter a number: 16
16 is even.
Please enter a number:

Die Wiederholung ist mit einer while-Schleife umgesetzt. Im Körper dieser Schleife wird zunächst die Benutzerin um die Eingabe einer Ganzzahl gebeten. Gibt sie nichts ein, gibt die input-Methode den leeren String zurück. Das wird mit Hilfe der folgenden Verzweigung geprüft. Falls nichts eingegeben wurde, wird die Schleife abgebrochen. Ansonsten wird die Ausführung des Schleifenkörpers fortgesetzt, es wird ausgegeben, ob die Zahl gerade oder ungerade ist, und dann beginnt die Ausführung des Schleifenkörpers wieder von vorn.

Das True in while True: ist eine Bedingung. Vor jeder Ausführung des Schleifenkörpers wird diese Bedingung geprüft. Gibt sie einen „falsy“ Wert zurückt, wird die Schleife abgebrochen.

Man kann hier also auch eine komplexere Bedingung angeben, die irgendwann falsch wird, und so die Schleife abbrechen. break-Anweisungen sind jedoch flexibler, da sie die Schleife an einer beliebigen Stelle im Körper abbrechen können und nicht nur am Anfang. Im Prinzip können daher alle while-Schleifen mit while True: beginnen.

Weiterführende Lektüre