Programmieren mit Python 3

Ein Tischrechner (Kommandozeilenversion)

Nachdem Sie jetzt ein wenig programmieren können, wollen wir mal ein "richtiges" Programm erstellen, dass etwas macht, was wir nicht so einfach im Kopf nachvollziehen können: Einen Tischrechner (Taschenrechner).

Was soll unser Rechner können? Er soll normale mathematische Ausdrücke wie:

"12+3*(3+1)" berechnen können, also nach mathematischen Regeln einen Ausdruck berechnen (Punkt vor Strich beachten, Klammern kommen zuerst, usw.).

Exercise: Was ist das Ergebnis des obigen Ausdrucks?

Bislang haben wir nur App(likationen)s mit grafischer Oberfläche erstellt. Apps sind übrigens einfach Programme - Apple hat sich da einen tollen Namen dafür ausgedacht. Jetzt erstellen wir mal ein Programm, wie man es früher (auch auf Apple-Computern) erstellt hat, nämlich für die Eingabeaufforderung. So heiß es unter Windows. Unter UNIX nennt man es Shell, und davon gibt es viele (die alle eine minimal andere Syntax haben), die bekanntest ist wohl die BASH (Bourne Again Shell), die durch Linux (ein UNIX-Derivat) berühmt geworden ist. Es gibt aber noch viele andere, wie die klassische "Bourne-Shell" (mit der möchten Sie heutzutage nicht mehr arbeiten), die "C-Shell", "Korn-Shell", ...

Warum schreibt man überhaupt ein Programm für die "Kommandozeile" (so werde ich diese Art Programe ab jetzt nennen), wenn man doch (das haben sie bereits gesehen) so einfach Programme mit grafischer Oberfläche schreiben kann? Weil man damit teilweise (NICHT IMMER) sehr viel schneller ein kleines Programm (Apps heißen in dieser Einführung ab sofort Programm) schreiben kann, dass die gewünschte Aufgabe erledigt. Aber vor allem weil Sie ganz einfach überall eine "print(...)-Funktion" (in Python 2 war print noch eine Python-Anweisung [also ein Python-Schlüsselwort], d. h. konnte ohne die umschließenden Klammern verwendet werden.) einfügen können, mit der sie sehr einfach mitten in Ihren normalen Program-Ausgaben Kontroll-Ausgaben einfügen können. Das ist sehr nützlich, um Fehler zu finden. Bei GUI-Anwendungen ist das deutlich aufwendiger. Ein Kommandozeilenprogramm sollten Sie aber (unter Windows) nicht mit der Endung .pyw speichern, sondern mit .py, dann können Sie es direkt durch Eingabe des Namens z. B. calc00.py starten. Ansonsten müssen Sie "PYTHON calc00.py" verwenden. Programme mit grafischer Oberfläche (GUI) speichern sie nur unter Windows(!) mit der Endung ".pyw" ab Das 'w' in ".pyw" zeigt unter Windows an, dass es ein Programm für die grafische Oberfläche ist. Bei UNIXoiden Betriebssystemen ist dies (falls der Shebang richtig angegeben ist) nicht notwendig, hier speichern Sie Python-Programme immer mit Endung ".py" ab.

Eine Kommandozeile erhalten Sie unter Windows, indem Sie den „Start” Button (unten links) drücken und dann "Eingabeaufforderung" eingeben. Bereits nach den ersten Zeichen sollte im Menü oben "Eingabeaufforderung" erscheinen. Klicken Sie darauf

Hier zunächst wieder das Programm, dass obige Anforderungen an unseren Tischrechner erfüllt. Kommentare sind ab sofort in Englisch. Da man bei umfangreicheren Programmen häufig auch fremde Quelltexte (Sourcecode) benutzt, deren Kommentare zumeist in Englisch sind, hat sich der Autor angewöhnt, Kommentare generell in Englisch zu schreiben. Das halten sehr viele (auch deutschsprachige) Softwareentwickler so.

1  2  3  4  5  6  7  8  9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  #!/usr/bin/python3 # Simple calculator from math import * # For a real calcualtor we need all mathematical functions import sys # needed to determine the smallest floating point number _eval__=eval # make an alternative name for calling the eval(...) function _type__=type # make an alternative name for calling the type(...) function _input__=input # make an alternative name for calling the input(...) function def _main__(): print("Simple expression calculator") print("Enter an expression like 5+3*12 - ** is the Power Off operator.") print('Enter "bye" (without the quotes) to quit') while (True): _expr__=input("calc>") if (_expr__.lower() == "bye" or _expr__ == "."): break _rslt__, _err__=_calcExpr__(_expr__) if (_err__ != ""): print("Error:",_err__) else: print(_rslt__) def _calcExpr__(_expression__): # implements expression parser with error checking try: _err__="" _rslt__=_eval__(_expression__) # An "Expression Parser" in one line(!) except: _rslt__=-sys.float_info.min # We return this if an error occures _err__="Invalid expression" return (_rslt__,_err__) # we return a tupel (2 values at once :-)) _main__() # run our App(lication)

