Programmieren mit Python

Ein „Taschenrechner” (Tischrechner) – GUI-Implementation (grafische Oberfläche)

Unser Tischrechner scheint zu funktionieren, daher können wir uns jetzt daran machen, ihm eine grafische Oberfläche (graphical user interface, kurz GUI) zu verpassen.

Zunächst schreiben wir die Oberfläche in einem Modul (einer Datei). Dabei werden wir einige neue Elemente von Tkinter (dem GUI-Toolkit für Python) kennen lernen:

Zunächst wieder die eigentliche Funktion (nur der Teil für die Oberfläche [GUI]), die eigentliche Funktion - den Parser - habe ich in eine eigene Datei ausgelagert (den Quellcode habe ich ganz unten noch mal angegeben). Denn diese Funktionen haben wir ja bereits in unserer vorherigen Version für die Kommandozeile ausgiebig getestet, und er scheint funktioniert zu haben. Wir benötigen nur 2 Funktionen davon. Hier die erstaunlich kurze (wenn Sie sich die extrem ausührlichen Kommentare weg denken) Version für die grafische Oberfläche (vieles davon kennen Sie schon), mit einigen Neuerungen (UNIX-Nutzer [gilt wahrscheinlich auch für MacIntosh]: Denken Sie daran [NUR] den ersten Hash [#] im Shebang in der ersten Zeile zu löschen!):

Tipp Fügen Sie das Programm unten mittels Copy and Paste in Idle ein und sehen Sie es sich dort an.

##!/usr/bin/python3 # Shebang (if you need it, remove ONLY the first hash (#) # IF YOU HAVE PROBLEMS RUNNING THIS PROGRAM DELETE THIS LINE SO THAT NEXT LINE IS SECOND LINE # -*- encoding: iso-8859-15 -*- import tkinter import tkinter.ttk as ttk # tkinter.ttk may now be abbreviated with ttk (e.g. # myCombo = ttk.Combobox(...) #import sys # commented as currently not needed from calcpars import * # import our parser (now sourced out in the # separate module calcpars.py) _win__=ttk.tkinter.Tk() # Instead of Tk() we now need ttk.tkInter.Tk(), cause # ttk itself (needed for combobox) has no Tk() class _win__.geometry("620x220") # You know that(!), adjust size of our window _win__.wm_title("Calculator with Expression Parser") # set title of our program _cbV__=ttk.tkinter.StringVar() # Create a special "ttk string variable" # Give user a little help after program has started: _cbV__.set( # You cannot assign to _cvB__ with "=", you must use the set() method " # Insert an expression like sin(pi/6) BEFORE the hash (#), " "hit the <ENTER> key, afterwards fold-out the listbox") _cbInList__=[] # The "list" (aPython class) for the fold-out list box of our # combobox. At first empty, we will fill it later _cbIn__=ttk.Combobox(textvariable=_cbV__,values=_cbInList__) # Create a Combobox # which displays the value of our _cbV__ variable in its entry field _cbIn__.pack(fill="x",padx=5,pady=2) # You know that(!), some adjustements _cbIn__.focus() # Set the focus to our combobox, so that the user must NOT # first click into the combobox to enable the write cursor def _cbReturn__(event): # This method will be called, if the user taps the # <ENTER> key (Carriage Return). We do not need the # event, but it must be specified (required by Tkinter). _expr__=_cbV__.get() # First get the input in the entry field of the combobox if (_expr__.strip() == ""): # if user entered a blank line _cbV__.set("") # clear the input line return # and do nothing else # We want to add the user input to the fold-out listbox of the combobox, # so that the user has a "history" of old expressions, which he can # quickly recall by selecting an expression in the listbox: _cbInList__.insert(0,_expr__) # First insert user input to _cbInList__ bound # to combobox at first position (top) ... # ... and bind the list again to the combobox: # _cbIn__.values=_cbInList__ # unfortunately this does NOT work(!) The # combobox does not realize that we have bound the (changed) list again. # You must do it this way: _cbIn__["values"] = _cbInList__ # Note the quotation marks around values(!) _rslt__, _err__=calcExpr__(_expr__) # Call our parser function in calcPars if (_err__ != ""): _cbV__.set("Error: {0}".format(_err__)) else: _cbV__.set(formatResult__(_rslt__)) _cbIn__.select_clear() # clear the selection and _cbIn__.icursor(_cbV__.get().__len__()) # position cursor after last character # Note: In Python normally the len(string)-function ist used instead of # the ugly __len__() method, but we use the method, else we had to save the # len() function under another name ("Grosse Schweinerei"). See also: # http://stackoverflow.com/questions/237128/is-there-a-reason-python-strings-dont-have-a-string-length-method _cbIn__.bind('<Return>',_cbReturn__) # Now we bind the <ENTER>-key to our method # above (only if the combobox has the # focus). I.e.: # If the user taps the <ENTER> key, our # function _cbReturn__ will be called _win__.mainloop() # Let's run our program

Das ist bereits unsere Anzeigefunktion - ist doch gar nicht so schwer. Sie werden es zugegebenermaßen einige male durchlesen müssen und sollten es unbedingt auch selbst in Idle implementieren (selber "schreiben") und testen, aber dann sollten Sie es verstehen. Hier jetzt noch mal die ausführliche Erklärung zu jedem Teil der obigen Funktion:

##!/usr/bin/python3 # Shebang import tkinter import tkinter.ttk as ttk # tkinter.ttk may now be abbreviated with ttk (e.g. # myCombo = ttk.Combobox(...) #import sys # commented as currently not needed

Die obigen Aufrufe sehen etwas anders aus, als unsere bisherigen GUI-Programme. Zum einen beutzen wir nicht mehr "from ... import *", sondern "import ..." Dies bedeutet, dass wir zwar alle Funktionen/Klassen, der importierten Module zur Verfügung haben, sie aber nicht mehr direkt, sondern nur über ihren "Namespace" (ihren Modul-Namen) ansprechen können. Zum anderen verwenden wir das neue Modul (das Paket) "ttk" (Themes for TK). Dies benötigen wir, weil wir eine Combobox (ein grafisches Element, auch Icon genannt) verwenden wollen, die es im normalen (alten) "tkinter" nicht gibt. Manche Klassen sind direkt über ttk... ansprechbar (die vom neuen "Themes" Toolkit). Manche Klassen nur über das alte Tk-Modul. Letztere ruft man mittels ttk.tkinter... auf, die neuen "ttk" Klassen über ttk...).

from calcpars import * # import our parser (now sourced out in the # separate module calcpars.py)

Dies ist unser ausgelagerter Parser (aus calc01.py), den wir genau so wie ein System-Paket von Python (wie z. B. tkinter) mit "import ..." in unser Programm einbinden können. Da wir dieses Modul selbst geschrieben haben und wissen, dass wir alle Funktionen daraus benötigen und das Modul nur 2 neue Namen einführt, verwenden wir hier "from ... import *". Ansonsten sollten Sie sich angewöhnen, die Module wie oben zu importieren und sie später über ihren voll qualifizierten Namen anzusprechen. Diese Funktion finden Sie ganz unten. Da ist nichts neues dabei, die haben wir so schon in der Kommandozeilenversion geschrieben und getestet(!).

Keine Panik, Erklärung hier: Wir haben die beiden Funktionen _calcExpr__(...) und _formatResult__(...) aus unserem Programm "calc01.py" in eine eigene Datei ausgelagert, die wir calcpars.py nennen (diese benötigt keine Endung .pyw, da sie keine GUI-Funktionen/-Methoden enthält). Diese Datei können wir genau so, wie eine der System-Pakete von Python mit "import" einbinden - nett, oder.

_win__=ttk.tkinter.Tk() # Instead of Tk() we now need ttk.tkInter.Tk(), cause # ttk itself (needed for combobox) has no Tk() class

Da ttk selbst keine Tk-Klasse hat (die eigentliche Fensterklasse), müssen wir die aus dem normalen Tkinter benutzen, das wir jetzt über ttk.tkinter ansprechen müssen

_cbV__=ttk.tkinter.StringVar() # Create a special "ttk string variable" # Give user a little help after program has started: _cbV__.set( # You cannot assign to _cvB__ with "=", you must use the set() method " # Insert an expression like sin(pi/6) BEFORE the hash (#), " "hit the <ENTER> key, afterwards fold-out the listbox")

_cbv__ ist ein Objekt der Klasse ttk.tkinter.StringVar. Eine Klasse ist ein benutzerdefinierter Datentyp. Klassen können (haben normalerweise) Instanzvariablen enthalten. Mann kann (normalerweise) beliebig viele Objekte (Instanzen) einer Klasse erstellen, aber jedes Objekt hat seine eigenen Instanzvariablen, die in jeder Instanz verschiedene Werte haben können. Unsere Combobox hat die Instanzvariable textvariable vom Typ ttk.tkinter.StringVar. Ein Objekt dieser Klasse (...StringVar) enthält wie eine normale Python String-Variable eine Zeichenkette. Der Wert dieser Variablen wird im Eingabefeld der Combobox angezeigt. Das wichtige dabei ist, dass man Variablen dieser speziellen Klasse mit ihrer Methode set("beliebiger Text") (also _cbV__.set("...") einen Wert zuweisen kann, der dann sofort im Eingabefeld der Combobox angezeigt wird. Umgekehrt, ändert die Benutzerin unseres Programms den Wert im Eingabefeld, wird dieser Wert automatisch in in die Variable (_cbV__) übernommen und kann mittels "variable=_cbV__.get()" in unserem Programm ausgelesen werden. Das nutzen wir auch gleich, um der Benutzerin einen Hinweis zu geben, was sie machen muss. Wichtig ist der Hash (#), das Kommentarzeichen von Python. Da unsere eigentliche Parser-Funktion die, "Zauber-Methode" eval(), beliebige gültige Python-Ausdrücke verarbeitet, können wir auch das Kommentarzeichen als letztes(!) Zeichen eines Ausdrucks benutzen und dahinter einen Benutzerhinweis anzeigen - praktisch.

_cbInList__=[] # The "list" (aPython class) for the fold-out list box of our # combobox. At first empty, we will fill it later

Eine Combobox ist (im Prinzip) aus einem Eingabefeld (Entry in Python) und einer ausklappbaren Listbox zusammengesetzt. Beides haben wir bereits in unserem Programm mit Ein-/Ausgabe verwendet. Dort haben wir aber direkt an die Listbox zugewiesen.

Intern benutzt eine Listbox ein Objekt (eine Variable) des eingebauten Python-Typs "list". Man kann daher die Listbox der Combobox auch an ein Objekt dieser Klasse "binden". Daher legen wir oben eine Variable der Klasse list an (_cbInList__). Python erkennt an den eckigen Klammern, das _cbInList__ vom Typ "list" sein soll. [] bedeutet die leere Liste (enhält noch keine Objekte). An eine Liste können Sie beliebige Objekte zuweisen, auch unterschiedlichen Typs, z. B. myList=[1,"zwei",3.0]. myList enthält jetzt eine Integer-Variable 2, einen String "zwei" und eine Float (Gleitpunktzahl) 3.0. Heraus bekommt man die Werte ebenfalls mit den eckigen Klammern, z. B. kann man auf das erste Element mittels print(myList[0]) die 1 ausgeben. Die Länge einer Liste (wie viele Elemente sie enthält) können Sie mittels der len-Funktion von Python ermitteln (oder der der Methode __len__(), genau so wie bei Strings). Also print l=len(myList) oder l=myList.__len__() setzen beide l auf 3. Sie müssen sich nur merken, dass immer von 0 an gezählt wird. Das letzte Element unserer Liste ist also die 2. Im Unterschied zu Tupeln kann man Listen auch nachträglich verändern (Werte darin verändern, hinzufügen, entfernen, ...).

Tipp Sie können (und sollten!) das interaktiv in Idle ausprobieren. Starten Sie hierzu Idle, ohne ein Dokument zu laden. Sie sehen den "Eingabe-Prompt" von Python (>>>) geben Sie jetzt z. B. myList=[1,"zwei",3.0] ein (Eingabetaste drücken) und dann z. B. myList[1]. Testen Sie auch mal, was passiert, wenn Sie myList[3] eingeben.

_cbIn__=ttk.Combobox(textvariable=_cbV__,values=_cbInList__) # Create a Combobox # which displays the value of our _cbV__ variable in its entry field

NEU Hiermit legen wir jetzt unsere Combobox an und binden die beiden oben angelegten Objekte an sie: "textvariable" ist das Attribut der Listbox (sorry, das war bis 08.04.20 falsch) Entry-Klasse, das den String der im Eingabefeld angezeigt wird, enthält. "values" ist das Attribut der Klasse, das die Liste enthält. An Attribute (die Attributnamen dürfen Sie nicht ändern!) können Sie Objekte (die vom richtigen Typ sein müssen) "binden"/zuweisen. An textvariable weisen wir daher unsere "Spezial-String-Variable" _cbV__ zu, und an values unsere oben angelegte Liste _cbInList__.

_cbIn__.pack(fill="x",padx=5,pady=2) # das ist auch neu

Das ".pack()" kennen Sie ja schon, wird benötigt, um das grafische Element, die Combox, die ich _cbIn__ genannt habe, anzuzeigen. Man kann da aber auch Parameter übergeben. "fill=x" bedeutet, dass unser grafisches Element (zukünftig werde ich das "Icon" nennen, so heißt das bei GUIs), die komplette Dimension in "X-Richtung" (also Breite) nutzt. Unser Icon (die Combox) ist also immer so breit wie das Fenster, in dem es sich befindet. Na ja, fast. Den "padx=5" bedeutet, dass rechts und links jeweils 5 Pixel als Rand gelassen werden, und was bedeutet jetzt wohl "pady=2"? Das erkläre ich Ihnen jetzt aber nicht. Ich will ja nicht Ihre Intelligenz beleidigen ;-)

Jetzt kommt die Funktion, die die eigentliche Arbeit (die Berechnung des Ausdrucks) ermittelt. Das ist eine sogenannte "Callback"-Funktion, die immer aufgerufen wird, wenn ein bestimmtes Ereignis (hier drücken der Eingabetaste) aufgerufen wird. Da diese vom Python-Interpreter direkt aufgerufen wird, ist die Parameterliste vorgegeben. es muss also eine Variable übergeben werden, selbst wenn wir sie in der Funktion gar nicht auswerten müssen/wollen. Per Konvention nennt man sie meist event:

def _cbReturn__(event): # This method will be called, if the user taps the # <ENTER> key (Carriage Return). We do not need the # event, but it must be specified (required by Tkinter). _expr__=_cbV__.get() # First get the input in the entry field of the combobox if (_expr__.strip() == ""): # if user entered a blank line _cbV__.set("") # clear the input line return # and do nothing else

Zunächst holen wir uns die Benutzereingabe aus der Eingabezeile der Combobox. Dann entfernen wir führende und nachfolgenden "Whitespace" (Leerzeichen, Tabulatorzeichen, ...) mittels der .strip()-Methode der String-Klasse, da uns das den nachfolgenden Test erleichtert. Falls die Benutzerin nichts, oder nur "leere Zeichen" eingegeben hat, löschen wir diese, damit die Eingabezeile wieder ganz leer ist, und kehren zurück (wir haben nichts zu tun).

_cbInList__.insert(0,_expr__) # First insert user input to _cbInList__ bound # to combobox at first position (top) ...

Benutzerfreundlich wie wir unseren Rechner gestalten wollen, speichern wir die Benutzereingabe in der Liste an erster (oberster) Stelle (Index 0), die wir an die Listbox gebunden haben, damit der Benutzer, diese Eingabe später (z. B. weil er sich vertippt hat), wieder in der ausklappbaren Listbox (man kann auch einfach die Cursor-nach-unten-Taste drücken) auswählen und in das Eingabefeld zurückholen und bearbeiten kann. So eine Funktion nennt man "History". Wenn Sie mit der Einabeaufforderung (oder Bash unter Linux) arbeiten, können Sie auch ihre alten Eingaben mit den Cursortasten zurückholen. Allerdings ist diese Eingabe zunächst mal nur in unserer Variable _cbInList__ gespeichert. Die Listbox der Combobox bekommt das leider nicht automatisch mit. Es gibt keine so praktische Spezial-Klasse, wie für das Eingabefeld.

Daher müssen wir jetzt irgendwie der Combobox mitteilen, dass sich unsere Liste geändert hat. Hier hatte ich zunächst Probleme und konnte auch auf den diversen Entwicklerseiten und beim "Googlen" zunächst nichts finden, was mir weitergeholfen hätte. Ich wollte dann schon auf "StackOverflow" nach einer Lösung fragen und habe dazu ein Minimalbeispiel geschrieben, an dem ich mein Problem demonstrieren kann (da müssen die Kommentare dann natürlich in Englisch sein([!]). Habe es dann doch noch lösen können. Wie es geht entnehmen Sie daher bitte dem folgenden Beispielprogramm (das Sie auch direkt ausführen können - falls nicht, sehen Sie sich die Fußnote an.)1). Der Link zur Beispielfunktion öffnet in einem neuen Browser-Tab (oder Fenster), so dass sie dieses einfach schließen können, wenn Sie es verstanden haben. Danach sollten Sie den untenstehenden Code verstehen:

# ... and bind the list again to the combobox: # _cbIn__.values=_cbInList__ # unfortunately this does NOT work(!) The list # is updated, but the combobox does not realize it automatically. # You must do it this way: _cbIn__["values"] = _cbInList__ # Note the quotation marks around values(!)

Jetzt müssen wir nur noch unsere Parserfunktion aufrufen, und das Ergebnis in der Eingabezeile der Combobox ausgeben. Das ist aber ziemlich ähnlich wie in der Kommandozeilenversion, nur das wir statt der print(...)-Funktion unsere "Spezial-Variable _cbV__ setzen, wodurch sich das Eingabefeld automatisch aktualisiert. Ein paar Benutzerfreundlichkeiten habe ich noch hinzugefügt. Erkläre ich unten.

_rslt__, _err__=calcExpr__(_expr__) # Call our parser function in calcPars if (_err__ != ""): _cbV__.set("Error: {0}".format(_err__)) else: _cbV__.set(formatResult__(_rslt__)) _cbIn__.select_clear() # clear the selection and _cbIn__.icursor(_cbV__.get().__len__()) # position cursor after last character # Note: In Python normally the len(string)-function ist used instead of # the ugly __len__() method, but we use the method, else we had to save the # len() function under another name ("Grosse Schweinerei"). See also: # http://stackoverflow.com/questions/237128/is-there-a-reason-python-strings-dont-have-a-string-length-method

Durch das "...select_clear() wird eine eventuelle Selektion gelöscht. Beim Testen hatte ich das Problem (kann es nicht mehr reproduzieren), dass das Ergebnis manchmal selektiert war. Hätte der Benutzer etwas eingegeben, wäre das Ergebnis (weil selektiert) gelöscht worden. Daher stellen wir sicher, dass das Ergebnis nicht selektiert ist. Mit dem ...icursor(...) positionieren wir den Cursor so, dass er immer hinter dem letzten Zeichen steht, damit der Benutzer sofort mit dem Ergebnis weiterarbeiten kann. Der Parameter für die Methode "icursor()" ist die Position, an die die Schreibmarke gesetzt werden soll. Diese Position (hinter dem letzten Zeichen) ermitteln wir anhand der Länge unserer "Spezialvariablen" _cbV__, die ja immer den String zurückgibt, der im Eingabefeld angezeigt wird. Die Länge von Strings (und Listen, und ...) berechnet man normalerweise mit der eingebauten Python-Funktion len(). Alternativ kann man auch die String-Methode __len__() verwenden, also _cbV__.get().__len__(). Wir nehmen hier die hässliche __len__() Methode, sonst müssten wir den Namen der len() Funktion auch noch unter einem anderen Namen sichern (Große Schweinerei).

_cbIn__.bind('<Return>',_cbReturn__) # Hier "binden" wir die "Enter-" (Return-) # Taste an unsere oben definierte _cbReturn__-Funktion _win__.mainloop() # das kennen Sie schon (wir lassen unser Programm laufen)

Sie erinnern sich noch an unser Programm helloWithName.pyw (das "Hello, world" Programm mit Namensausgabe unter "- Ein-/Ausgabe auf dem Bildschirm")! Dort konnten Sie nicht einfach die Eingabetaste (Enter) drücken, um die Begrüßung auszugeben, sondern mussten den Button (die "Schaltflaeche") "Print a Greeting for:" drücken. Durch die "Bindung" in der Zeile unten "_cbIn__.bin('<Return>',_cbReturn__)..." "binden" wir die "Return"-Taste (so wird die Eingabetaste häufig bezeichnet) an unsere obige Funktion "_cbReturn__(event)". D. h. immer wenn der Cursor im Eingabefeld der Combobox steht, und der Benutzer die Eingabetaste betätigt, wird diese Funktion ausgeführt - praktisch, oder! Falls Sie einen Button (Schaltfläche) wollen, mit dem Sie die Berechnung starten können, dann bauen Sie das selbst ein. Das ist einfach sie müssen nur unsere Funktion _cbReturn__ aufrufen. Wie man auf das Anklicken von Buttons im Programm reagieren kann, haben Sie ja bereits im Abschnitt "Ein-/Ausgabe auf dem Bilddschirm" gelernt. Daher überlasse ich das Ihnen das als "Excercise".


So nun noch unser Modul (in der Datei calcpars.py) mit der eigentlichen Parser-Funktion. Der Parser ist der Teil, der den mathematischen Ausdruck (eine Zeichenfolge [String]), den die Benutzerin eingegeben hat, berechnet, und das Ergebnis zurückgibt. Da gibt es aber nichts neues, das hatten wir ja schon in der Kommandozeilenversion programmiert:

# Parser Module for Tk-App (GUI) calc02.pyw # parser to evaluate mathematical expressions from math import * # For a real calcualtor we need all mathematical functions from cmath import * # ... and all complex mathematical functions import sys # needed for maximum and minnimum floating point values _eval__=eval # make an alternative name for calling the eval(...) function _type__=type # make an alternative name for calling the type(...) function 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" if (_type__(_rslt__) == tuple or _type__(_rslt__) == list or _type__(_rslt__) == dict or _type__(_rslt__) == set): _rslt__=-sys.float_info.min # We return this if an error occures _err__="Please enter a correct expression, e. g. no comma allowed" return (_rslt__,_err__) # we return a tupel (2 values at once :-)) def formatResult__(_rslt__): if (abs(_rslt__.real) >= 2e-14): if (abs(_rslt__.imag) < 2e-14): return "{0}".format(_rslt__.real) return "({0}+{1}j)".format(_rslt__.real,_rslt__.imag) if (abs(_rslt__.imag) < 2e-14): return "0" return "(0+{0}j)".format(_rslt__.imag)

Sie haben jetzt also 2 Dateien: Das (grafische) Hauptprogramm, ich habe es "calcgui01.pyw" genannt, und den eigentlichen Parser "calcpars.py". Sie müssen den Parser EXAKT, einschließlich Groß-/Kleinschreibung unter diesem Namen im SELBEN Verzeichnis speichern(!), da wir in unserem GUI-Programm auf diesen Dateinamen verweisen. Der Parser selbst ist kein grafisches Programm, d. h. kann auch in einem Kommandozeilen-Programm so benutzt werden, und braucht daher nur die Endung .py. (Windows, unter UNIXoiden Betriebssystemen verwenden sie sowieso nur die Endung .py.)

Haben Sie bemerkt, dass die Funktionen im Parser-Modul nicht (wie alle anderen Namen) mit einem Unterstrich (_) beginnen! (Sie erinnern sich an die "Große Schweinerei, die vermutlich in der nächsten Lektion kommt") Benennen Sie zum Testen mal die Funktion calcExpr__() im Parsermodul in _calcExpr__() um (natürlich müssen Sie dann auch den Aufruf im GUI-Modul ändern!). Das Programm läuft nicht mehr. Das ist kein Fehler von Python, das ist so gewollt. Indem Sie Namen in einem Modul mit einem Unterstrich beginnen lassen, kann auf diese Namen nur noch von innerhalb dieses Moduls zugegriffen werden. Somit können Sie Implementierungsdetails, auf die eine Benutzerin Ihres Moduls nicht zugreifen können darf, schützen, indem Sie diesen Namen einen Unterstrich voranstellen.

TippWenn Sie dieses Programm verstanden haben, dann sollten Sie mal die Kommentare (zumindest die meisten) löschen, testen ob es noch funktioniert und es dann ansehen. Es wird Ihnen dann viel übersichtlicher erscheinen.

Generell sollte man nicht zu viel Kommentar in einem Programm schreiben. Kommentare haben die schlechte Angewohnheit, dass sie nicht geändert werden, wenn man im Code etwas ändert, und dann das falsche darin steht, was noch mehr verwirrt.

Kommentare wie folgender, haben in einem Programm nichts zu suchen:

if (l > maxlen) # If length of variable l exceeds the maximum allowed length

In Python und in den meisten anderen modernen Programmiersprachen kann man das mit den Mitteln der Sprache und geeigneten Bezeichnern (z. B Variablennamen) selbst ausdrücken. Also nehmen Sie die Programmbeispiele aus diesem Kurs nicht als Beispiel, wie Sie Kommentare einsetzen. Ein Lehrbuch muss hier andere Prioritäten setzen.

Im nächsten Kapitel werden noch Reguläre Ausdrücke vorgestellt, die wir benötigen, bevor wir mit der „Großen Schweinerei” beginnen.



geaendert.png Folgendes stimmt nicht (mehr)! Verwenden Sie für Python 3 Programme IMMER UTF-8 als Zeichencodierung (in Python 2 würden Sie damit wahrscheinlich „auf die Schnauze fallen”). Sie müssen in der Quelldatei auch keinerlei "encoding ..." Anweisung einfügen. Ohne encoding-Anweisung nimmt Python 3 an, dass die Datei mit UTF-8 codiert ist. (Ich entschuldige mich dafür, dass ich das erst nach so langer Zeit korrigiert habe. Aber ich habe ja bereits im Einführungsabschnitt gewarnt, dass ich selbst vorher auch nur mit Python 2 gearbeitet habe, und in dieser Einführung noch jede Menge Fehler stecken können 😁.)

1) Sie werden bemerken, dass ich in dem Beispielprogramm keine Umlaute verwendet habe. Der Grund ist, dass es in ANSI codiert ist (also nur die Zeichencodes 0 bis 255 dargestellt werden, die aber alle Zeichen enthalten, die man im Deutschen benötigt). Starten Sie einen Editor, stellen die Codierung auf ANSI (bei dem guten, kostenlosen Programmier-Editor Notepad++ unter Encoding), fügen sie das Beispielprogramm aus der Webseite (die ist in ANSI codiert) mit Copy and Paste ein und fügen Sie einen Umlaut in einem der Kommentare ein. Dann speichern sie ab (Dateiendung .pyw) und versuchen das Programm direkt (Doppelklick) zu starten. Schließen Sie die Datei im Editor und laden Sie das Programm in Idle, bestätigen Sie mit ja, dass es in UTF-8 konvertiert wird. Speichern Sie es in Idle ab und verlassen sie Idle. Jetzt können Sie es direkt starten. Aber laden Sie es jetzt mal in den Editor zurück und sehen sich an, was aus dem Umlaut geworden ist(!).

Der Grund für dieses Verhalten liegt daran, dass Python ANSI-Dateien nur unterstützt, falls keine Zeichen mit Codes >127 (Umlaute liegen alle darüber) vorkommen. Alles andere will es (weil es von UNIX her kommt) als UTF-8. In Windows, und vielen anderen Software-Paketen wird aber UTF-16 verwendet, sogar in Java, obwohl das auch von UNIX kommt. UTF-16 braucht zwar doppelt so viel Speicher, macht aber anscheinend weniger Probleme (in Java oder C# hatte ich solche Probleme mit den Umlauten nie).

Man könnte jetzt natürlich generell in UTF-8 speichern. Es gibt aber immer noch viele Programme, die mit Unicode nicht klar kommen, aber trotzdem sehr nützlich sind. Daher speichere ich meine Textdateien (Python Quelldateien und auch die Quelldateien für diese Webseite sind Textdateien) lieber mit ANSI. Für Deutsch sind da alle Zeichen drin, die man benötigt, und man kann alle Programme verwenden, ob sie Unicode unterstützen oder nicht.

Tipp Falls Sie (insbesondere als Windows-Nutzer) Ihre Dateien auch lieber in ANSI speichern wollen, fügen Sie in allen ihren Python-Quelldateien möglichst weit oben (Windows-Nutzer als erste Zeile, UNIX-Nutzer direkt nach dem Shebang - letzteres kann ich nicht testen, ob das tatsächlich funktioniert) folgende Zeile ein:

# -*- encoding: iso-8859-15 -*-

Dann können Sie auch bei ANSI-Codierung Umlaute in Python verwenden. Sorry, dass ich das erst so spät erwähne, hatte ich vergessen. Beim Eurozeichen funktioniert es leider nicht: '€' (hier wird es angezeigt, oder auch nicht, wenn ich es auf meinem lokalen Webserver teste nicht und in Python auch nicht). Das gab es noch nicht, als die ANSI (iso-8859-15) Codepage erstellt wurde.