An den Zeilen 8 bis 10 werden Sie sich zunächst mal stören - was soll dass? Hierzu müssen Sie wissen, dass sie in Python Namen, die in Python vordefiniert sind, mit Ihren eigenen Namen überschreiben können(! - dies gilt aber nicht für Keywords [Schlüsselwörter] wie if, while, usw.). Aber z. B "math.pi" (normalerweise der Wert der Kreisvariablen PI) können Sie auf einen anderen Wert setzen. Das funktioniert aber genau so mit eingebauten Python-Funktionen wie eval(...) oder type(...) - diese Funktionen werden später erklärt, bzw. sie erkennen die Funktion anhand des Quellcodes.

Da wir in diesem Programm später noch eine "große Schweinerei" machen werden, bei der unter Umständen die Namen dieser (für dieses Programm) extrem wichtigen Funktionen überschrieben werden können, sichern wir diese Funktionen unter einem anderen Namen, den hoffentlich kein Benutzer später verwenden wird. Und rufen sie später auch mit diesen Namen auf (also nicht:)

rslt=eval(3*4)

sondern: _rslt__=_eval__(3*4)

Falls Sie das jetzt nicht verstehen, "schlucken" Sie es einfach. Im Normalfall machen Sie solche "Schweinereien" NICHT. Ich möchte Ihnen hier ein Programm vorstellen, dass ähnlich wie mein "Coca" funktioniert (der aus sehr vielen Dateien und sehr viel mehr Zeilen Code besteht), aber in viel weniger Zeilen Code realisiert wird (Coca ist in C++ geschrieben, was die Möglichkeiten von Python nicht bietet).

Nächster Trick: Wie ich vorher erwähnt habe, müssen alle Funktionen vor dem Hauptprogramm kommen. Das sieht nicht gut aus, normalerweise programmiert man Top Down. D. h. man fängt mit dem grundsätzlichen an (das ist meist sehr einfach) und arbeitet sich nach unten durch (da wird es fast immer kompliziert). Daher habe ich eine Funktion _main__(...) angelegt, die das Hauptprogramm ist, danach werden alle Funktionen definiert, die wir benötigen. Das eigentliche Hauptprogramm (ganz am Ende) ruft dann einfach unsere "main"-Funktion auf (die hier - Sie erinnern sich an "die Schweinerei", die ich hier machen werde) _main__ heißt.

Unsere "main(...)"-Funktion (die hier _main__(...) heißt:

Die ersten 3 Zeilen sind einfach Ausgabeanweisungen, die eine kurze Erklärung für den Benutzer zur Bedienung des Programms ausgeben: print(...)

Zeilen 8 bis 10 habe ich bereits oben erklärt.

Jetzt benötigen wir zum ersten mal ein "Kontroll-Element"/"Kontroll-Struktur".

Bei der grafischen Oberläche von den vorhergehenden Programmen, übernimmt das Betriebssystem (z. B. Windows, Linux, ...) die Steuerung des Programmablaufs. Bei der Kommandozeile müssen wir das selbst machen.

Unser Taschenrechner soll so lange Ausdrücke berechnen, bis wir "bye" oder (Kurzfassung) einen Punkt (.) eingeben. Dann soll das Programm beendet werden.

Dies können wir mittels einer Schleifenanweisung wie:

while (Bedingung == True)

erreichen (Zeile 17). Da „True” ein Python Schlüsselwort ist, müssen wir nicht die komplette Bedingung eingeben. Es langt while (True): (der Doppelpunkt am Ende ist wichtig!) Alles was danach eingerückt ist, wird innerhalb dieser Schleife ausgeführt. Da die Abbruchbedingung (True) immer erfüllt ist (True ist), ist dies eine Endlosschleife. Beachten Sie, dass in Python der Operator, der auf Gleichheit zweier Werte prüft "==" ist und nicht "=" wie in BASIC. Die Schleife wird abgebrochen, indem der Benutzer "bye" (ohne die Anführungszeichen - das werde ich zukünftig nicht mehr erwähnen) oder einen Punkt (.) eingibt (siehe Zeile 18 in der "input(...)-Anweisung" und den anschließenden Test in Zeile 19 [siehe weiter unten]), wenn der wahr (True) ist wird die Schleife abgebrochen mit dem Python Schlüsselwort break. Die Funktion input("calc>") gibt zunächst den übergebenen String-Ausdruck "calc>", den Eingabeprompt, aus, wartet auf die Eingabe des Benutzers, und gibt diese Eingabe als String zurück.

Die if (...) Anweisung in Zeile 19 (das ".lower()" in der Testbedingung dieser Anweisung wandelt die Benutzereingabe [_expr__] in Kleinbuchstaben um - sonst müssten wir auf "Bye", "BYE", "bye", ... testen) testet den Ausdruck in den Klammern. Falls dieser wahr ist, wird der nachfolgende, eingerückte Block ausgeführt, sonst nicht. Der eigentliche Abbruch wird durch das Keyword "break" in Zeile 20 erreicht. "break" bricht die aktuell ausgeführte (innerste) Schleife ab. Dadurch erreicht unser Programm das Ende der Funktion _main__(), wodurch diese Funktion zurückkehrt, und mit der Zeile nach dem Aufruf (in Zeile 36) weitermacht. Da hier nichts mehr kommt, wird das Programm beendet.

Falls der Benutzer nicht "bye" oder "." eingegeben hat, fährt unser Programm mit der Zeile nach dem "if"-Block fort (Zeile 21). Dort wird einfach die Funktion _calcExpr__ in Zeile 27 aufgerufen (diese Funktion wird gleich unten erklärt), die das Ergebnis des eingegebenen Ausdrucks zurückgibt, bzw. einen Fehler, falls der mathematische Ausdruck nicht korrekt war. Die Funktion gibt ein sogenanntes Tupel zurück. Ein Tupel kann mehrere Werte (auch unterschiedlichen Typs) enthalten. Diese Werte kann man einzelnen Variablen in einer einzigen Anweisung zuweisen. z. B. myTupel=(3.14, "Die Antwort...", 42). Dieses Tupel können wir jetzt mit folgender Anweisung:

flt, quest, ans = myTupel

zuweisen. Danach is flt ist eine Gleitpunktzahl mit Wert 3.14, quest eine String-Variablen mit dem Inhalt "Die Antwort..." und ans eine Ganzzahlvariable mit dem Wert 42.

Danach prüfen wir in der folgenden if (...) Anweisung (Zeile 22) ob _err__ ungleich (!= ist der Operator, der prüft, ob zwei Werte ungleich [nicht gleich] sind) dem leeren String "" ist, und falls nicht (ein Fehler wurde zurück gegeben), geben wir diese Fehlermeldung aus. Andernfalls wird der Code im else-Zweig (ab bzw. nach Zeile 24) ausgeführt, der einfach das Ergebnis (_rslt__) ausgibt.

Die Berechnungsfunktion (Informatiker nennen so eine Funktion Parser)

Die Funktion _calcExpr__() wäre in beinahe jeder anderen Programmiersprache sehr schwierig und umfangreich, ist in Python dank der eingebauten eval(Stringausdruck) aber erstaunlich kurz. eval() wertet den übergebenen String aus und gibt dessen Ergebnis (Wert) zurück. Vorher setzen wir noch _err__ auf den leeren String (kein Fehler). Der übergebene Ausdruck muss allerdings ein korrekter Python-Ausdruck sein, falls nicht bricht eval() das Programm mit einer Fehlermeldung ab. Das darf in einem benutzerfreundlichen Programm natürlich nicht vorkommen, darum fangen wir dies über eine sogenannte "Ausnahmebehandlung" ab. Dies funktioniert in Python wie in vielen anderen Sprachen mittels eines "try except" Konstrukts. Falls in dem Block nach dem "try:" ein Python-Fehler auftritt, und nur dann, führt Python den "except:" Block aus. Das Ergebnis wird auf den negativen kleinstmöglichen Gleitpunktwert gesetzt (vordefinierter Wert "float_min" aus dem Modul "sys" [das wir weiter oben mittels der „import”-Anweisung eingebunden haben] in Zeile 32), der in Python möglich ist (man könnte dies z. B. auch als eine Art Fehlercode betrachten) und setzt _err__ auf "Invalid expression" (Zeile 33 - ungültiger Ausdruck - selbstverständlich dürfen Sie jeden anderen, nicht leeren String [""] zurückgeben, um einen Fehler an den aufrufenden Code zurückzugeben).

Zuletzt geben wir ein Tupel zurück, dass eine Gleitpunktzahl und einen String enthält. Wenn der zurückgegebene String leer ist, trat kein Fehler auf und der zurückgegebene Gleitpunktwert ist gültig (der Wert des Ausdrucks). Unten sehen Sie, wie das Programm aussieht, wenn Sie es von der Kommandozeile aus starten.

pythonCalc00.jpg

Jetzt testen wir das Programm mit einigen Ausdrücken:

-2**.5 # man kann 0.5 mit .5 abkürzen wie bei einem Taschenrechner.

Das Ergebnis wird nicht ganz das sein, was Sie erwartet haben. Das liegt daran, dass Python nicht wirklich einen unären Vorzeichenoperator hat. Das - vor 2 ist eine Kurzschreibweise für (-1)*2, d. h. zuerst wird die Wurzel von 2 berechnet (potenzieren mit 1/2 ist gleich radizieren (Wurzel ziehen) mit dem Nenner, hier also Quadratwurzel) und dann das Ergebnis negiert. Wahrscheinlich wollten Sie aber die Quadratwurzel von (-2) berechnen, dann müssen Sie das folgendermaßen schreiben:

(-2)**.5
(8.659560562354934e-17+1.4142135623730951j)

Das ist natürlich auch nicht ganz das, was Sie erwartet haben. Sie haben vermutlich +1.4142135623730951i erwartet oder zumindest (0+1.4142135623730951i). Python spricht (wie praktisch alle Programmiersprachen) Englisch und im Englischen wird (wie im übrigen auch in der Elektrotechnik) ein 'j' statt dem 'i' verwendet. Stört noch der Realteil, der natürlich 0 sein müsste. Intern rechnet Python mit binären Gleitpunktzahlen, die Ausgabe erfolgt aber in Dezimalzahlen. Leider lassen sich binäre Brüche nicht immer exakt in Dezimalbrüche umrechnen, daher kommt der falsche Realwert, allerdings ist -8.6...e-17 (Schreibweise für 8,6...*10-17 = 1/(8,6...*1017)) fast 0 - hier müssen Sie schon selbst mitdenken. Wir werden unsere Funktion _calcExpr__() aber noch verbessern, damit diese Unschönheit wegfällt. Solche "Unschönheiten nennt man auch "kosmetische Fehler".

Fehlerfrei ist unser Programm sowieso noch nicht, bei einigen Eingaben liefert es falsche, bzw, "komische" Ergebnisse. Das, und einige Ergänzungen (mit denen bricht unser Programm dann sogar ab, bei manchen Eingaben) werden wir noch korrigieren, bevor wir ein GUI-Programm daraus machen.

A u f w a c h e n :   Hallo! Denken Sie mit? Falls ja, dann haben Sie tatsächlich vermutlich noch nie programmiert - dann habe ich ja mein Ziel erreicht. Falls Sie aber schon mal programmiert haben, dann hätten Sie spätestens bei dem Ausdruck (-2)**.5 stutzig werden müssen. Mir ist jedenfalls außer Fortran keine Programmiersprache bekannt, die komplexe Zahlen als eingebaute Datentypen unterstützt. In C++ kann man Sie einbauen (seit C++ 11 in der Standardbibliothek als Klasse enthalten) und in C#/.Net gibt es seit 4.0 auch eine Klasse dafür. Das sind aber keine eingebauten Datentypen, auch wenn sie sich weitgehend wie solche verhalten. In Java gibt es zwar ein Paket für komplexe Zahlen, aber die Benutzung ist nur umständlich (hässlich) über Funktionsaufrufe möglich, da Java kein Operator-Overloading kennt. Operatoren sind z. B. das +, -, *, /, ** Zeichen. Es gibt in Programmiersprachen aber noch viel mehr Operatoren, in Python z. B. "or" und "and", das man in logischen Ausdrücken benötigt. Sehen Sie sich die if-Anweisung in Zeile 19 an, wo wir den or-Operator verwenden.

Exercise: Versuchen Sie einen Ausdruck einzugeben, bei dem das Programm ein unerwartetes Ergebnis liefert.

Exercise 2: Versuchen Sie obiges Programm mit unserem "Hello world! Mit Namensausgabe" aus dem vorhergehenden Kapitel zu verbinden. Passen Sie hierzu die Titelleiste des Fensters an, den Text des Buttons (z. B. "Geben Sie einen Ausdruck ein"), werten Sie den Ausdruck (den der Benutzer in dem "Entry"-Texteingabefeld eingibt) aus, und geben Sie das Resultat in der Listbox am Anfang aus. Das sollten Sie hinbekommen (Hint: Nutzen Sie unsere _calcExpr__()-Funktion für die Auswertuntg).

Im nächsten Kapitel werden wir die noch bestehenden Unschönheiten beseitigen.