Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1...

52
Kapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache mit dy- namischer Typisierung. Sie ist gut geeignet zum schnellen Ausprobieren von Algorithmen (rapid prototyping) und auch als Script-Sprache. Sie hat als Basiswerte ganze Zahlen, Gleitkommazahlen (Double), und Strings. Sie hat eine umfangreiche Fehlerbehandlung zur Laufzeit (Exception-Handling). Es gibt bereits vordefinierte Datentypen wie Tupel und Listen und auch vor- definierte Listenfunktionen. Auf weitere M¨ oglichkeiten von Python wie eigene Datentypen, Klassen, Methoden, Aus- und Eingabe werden wir hier nicht ex- plizit eingehen. 3.2 Programmierung in Python Wir verwenden den Interpreter in Python zur Auswertung von Definitionen und Ausdr¨ ucken. Ziel ist nicht die volle Programmiersprache Python zu lernen, sondern die typi- schen Programmiermethoden und das Speicher- und Auswertungsmodell einer imperativen Programmiersprache zu verstehen. Zun¨ achst f¨ uhren wir nur die Programmierung ohne Listen und Listenfunktionen ein. 3.2.1 Python: Struktur eines imperativen Programms Ein Python-Programm-File besteht, ¨ ahnlich wie ein Haskell-Programm, aus Definitionen von Funktionen. Es k¨ onnen Deklarationsteile wie Import- Deklarationen enthalten sein. 1

Transcript of Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1...

Page 1: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Kapitel 3

Algorithmische Abstraktionmit Python

3.1 Allgemeines zu Python

Python ist eine imperative und objekt-orientierte Programmiersprache mit dy-namischer Typisierung. Sie ist gut geeignet zum schnellen Ausprobieren vonAlgorithmen (rapid prototyping) und auch als Script-Sprache.Sie hat als Basiswerte ganze Zahlen, Gleitkommazahlen (Double), und Strings.Sie hat eine umfangreiche Fehlerbehandlung zur Laufzeit (Exception-Handling).Es gibt bereits vordefinierte Datentypen wie Tupel und Listen und auch vor-definierte Listenfunktionen. Auf weitere Moglichkeiten von Python wie eigeneDatentypen, Klassen, Methoden, Aus- und Eingabe werden wir hier nicht ex-plizit eingehen.

3.2 Programmierung in Python

Wir verwenden den Interpreter in Python zur Auswertung von Definitionen undAusdrucken.Ziel ist nicht die volle Programmiersprache Python zu lernen, sondern die typi-schen Programmiermethoden und das Speicher- und Auswertungsmodell einerimperativen Programmiersprache zu verstehen.Zunachst fuhren wir nur die Programmierung ohne Listen und Listenfunktionenein.

3.2.1 Python: Struktur eines imperativen Programms

Ein Python-Programm-File besteht, ahnlich wie ein Haskell-Programm,aus Definitionen von Funktionen. Es konnen Deklarationsteile wie Import-Deklarationen enthalten sein.

1

Page 2: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 2

Die wesentlichen Bausteine einer Funktionsdefinition sind Befehle (Anwei-sungen, Kommandos), wie Zuweisungen Sprungbefehle, Ein/Ausgabebefehle,usw.. Die meisten Befehle haben das Ziel, irgendeinen Seiteneffekt zu bewirkenwie Anderung des Speichers, Ausgabe auf einen Drucker, u.a. oder den aktuellenZustand zu ermitteln.

Innerhalb der Anweisungen gibt es auch die Moglichkeit Ausdrucke zu schrei-ben, deren Auswertung ahnlich zu Haskell erfolgt, allerdings ist es immer diestrikte Auswertungsreihenfolge. In diesen Ausdrucken ist die Verwendung selbst-definierter Funktionen moglich, wobei auch die Ergebnisse der Funktionsaufrufe(Returnwert) bei der Berechnung verwendet wird.

3.2.2 Zuweisung und Kontrollstrukturen in Python

Python erlaubt die Eingabe von arithmetischen Ausdrucken. Man kann den In-terpreter wie einen Taschenrechner, aber mit anderbarem Speicher, verwenden:

>>> a = 3>>> b = 4>>> c = a**2 + b**2>>> c25>>> print math.sqrt(c)5.0>>>

Wir werden die Syntax von wesentlichen Konstrukten in Python nun kurzerklaren, fur weitere Konstrukte sowie Details zur Syntax von Python sei aufTutorials und Handbucher verwiesen.

Die Syntax der Zuweisung (Assignment) ist:

variable = ausdruck

Zunachst wird das Ergebnis des Ausdruckes berechnet, wobei die Werte vonVariablen im Speicher nachgeschaut werden. Das Resultat ist ein im Speicherliegendes “Objekt”. Objekte konnen hierbei einfache Zahlen, Strings aber auchkomplexe Datenstrukturen sein. Anschließend weist die Zuweisung der Varia-blen als Wert das Objekt zu. Alternative Sichtweise ist, dass das Objekt alsName den Namen der Variablen erhalt. Ein Objekt kann durchaus mehrere Na-men besitzen. Die Deklaration der Variablen(namen) geschieht in Python mitder ersten Zuweisung, Variablen mussen somit nicht explizit vorher deklariertwerden.

Man kann auch arithmetische Operatoren mit der Zuweisung verknupfen:

x op = Ausdruck

ist moglich:

Page 3: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 3

>>> a= 100>>> a /= 5>>> a20>>> a *= 5>>> a100

Es ist auch eine (scheinbar parallele) Mehrfach-Zuweisung moglich:

x1, . . . xn = e1, . . . , en

wobei ei Ausdrucke sind.Die Auswertung des Ausdrucks

x1, . . . xn = s1, . . . , sn

ist folgendermaßen: Der Wert aller Ausdrucke si wird zuerst ermittelt, unddanach werden den Variablennamen xi die Werte (d.h. Objekte ) si zugewiesen.

Das if-then-else-Konstrukt hat in Python die Syntax:

if Bedingung:Anweisungen wenn Bedingung wahr

else:Anweisungen, wenn Bedingung falsch

Eine Besonderheit von Python ist, dass es das Schlusselwort then nichtgibt, dafur Doppelpunkte zur Trennung einzelner Teilkonstrukte verwendet wer-den. Zusatzlich bestimmt die Einruckung die Blockstruktur, und sorgt dafur,dass man Klammern weglassen kann. Es gibt noch weitere Varianten vonif-then-else in Python, ohne else Zweig oder mit zusatzlichen Abfragen(elif-Zweigen).

Die while-Schleife dient zur Iteration, die Syntax ist:

while Bedingung:Schleifenkorper

Der Anweisungsblock des Schleifenkorpers wird solange ausgefuhrt bis die Be-dingung nicht (mehr) erfullt ist.

Im Schleifenrumpf konnen die Statements break und continue verwendetwerden.

Beispiel 3.2.1 >>> a=10>>> while a > 0:... print a... a = a-1...

Page 4: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 4

10987654321

Funktions- bzw Prozedurdefinitionen, werden mit dem Schlusselwortdef eingeleitet:

def Funktioname(parameter1, parameter2, . . .) :Anweisungenreturn Wert

Der Ruckgabewert der Funktion ist der mit dem Schlusselwort return zuruckgereichte Wert. Die return-Anweisung darf auch weggelassen werden, dann lie-fert die Funktion nichts zuruck (bzw. None). Prozeduraufrufe finden immer mitvoller Argumentanzahl und mit eingeklammerten Argumenten statt: f(a, b, c).

Komplexe Zahlen sind verfugbar. Sie werden als Paar von zwei Double-Zahlen reprasentiert, der Real- und der Imaginar-Teil. Will man diese aus derkomplexen Zahl z extrahieren, dann benutzt man z.real und z.imag.

>>> a=1.5+0.5j>>> a.real1.5>>> a.imag0.5>>> abs(a)1.5811388300841898>>> a*a(2+1.5j)>>>

Neben einfachen Strings kann man Unicode Strings in Python auf folgendeArt erzeugen:

>>> u’Hello World !’u’Hello World !’

Es gibt eine Menge von Modulen, die zur Verfugung stehen. Man kann sieim Interpreter verwenden durch entsprechende import-Anweisungen und quali-fizierte Aufrufe:

Page 5: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 5

>>> import cmath>>> a=1.5+0.5j>>> cmath.sin(a)(1.1248012470579227+0.036860823712804462j)>>>

Eine genauere Beschreibung folgt noch.

3.3 Beispiele und Besonderheiten in Python

3.3.1 Einige einfache Funktionen in Python

In Python kann man wie in Haskell Funktionen definieren. Die Syntax ist etwasanders: Z.B. die Wurzelberechnung mittels Iteration kann man so programmie-ren – wenn man sie selbst programmieren will, und nicht math.sqrt verwendenwill.

def wurzel(x): return wurzeliter(1.0,x)

def wurzeliter(schaetzwert,x):if gutgenug(schaetzwert,x):

return schaetzwertelse: return wurzeliter(verbessern(schaetzwert, x), x)

def quadrat(x): return x*x

def gutgenug(schaetzwert,x):return (abs ((quadrat(schaetzwert) - x) / x) < 0.00001)

def verbessern(schaetzwert,x):return mittelwert(schaetzwert, (x / schaetzwert))

def mittelwert(x,y): return (x + y) / 2.0

>>> wurzel(2.0)1.4142156862745097>>>

3.3.2 Verletzung der referentiellen Transparenz

Die referentielle Transparenz, die in Haskell galt, wird in Python, wie in allenimperativen Programmiersprachen, verletzt:

count = 0

def f(x):

Page 6: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 6

global countcount = count + xreturn count

Der Test f (1) == f (1) ergibt 0, d.h. ”falsch “ bei diesem Beispiel, da dieglobale Variable count durch Seiteneffekte verandert wird.

3.3.3 Flussdiagramme

Der großte gemeinsame Teiler kann durch die Prozedur ggtpy berechnet werden:

def ggtpy(x,y):if x <= 0 or y <= 0:

print ’Eingabe in GGT muss positiv sein’else:

while x != y:if x > y:

x = x - yelse:

y = y - xreturn x

Die schnellere Version von ggt, hier rekursiv:

def ggt(x,y):if y == 0:

return xelse: return ggt(y, (x % y))

Einige Beispielauswertungen:

>>> ggtpy(4,6)2>>> ggtpy(100,85)5>>> ggtpy(3,3)3>>> ggtpy(0,0)Eingabe in GGT muss positiv sein0>>> ggtpy(1234,4321)1>>> ggtpy(1243,3421)11

Man kann ein sogenanntes Flussdiagramm verwenden, um die Wirkungs-weise dieses Programms zu veranschaulichen (siehe Figur 3.1). Allerdings sindFlussdiagramme als Entwurfsmethode oft von begrenztem Nutzen, da zu großeDiagramme entstehen.

Page 7: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 7

x := a; y := b

x = ydrucke x

Stop

Ja nein

x > yJa

x := x-y

nein

y := y-x

Abbildung 3.1: Flußdiagramm fur ggt

3.3.4 Vertauschung ohne Hilfsvariable

Mit der Mehrfach-Zuweisung kann man die Vertauschung von Variablenwertenohne Hilfsvariable durchfuhren:

>>> a = 1>>> b = 2>>> a,b = b,a>>> a2>>> b1>>>

In anderen imperativen Programmiersprachen braucht man i.a. die Sequenz:

Page 8: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 8

c = aa = bb = c####### Im Python Interpreter:>>> a = 1>>> b = 2>>> c = a>>> a = b>>> b = c>>> a2>>> b1>>>

Die Implementierung der Mehrfach-Zuweisung ist doch nicht ganz so parallelwie vermutet

>>> a,b,a = 1,2,3>>> a3>>>

3.3.5 Berechnung der Fibonacci-Zahlen

Die Berechnung der Fibonacci-Zahlen kleiner als n kann man in Python (opti-miert) so hinschreiben:

def fib(n):a,b = 0,1while b < n:print b,a,b = b,a+b

Die formale Definition ist fibn = fibn−1 + fibn−2 fur n > 1 und fib0 = 0,fib1 = 1.

>>> fib(1000)1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

3.3.6 Implementierung der 3 ∗ n + 1-Funktion

Der Fairness halber die Python-Implementierung von drein in Python:

def drein(x):while x != 1:print(x)if x % 2 == 0: x = x/2

Page 9: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 9

else: x = 3*x+1print(1)

3.3.7 Boolesche Funktionen

Beispiel 3.3.1 Zur Demonstration erlautern wir kurz die Wirkungsweise derBooleschen Funktionen:

>>> a = 1>>> not aFalse>>> a = 0>>> b = 1>>> a or b1>>> a and b0>>> del b>>> b

Traceback (most recent call last):File "<input>", line 1, in ?

NameError: name ’b’ is not defined>>> a1>>> a or b1

D.h. not, or, and wirken (fast) wie die Booleschen Operatoren. Es gibt einekleine Besonderheit: or und and werten von links nach rechts aus und geben ihrErgebnis sofort zuruck, wenn der Wert aufgrund des ersten Arguments schonbekannt ist, auch wenn das zweite undefiniert ist.Sie wirken wie folgende Funktionen:

def or(x,y):if x:

return xelse:

return y

def and(x,y):if x:

return yelse:

return x

Page 10: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 10

3.3.8 Iterative Prozesse

Funktionen, die iterative Prozesse erzeugen, lassen sich leicht mittels einerwhile-Schleife (oder einer for-Schleife) programmieren: Hier die den Haskell-Funktionen entsprechenden Funktionen zur Fakultat in Python. Beachte, dassman die Anweisung zur strikten Auswertung in Python nicht braucht.

def fakultaetlin(n):a = faktiter(1,1,n)return a

def faktiter(produkt,zaehler,max):if zaehler > max:return produkt

else: return faktiter(zaehler * produkt,zaehler + 1,max)

def faktwhile(n):produkt = 1zaehler = 1max = nwhile zaehler <= max:

produkt = (zaehler * produkt)zaehler = zaehler + 1

return produkt

3.3.9 Module in Python

Module dienen zur

Strukturierung / Hierarchisierung: Einzelne Programmteile konnen inner-halb verschiedener Module definiert werden; eine (z. B. inhaltliche) Unter-teilung des gesamten Programms ist somit moglich. Hierarchisierung istmoglich, indem kleinere Programmteile mittels Modulimport zu großerenProgrammen zusammen gesetzt werden.

Kapselung: Nur uber Schnittstellen kann auf bestimmte Funktionalitaten zu-gegriffen werden, die Implementierung bleibt verdeckt. Sie kann somit un-abhangig von anderen Programmteilen geandert werden, solange die Funk-tionalitat (bzgl. einer vorher festgelegten Spezifikation) erhalten bleibt.

Wiederverwendbarkeit: Ein Modul kann fur verschiedene Programme be-nutzt (d.h. importiert) werden.

In Python gibt es ebenfalls Module, hierbei gibt es jedoch keinen Modulkopfwie in Haskell, der eine Datei als Modul auszeichnet. Vielmehr ist jede Datei, diePython-Code enthalt, ein Modul und kann von anderen Modulen aus importiertwerden.

Page 11: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 11

Modulimport mittels import

In einfachster Weise kann ein Modul, durch die Anweisung

import Modulname

importiert werden, wobei der Modulname dem Dateinamen ohne Dateiendungentspricht. Bei erstmaliger Ausfuhrung der import-Anweisung werden die aufoberster Ebene des Moduls definierten Befehle (d.h. solche die nicht innerhalbeiner Funktionsdefinition stehen) ausgefuhrt. Bei nochmaligem Ausfuhren derimport-Anweisung werden diese Anweisungen nicht erneut ausgefuhrt. Zur Rei-nitialisierung eines geladenen Moduls steht jedoch die Funktion reload zurVerfugung, die als Argument das Modulobjekt erwartet, d.h.

reload(Modulname)

kompiliert den Quellcode erneut (in interpretierbaren Byte-Code) und fuhrt an-schließend die Anweisungen auf oberster Ebene des importierten Moduls erneutaus.

Beispiel 3.3.2 Wir betrachten die Datei Fib.py, d.h. das Modul Fib:

def fib(n):a,b = 0,1while b < n:print b,a,b = b,a+b

X=10 # Diese Zeilen werden beim Importfib(X) # ausgefuehrt

Beim Import des Moduls werden die beiden letzten Befehle (im Namensraumdes Modules Fib) ausgefuhrt:

>>> import Fib1 1 2 3 5 8

Auf die Funktionsdefinitionen und globalen Variablen des mittels importeingebundenen Moduls kann nur qualifiziert, d.h. in der Form Modulna-me.Funktionsname bzw. Modulname.Variablenname, zugegriffen werden.

Beispiel 3.3.3 Nach Laden des Moduls Fib aus Beispiel 3.3.2 ist die (globa-le) Variable X unbekannt, sie existiert jedoch qualifiziert, gleiches gilt fur dieFunktion fib:

>>> XTraceback (most recent call last):File "<stdin>", line 1, in ?

NameError: name ’X’ is not defined>>> Fib.X10>>> Fib.fib(5)1 1 2 3

Page 12: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 12

Lokale Aliase fur Modulnamen

Importierte Module konnen analog wie in Haskell unter einem Alias-Namenimportiert werden, hierfur gibt es in Python das Schlusselwort as, z.B. kann mandas Modul Fib aus Beispiel 3.3.2 unter dem Namen Fibonacci importieren:

>>> import Fib as Fibonacci1 1 2 3 5 8>>> Fibonacci.fib(5)1 1 2 3

Einbinden mittels from

Mit der from-Anweisung konnen zum einen die zu importierenden Definitionenselektiert werden, zum anderen sind sie direkt im Namensraum des importie-renden Moduls, d.h. unqualifiziert, verfugbar, die Syntax hierbei ist

from Modulname import Definitionsliste

Z.B. kann man ausschließlich die Funktion fib importieren:

>>> from Fib import fib1 1 2 3 5 8>>> fib(5)1 1 2 3

Es gibt noch die Variante

from Modulname import *

die samtliche Namen eines Moduls in den Namensraum des importierenden Mo-duls kopiert. Hiervon sei aber abgeraten, da unerwartete Seiteneffekte auftretenkonnen:

Beispiel 3.3.4 Wir betrachten das Modul Printer:

X = 10;

def printer():global X:print X

Wird dieses Modul mit der from *-Anweisung importiert und die importierteglobale Variable X verandert, so gilt diese Anderung nur im Namensraum desimportierenden Moduls, nicht jedoch im Namensraum der Funktion printer:

>>> from Printer import *>>> X10>>> printer()10

Page 13: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 13

>>> X = 20>>> printer()10>>> X20

Keine Datenkapselung in Python

In Python gibt es keine (echte) Moglichkeit bestimmte Funktionsdefinitionen,o.a. vom Export auszuschließen, d.h. das importierende Modul kann auf samtli-che definierten Namen des importierten Moduls zugreifen. Eine Datenkapselungist somit in Python nicht moglich. Es gibt lediglich die Konvention, dass manDefinitionen deren Namen mit einem Unterstrich _ beginnen, als versteckt an-sehen sollte, und im importierenden Modul nicht benutzen soll. Der Interpreterverbietet den Zugriff jedoch nicht.

3.4 Auswertung von Python-Programmen

Der von-Neumann-Rechner ist ein von John von Neumann (1903-1957) vor-geschlagenes Modell eines universellen Rechners, der zum Konzept der impe-rativen Programmiersprachen passt, genauer: zu einem primitiven Assembler-Programm.

Das Konzept des von-Neumann Rechners ist hilfreich beim Verstandnisder Auswertung von imperativen Programmen. Um die volle Auswirkung derZuweisung zu verstehen, insbesondere die Gultigkeitsbereiche von Variablen,ist es hilfreich, ein solches Modell des Rechners und Speichers zu betrachten.

Page 14: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 14

Haupt-Speicher

Daten Programm

Bus

Ein/Ausgabe

Rechen-werk

ProgrammzählerRegister

Steuer-werk Datenzeiger

Akkumulator

Der Speicher ist in gleichlange Einheiten, die Speicherzellen zu je einemByte = 8 Bits, eingeteilt, die fortlaufend nummeriert sind und unter dieserNummer adressierbar sind, und direkt, unter Angabe der Adresse, gelesen undgeschrieben werden konnen. Als weitere großere Einheit hat man manchmalauch (Speicher-)Worte (normalerweise 1 Wort = 4 Byte = 32 Bit).

Eine zentrale Idee ist die Speicherung von Daten und Programmen im glei-chen Speicher in Binarcode, wobei die Programme in kodierter Form vorliegen.

Selbst die von-Neumann Maschine in der einfachsten Form benotigt zumin-dest: ein Adressregister fur das Programm (den Programmzahler), ein Registerfur Datenadressierung, und ein Register zum Rechnen (Akkumulator). Heut-zutage sind die Register 32 oder 64 Bit lang. Mit 32 Bit ist der adressierbareSpeicher auf 232 Bytes beschrankt (ca. 4 Gigabyte). Im Steuerwerk werden dieProgrammbefehle interpretiert, und im Rechenwerk die arithmetischen Opera-tionen ausgefuhrt.

Ein Programm fur eine von-Neumann-Maschine besteht aus Befehlen, dieman sich durchnummeriert vorstellt: 1.,2.,3. . . . , und die hintereinander im Spei-cher abgelegt sind.Als Befehle sind moglich:

• arithmetische: Addiere Konstante zum Akkumulator. Auch indirekt adres-sierte: mulitpliziere Zahl an Adresse xxx mit dem Akkumulator und spei-chere das Ergebnis im Akkumulator. Auch arithmetische Vergleiche sindmoglich.

Page 15: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 15

• bedingte und unbedingte Sprungbefehle. Z.B. springe zu Programmbefehl108. Oder: Wenn Akkumulator 6= 0, dann springe zu Befehl 101, sonst zu201.

• Ein-Ausgabe-Befehle: Drucke den Inhalt des Akkumulators. Oder: Spei-chere den Inhalt des Akkumulators an der Adresse, die im Datenregistersteht.

Die zweite zentrale Idee des von-Neumann Rechner-Modells ist die Methodeder Abarbeitung des Programms:Es gibt einen Befehlszyklus, der in etwa so ablauft.Start des Programms ist mit dem Programmbefehl 1.Der Befehlszyklus:

1. Lade den Programmbefehl mit der Nummer, die im Programmzahler an-gegeben ist, in das Steuerwerk. Das Steuerwerk steuert dann, abhangigvom Inhalt des Programmbefehle, die weitere Abarbeitung.

2. • Wenn es ein Sprungbefehl ist, setze den Programmzahler neu. Endedes Zyklus.

• Wenn es ein arithmetischer Befehl ist, dann entsprechend abar-beiten. Diese Abarbeitung wird vom Rechenwerk erledigt. DerProgrammzahler wird danach um 1 erhoht. Ende des Zyklus.

• Wenn es ein Ein-Ausgabebefehle ist, dann entsprechend abarbeiten.Der Programmzahler wird danach um 1 erhoht. Ende des Zyklus.

3. Bei dem speziellen Befehl Stop wird das Programm beendet.

Naturlich ist ein richtiger Computer (bzw. Prozessor) komplizierter. ZumBeispiel hat die Recheneinheit noch weitere universelle Register, die meist sogroß sind, dass eine Adresse gespeichert werden kann.

3.4.1 Auswertung von Pythonprogrammen

In diesem Abschnitt werden wir einen Ausschnitt aus der operationalen Seman-tik von Python angeben. Das soll das Verstandnis der Vorgange vertiefen. Auchwenn es zunachst wie eine umstandliche Erklarung von Zusammenhangen wirkt,die man nach dem Erlernen der Programmiersprache einfach weiß, brauchtman eine solche formale Begriffsbildung und Regeln, um die Auswertung vonProgrammen und Ausdrucken rechnerunabhangig mitteilen und beschreiben zukonnen. Dieser Formalismus ist es auch notwendig, um zu spezifizieren bzw. zuverstehen, wie man geschachtelte Ausdrucke mit Seiteneffekten und Funktions-aufrufen auswertet. Zudem versteht man danach auch, welche Implementierun-gen der Modellvorstellung nicht entsprechen.

Page 16: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 9. Dezember 2004 16

Wir nehmen an, dass nur sehr einfach strukturierte Programme vorliegen.D.h. als Python-Programm betrachten wir eine Menge von Funktionsdefinitio-nen und Variablendefinitionen, wobei nur Zuweisung, if-then-else, while, arith-metische und logische Operationen benutzt werden. Funktionsaufrufe werdenerst mal ausgeschlossen, da sie Seiteneffekte auf globalen Variablen bewirkenkonnen und auch etwas komplizierter sind. Diese werden spater behandelt. AnBasiswerten betrachten wir im Moment nur Zahlen, wobei auch Boolesche Werteals Zahlen gespeichert werden 1 bedeutet ’wahr’ und 0 bedeutet ’falsch’.

3.4.2 Die formale Modellierung

Wir betrachten auch die globale Umgebung, damit wir Seiteneffekte auf globaleVariablen angeben und modellieren konnen, da dies in den meisten imperativenProgrammiersprachen vorkommt. Insbesondere muss die operationale Seman-tik dann die Reihenfolge der Operationen beschreiben, denn die Seiteneffekteandern sich, wenn die Reihenfolge der Auswertungen verandert wird.

Unsere Modellierung in diesem Abschnitt ist vereinfacht und ist nicht ein-fach erweiterbar auf geschachtelte Funktionsdefinitionen, da man dazu weitereKonzepte benotigt.

Definition 3.4.1 Eine Bindung ist ein Paar (x, v), wobei x ein Variablennameund v ein Basiswert ist oder ⊥. Das schreiben wir etwas deutlicher als x 7→ v.

Ein Umgebungsrahmen (Frame) ist eine endliche Menge von Bindungen.Pro Variablennamen x kann nur eine Bindung x 7→ v in einem Umgebungsrah-men enthalten sein.

Eine Umgebung (Variablenumgebung) ist ein Paar (R1, R2) von zwei Umge-bungsrahmen, des lokalen Umgebungsrahmens R1 und des globalen Umgebungs-rahmens R2.

Um das Modell zu vereinfachen, nehmen wir an, dass in unserer Modellie-rung lokale Variablennamen niemals gleich globalen Variablennamen sind. Daskann man erreichen, indem lokale Variablen in Funktionsrumpfen so umbenanntwerden, dass diese disjunkt zu allen anderen Variablen sind.

Der Wert wert(x,Z) einer Variablen x in einer Umgebung Z = (R1, R2) istdefiniert als der Wert v der Bindung x 7→ v in der Umgebung R1 ∪R2. Kommtkein solches Paar in R1∪R2 vor, oder ergibt sich ⊥, so ist der Wert undefiniert.Normalerweise wird bei einer solchen Anfrage das Programm abgebrochen.

Die Funktion update(x, v, Z) hat als Ergebnis die Umgebung Zneu, die ausZ = (R1, R2) folgendermaßen entsteht:

• Wenn x lokale Variable ist, und es eine Bindung x 7→ v′ in R1 gibt, wirddiese durch x 7→ v ersetzt. Das ergibt einen veranderten Umgebungsrah-men R′

1, so dass Zneu := (R′1, R2).

• Wenn x lokale Variable ist und wenn es keine Bindung x 7→ v′ in R1 gibt,dann ergibt sich ein Fehler: Wert wird referenziert ohne dass er angelegtwurde.

Page 17: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 15.12.2004 17

• Wenn x globale Variable ist, und es eine Bindung x 7→ v′ in R2 gibt, wirddiese durch x 7→ v ersetzt. Das ergibt einen veranderten Umgebungsrah-men R′

2, so dass Zneu := (R1, R′2).

• Wenn x globale Variable ist und wenn es keine Bindung x 7→ v′ in R2 gibt,dann ergibt sich ein Fehler: Wert wird referenziert ohne dass er angelegtwurde.

Beispiel 3.4.2 Sei die Umgebung definiert als

Z = ({x 7→ 1, y 7→ 2, z 7→ 3}, {gx 7→ 2, gu 7→ 100})

Dann ist wert(x,Z) = 1,wert(gu, Z) = 100,und update(x, 30, Z) = ({x 7→ 30, y 7→ 2, z 7→ 3}, {gx 7→ 2, gu 7→ 100}).

Definition 3.4.3 Der Wert eines Ausdrucks s in der Umgebung Z ist derWert, der bei Berechnung des Ausdrucks entsteht, wobei der Wert der Variablenin der Umgebung Z ermittelt wurde. Ist eine (fur die Berechnung benotigte) Va-riable x in s undefiniert, dann ist der Wert von s ebenfalls undefiniert. D.h. wirnehmen bei der Auswertung von Ausdrucken in einfachem Python im Momentan, dass keine Seiteneffekte (d.h. keine Zustandsanderungen) durch Auswertun-gen von Ausdrucken entstehen.Hier einige formale Definitionen fur die Auswertung von arithmetischen undBooleschen Ausdrucken, die seiteneffektfrei sind, d.h. ohne weitere Funkti-onsaufrufe, wobei der Operator rechts in der Tabelle jeweils der arithmetischeOperator ist. Funktionsanwendungen und Kombinationen mit arithmetischenAusdrucken werden wir weiter unten behandeln.

wert(s1 + s2, Z) := wert(s1, Z) + wert(s2, Z)wert(s1 ∗ s2, Z) := wert(s1, Z) ∗ wert(s2, Z)

wert(s1 ≤ s2, Z) :={

1 wenn wert(s1, Z) ≤ wert(s2, Z)0 sonst

wert(not s, Z) :={

1 wenn wert(s, Z) = 00 wenn wert(s, Z) 6= 0

wert(s1 and s2, Z) :={

0 wenn wert(s1, Z) = 0wert(s2, Z) wenn wert(s1, Z) 6= 0

. . . . . . . . .

. . . . . . . . .

Beispiel 3.4.4 Wert eines seiteneffektfreien Ausdrucks in einer Umgebung.Sei Z = ({x 7→ 1, y 7→ 2, z 7→ 3}, {u 7→ 100}). Dann ergibt sich mit obiger

Definition als Wert von x+(y*z):

Page 18: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 15.12.2004 18

wert(x + (y ∗ z), Z) = wert(x, Z) + wert(y ∗ z, Z)

= 1 + (wert(y, Z) ∗ wert(z, Z))

= 1 + (2 ∗ 3)

= 7

Als Wert von x+u ergibt sich, wobei u global definiert ist:

wert(x + u, Z) = wert(x, Z) + wert(u, Z)

= 1 + 100

= 101

Auswertung von Zuweisungen

Definition 3.4.5 Wir werden im folgenden die Schreibweise der logischen Re-geln verwenden:

V oraussetzungen

Konsequenz

Oberhalb des Striches werden evtl. mehrere Voraussetzungen notiert und unter-halb die Konsequenz.Wenn es keine Voraussetzung gibt, wird nur die Konsequenz notiert (ohneBruchstrich)

Effekt der Ausfuhrung eines Python-BefehlsWir schreiben den Effekt einer Befehlsausfuhrung folgendermaßen hin:

(Z;B) → Z ′ , wenn, nach Ausfuhrung des Befehls (des Programmstucks) B

im Zustand Z der Zustand Z ′ danach hinterlassen wird.

Zuweisung:

(Z;x = s) → update(x, wert(s, Z), Z)

Auch hier ist zu beachten, dass wir erst mal annehmen, dass s keine Seiteneffektebewirkt.

Mehrfachzuweisung:

(Z;x1, . . . , xn = s1, . . . , sn)→

update(xn,wert(sn, Z),update(xn−1,wert(sn−1, Z), . . . update(x1,wert(s1, Z), Z) . . .))

Page 19: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 15.12.2004 19

Man erkennt, dass diese Zuweisung ein Potential zur Parallelisierbarkeit hat,aber nur unter der Annahme, dass die Auswertung der Ausdrucke keine Seiten-effekte bewirkt, und dass die Variablennamen der Zuweisung alle verschiedensind.

Beispiel 3.4.6 Sei Z = ({x 7→ 3, y 7→ 4, z 7→ 5}, ∅).Die Auswertung von x,y = y,x im Zustand Z ergibt:

Z ′ = update(y,wert(x, Z),update(x,wert(y, Z), Z))= update(y, wert(x, Z),update(x, 4, Z))= update(y, wert(x, ({x 7→ 3, y 7→ 4, z 7→ 5}, ∅)), ({x 7→ 4, y 7→ 4, z 7→ 5}, ∅))= update(y, 3, ({x 7→ 4, y 7→ 4, z 7→ 5}, ∅))= ({x 7→ 4, y 7→ 3, z 7→ 5}, ∅)

Hier muss man beachten, dass Z in den Berechnungen immer die gleicheUmgebung referenziert.

Beispiel 3.4.7 Sei Z = ({x 7→ 3, y 7→ 4, z 7→ 5}, ∅).Die Auswertung von x,x = y,0 im Zustand Z ergibt:

Z ′ = update(x,wert(0, Z),update(x, wert(y, Z), Z))= update(x, 0,update(x, 4, Z))= update(x, 0,update(x, 4, ({x 7→ 3, y 7→ 4, z 7→ 5}, ∅))),= update(x, 0, ({x 7→ 4, y 7→ 4, z 7→ 5}, ∅),= ({x 7→ 0, y 7→ 4, z 7→ 5}, ∅),

Auswertung der Sequenz

Dies ist ein Konzept, das in Haskell unnotig ist, aber in imperativen Program-miersprachen wie Python notwendig ist.{c1; c2} ist die Hintereinanderausfuhrung der beiden Kommandos c1, c2. DieRegel zur operationalen Semantik ist:

(Z; c1) → Z1 (Z1; c2) → Z2

(Z; {c1; c2}) → Z2

Beispiel 3.4.8 Sei Z = ({x 7→ 3, y 7→ 4, z 7→ 5}, ∅).Die Auswertung von x = y; y = x im Zustand Z ergibt:

1. (Z, x = y) → update(x, wert(y, Z), Z)= update(x, 4, Z)= ({x 7→ 4, y 7→ 4, z 7→ 5}, ∅)=: Z1

2. (Z1, y = x) → update(y,wert(x, Z1), Z1)= update(y, 4, Z1)= ({x 7→ 4, y 7→ 4, z 7→ 5}, ∅)= Z1

Fallunterscheidung

Auswertungsregeln:

Page 20: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 15.12.2004 20

wert(b, Z) 6= 0 (Z, c1) → Z1

(Z; if b : c1 else : c2) → Z1

wert(b, Z) = 0 (Z, c2) → Z2

(Z; if b : c1 else : c2) → Z2

Die Auswertung einer Fallunterscheidung ohne else-Fall oder mit weiterenelseif-Fallen ist jetzt eine einfache Verallgemeinerung.

Bei der Abarbeitung muss man sich merken, dass zunachst nur die Bedingungausgewertet wird, und abhangig von ihrem Wert entweder die Befehle des ja-oder des nein-Falles.

While-Schleife

Bei der formalen Definition der Auswertung der while-Schleife muss man beach-ten, dass diese evtl. nicht stoppt. Die folgende Regel berucksichtigt das. Ebensobeachte man, dass die Regel rekursiv ist.

wert(b, Z) = 0(Z; while b : c) → Z

wert(b, Z) 6= 0 (Z, c) → Z1 (Z1; while b : c) → Z2

(Z; while b : c) → Z2

Die erste Regel behandelt den Fall, dass die Bedingung falsch ist, und diewhile-Schleife nicht durchlaufen wird. In dem Fall bleibt die Umgebung erhal-ten.

Die zweite Regel behandelt den Fall, dass Die Bedingung wahr ist, und dieSchleife durchlaufen wird. In der Bedingung steht: wenn der Rumpf einmaldurchlaufen wird, danach die While-Schleife und sich dann die Umgebung Z2

ergibt, dann auch nach der While-Abarbeitung alleine.

Die for-Schleife werden wir noch behandeln.

Beispiel 3.4.9 Sei die Umgebung vor der Auswertung der while-Schleife: Z =({x 7→ 0, n 7→ 5}, ∅).Der Befehl seiwhile (n > 0): x = x+n; n = n-1

Wir gehen die Auswertung durch, entsprechend den Regeln:

1. wert(n > 0, Z) ergibt 1.

2. Wir wenden die zweite Regel an. Die zweite Voraussetzung ist (Z, c) → Z1.c ist hierbei der Rumpf des while-Ausdrucks und Z1 die Umgebung nacheinmaliger Ausfuhrung. Es ergibt sich Z1 = ({x 7→ 5, n 7→ 4}, ∅).Um die dritte Voraussetzung zu erfullen, muss man jetzt wieder einenwhile-Ausdruck, jetzt im Zustand Z1 auswerten. D.h. man muss wiederdie volle Regel fur while verwenden. Das kommt mehrfach vor und ergibt

Page 21: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 15.12.2004 21

eine Folge von Umgebungen:({x 7→ 9, n 7→ 3}, ∅).({x 7→ 12, n 7→ 2}, ∅).({x 7→ 14, n 7→ 1}, ∅).({x 7→ 15, n 7→ 0}, ∅).

3. Danach muss man, um die letzte Voraussetzung zu erfullen, die erste Regelanwenden, es ergibt sich, dass die Umgebung erhalten bleibt.

4. Jetzt kann man alle Voraussetzungen nach und nach erfullen, die letzteUmgebung wird jeweils weitergegeben. Es ergibt sich am EndeZ2 = ({x 7→ 15, n 7→ 0}, ∅).

3.4.3 Anzahl der Schritte bei Auswertung

Die Regeln der operationalen Semantik sind zwar nicht so formuliert, dass sie dieReihenfolge der Operationen genau festlegen, aber man kann an der Weitergabeder Umgebungen die notwendige Reihenfolge der Schritte ablesen.

Aufbauend auf der operationalen Semantik konnen wir jetzt die Anzahl derAuswertungsschritte definieren und auch zahlen.Wir vereinbaren: Bei Auswertung eines Ausdrucks werden folgende Auswer-tungsschritte gezahlt:

• Einzelauswertung einer Operation in einem Ausdruck

• Auswertung eines Befehls

• Zuweisung

• Wertermittlung bei Variablen

Nicht gezahlt wird die implizite Initialisierung von Variablen.Bei Operationen wir += nehmen wir an, dass diese zunachst in eine normaleZuweisung transformiert wurden.

Beispiel 3.4.10 Die Auswertung der Sequenz x = y; y = x ergibt:

Auswertung von yZuweisung auf xAuswertung von xZuweisung auf y

Das sind insgesamt 4 Auswertungsschritte.

Beispiel 3.4.11 Auswertung einer while-Schleife, die fib (10) berechnet.Wir zahlen die Schritte.

Page 22: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 15.12.2004 22

n = 10;a,b = 0,1;while n > 0:print b;a,b = b,a+b;n = n-1;

Die Anzahl der Auswertungsschritte ergibt:1 Zuweisung zu n2 Mehrfachzuweisung1 Aufruf von while2 Bedingung2 print2 Mehrfachzuweisung a = b4 Mehrfachzuweisung b = a+b; (a;b;+;=)3 Zuweisung n=n-1

Ergebnis: 3 fur Initialisierung1 fur while-Aufruf14 pro while-Durchlauf (10 mal)2 Bedingung zur While-Beendigung

In der Summe ergibt das: 6 + 14 ∗ n Auswertungsschritte, d.h. O(n).

3.4.4 Auswertung unter Benutzung von Funktionsaufru-fen und Seiteneffekten

Hier erweitern wir die Auswertung um die Behandlung von Funktionsaufrufenund auch Seiteneffekten. Die (echten) Seiteneffekte konnen in unserer Model-lierung nur von Funktionsanwendungen herruhren, in denen globale Variablenverandert werden.

Bevor wir die Formalisierung durchfuhren, hier noch zwei Beispiele.

Beispiel 3.4.12 Ein Beispiel einer Booleschen Funktion mit Seiteneffekt. DieFunktion schonmalda testet, ob der Wert 12345 schon einmal eingegeben wurde.

schonmaldaKenn = Falsedef schonmalda(x):

global schonmaldaKenn;if (x == 12345):

if schonmaldaKenn:return True

else: schonmaldaKenn = Truedef InitKenn():

global schonmaldaKenn;schonmaldaKenn = False

>>> kap3.schonmalda(3)False

Page 23: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 15.12.2004 23

>>> kap3.schonmalda(12345)False>>> kap3.schonmalda(12345)True>>> kap3.InitKenn()>>> kap3.schonmalda(12345)False>>> kap3.schonmalda(12345)True>>>

Beispiel 3.4.13 Die Funktion addToAkku addiert das Argument jeweils zu ei-nem Akkumulator, der als globale Variable vereinbart ist.

Akkumulator = 0def addToAkku(x):

global Akkumulator;Akkumulator = Akkumulator + xreturn Akkumulator

def ShowAkku():return Akkumulator

>>> kap3.addToAkku(2)2>>> kap3.ShowAkku()2>>> kap3.addToAkku(2) * kap3.addToAkku(2)24>>>

Die letzte Rechnung kommt von 4 ∗ 6 = 24, da der Akkumulator jeweils um 2erhoht wird.

Da Python keine explizite Variablendeklaration fur lokale Variablen in Funk-tionen hat, aber doch eine implizite Variablendeklaration macht, mussen wirdiese Variablen hier behandeln. Mit LV(f) bezeichnen wir die lokalen Variablenvon f : Das sind solche, die nicht als formale Parameter von f vereinbart sind, dieaber auf der linken Seite von Zuweisungen innerhalb des Rumpfs vorkommen.

Ein Funktionsaufruf in Python ist von der Form f(s1, . . . , sn). Um die Aus-wertung zu verstehen, muss man sich klarmachen, dass die si wieder Funktions-aufrufe enthalten konnen, und dass Argumente von links nach rechts ausgewertetwerden.

Da als Ergebnis einer Auswertung der Wert eines Funktionsaufrufs und derZustand benotigt wird, schreiben wir

(Z; s) →e (Z ′; v)

Page 24: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 24

falls es sich um die Auswertung eines Ausdrucks (expressions) s im Zustand Zhandelt, Z ′ der Zustand danach ist und v der return-Wert von s.

Aufgrund der Regeln wird bei der Auswertung →e nur der globale Anteil inZ und Z ′ unterschiedlich sein, im Gegensatz zu Auswertung von Befehlen.

Eine (kleine) Schwierigkeit bereitet die spezielle Methode der Ruckgabe mit-tels return, die nicht nur einen Wert zuruckgibt, sondern auch die Ausfuhrungder Funktion beendet, und damit einen Rucksprung bewirkt. Das kann in unse-rer Methode nicht genau modelliert werden, statt eines Rucksprungs sind nacheinem return innerhalb der Funktion alle Befehle ohne Wirkung.

Wir erweitern deshalb den bisher benutzten Formalismus, indem wir im Um-gebungsrahmen zwei spezielle Variablennamen zur Steuerung der Ausfuhrunghinzufugen:

rj enthalt true bzw. false, wenn ein Rucksprung aktiv ist.rw enthalt den Ruckgabewert, wenn ein Rucksprung aktiv ist.

Wir geben nacheinander die operationale Semantik der Befehle an, wennSeiteneffekte erlaubt sind.

Zunachst die einfachen Auswertungen:

(Z, x) →e (Z,wert(x,Z)) wenn x Variable ist

(Z, v) →e (Z, v) wenn v Basiswert ist

Wenn ein Rucksprung aktiv ist, sollen Anweisungen c nichts tun:

v 6= 0(({. . . , rj 7→ v, . . .}, R), c) → ({. . . , rj 7→ v, . . .}, R)

return

(Z; s) →e (Z ′;w)(Z, return s) → update( rj, 1,update( rw,w,Z ′))

Die operationale Semantik der Konstrukte muss jetzt so erweitert werden,dass diese nur unter der Bedingung ausgefuhrt werden, dass rj den Wert 0hat. Es reicht aus, das jeweils in den Bedingungen aller Regeln mit aufzufuhren.Wir lassen diese Vorbedingung bei der Notation unten weg, damit die NotationRegeln nicht zu kompliziert wird.

Zuweisung

(Z, s) →e (Z1, v)(Z;x = s) → update(x, v, Z1)

Mehrfachzuweisung:

Page 25: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 25

(Z, s1) →e (Z1, v1), (Z1, s2) →e (Z2, v2), . . . , (Zn−1, sn) →e (Zn, vn)(Z;x1, . . . , xn = s1, . . . , sn)

→update(xn, vn,update(xn−1, vn−1, . . . update(x1, v1, Zn) . . .))

Man erkennt, dass die Mehrfach-Zuweisung jetzt nicht mehr parallelisierbar ist,da die Umgebungen jeweils ubergeben werden mussen.

Fallunterscheidung:

(Z, b) →e (Z1, v) v 6= 0 (Z1, c1) → Z2

(Z; if b : c1 else : c2) → Z2

(Z, b) →e (Z1, v) v = 0 (Z1, c2) → Z2

(Z; if b : c1 else : c2) → Z2

While-Schleife

(Z, b) →e (Z1, v) v = 0(Z; while b : c) → Z1

(Z, b) →e (Z1, v) v 6= 0 (Z1, c) → Z2 (Z2; while b : c) → Z3

(Z; while b : c) → Z3

Auswertung eines Ausdrucks

Wir mussen auch den Fall betrachten, dass eine Funktion ohne ein return been-det wird. Dann wird auch kein Wert zuruckgegeben. Diesen “Nicht-Wert“ kannman durch einen eigenen Wert beschreiben, in Python ist es None. Er verhaltsich ahnlich wie das Objekt () des ”unit-types “ in Haskell.

Damit kann man die Auswertung eines Ausdrucks beschreiben. Hierbei gehenwir davon aus, dass f den Rumpf rumpff hat, die formalen Parameter x1, . . . , xn

und die lokalen Variablen LV (f) = {y1, . . . , ym}.

(Z, s1) →e (Z1, v1), . . . , (Zn−1, sn) →e (Zn, vn),R = {x1 7→ v1, . . . , xn 7→ vn, y1 7→ ⊥, . . . , ym 7→ ⊥, rj 7→ 0, rw 7→ None}Zn = (R1, R2)((R,R2), rumpff ) → ({. . . , rw 7→ w}, R′

2)(Z, f(s1, . . . , sn)) →e ((R1, R′

2), w)

Das kann man so beschreiben:Zeile 1: Zuerst werden die Argumente von f von links nach rechts ausgewertet.Da man damit rechnen muss, dass die Auswertung Seiteneffekte in der lokalenUmgebung und in der globalen hat, darf man das nur sequentiell machen, undder veranderte Zustand muss jeweils mitubergeben werden. Das kann einen re-kursiven Abstieg in die Auswertung von Unterargumenten bedeuten.

Page 26: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 26

Zeile 2: Wenn die Auswertung der Argumente beendet ist, kann man die loka-le Umgebung R definieren, die die LV-Variablen initialisiert und den formalenVariablen die entsprechenden Werte zuweist.Zeile 3: aus Zn wird die globale Umgebung extrahiert.Zeile 4: Jetzt kann man den Rumpf der Funktion auswerten. Am Ende des Auf-rufs ist der Ruckgabe-Wert bekannt, falls alles terminiert. Wenn kein returngemacht wurde, wird als Wert das Objekt None zuruckgegeben, das als Initiali-sierung verwendet wurde.

Da in den Argumenten eines Ausdrucks ein return unzulassig ist 1, brauchtman nicht damit zu rechnen, dass die Zustande Zi einen Sprung erzwingen.Ergebnis am Ende ist der evtl. veranderte Zustand, und der berechnete Wert.

Zu beachten ist, dass die Berechnung wert(s, Z) jetzt nur noch fur Varia-blen s stimmt. Die Berechnung des Wertes fur arithmetische (auch Boolesche)Ausdrucke muss angepasst werden und sieht jetzt so aus:

(Z, s1) →e (Z1, v1), (Z1, s2) →e (Z2, v2), v1 op v2 = v3

(Z, s1 op s2) →e (Z2, v3)

wobei op ein arithmetischer (bzw. Boolescher) Operator ist.

Beispiel 3.4.14 Als Abkurzung verwenden wir in den Beispielen rj statt rjund rw statt rw

def fakultaet(x):if x <= 1:

return 1else: return x*fakultaet(x-1)

Die jeweiligen lokalen Umgebungen bei Auswertung von fakultaet(3) ohnedie globale Umgebung sind in zeitlicher Reihenfolge wie folgt:

Aufruf von fakultaet(3) im Zustand Z{x 7→ 3, rj 7→ 0, rw 7→ None}

rekursives Auswerten von fakultaet (2)x 7→ 2, rj 7→ 0, rw 7→ None}

rekursives Auswerten von fakultaet (1){x 7→ 1, rj 7→ 0, rw 7→ None}

Auswerten von return 1{x 7→ 1, rj 7→ 1, rw 7→ 1}

Auswerten von return 2{x 7→ 2, rj 7→ 1, rw 7→ 2}

Auswerten von return 6{x 7→ 3, rj 7→ 1, rw 7→ 6}

Resultat am Ende ist 6.

Beispiel 3.4.15 Veranderung einer globalen Variablen durch eine Zuweisung.Hier kann man die dauerhafte Veranderung der Umgebung nachvollziehen.

1Hier gibt es doch eine Typprufung in Python

Page 27: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 27

count = 0def nreft(x):

global countcount = count + xreturn count

Die Ausfuhrung von nreft(1) ergibt:

Aufruf von nreft(1) ({x 7→ 1, rj 7→ 0, rw 7→ None}, {count 7→ 0})nach count = count + x ({x 7→ 1, rj 7→ 0, rw 7→ None}, {count 7→ 1})nach return count ({x 7→ 1, rj 7→ 1, rw 7→ 1}, {count 7→ 1})

Die globale Umgebung nach dem nreft(1)-Aufruf ist {count 7→ 1} und derRuckgabewert ist = 1.

Die Bedingung nreft(1) == nreft(1) kann jetzt nicht zu True auswerten,denn nach Auswertung des linken Ausdrucks hat count den Wert 1, nach Aufrufdes zweiten hat count den Wert 2, das sind auch die jeweiligen Ruckgabewerte,somit ergibt sich False.

Beispiel 3.4.16 Wir zeigen die Zustande bei Auswertung voneulerzahl(0.0001) in zeitlicher Reihenfolge.

def eulerzahl(x):s = 1e = 0n = 1while x < s:

e = e + ss = 1.0 / (fakultaet(n))n = n+1

return e

Abfolge der Zustande, wobei wir die globale Umgebung weglassen.

Page 28: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 28

Aufruf von eulerzahl(0.0001){x 7→ 0.0001, rj 7→ 0, rw 7→ None}

Aufruf von eulerzahl{s 7→ ⊥, e 7→ ⊥, n 7→ ⊥, x 7→ 0.0001, rj 7→ 0, rw 7→ None}

3 Zuweisungen{s 7→ 1, e 7→ 0, n 7→ 1, x 7→ 0.0001, rj 7→ 0, rw 7→ None}

1. Zuweisungen im while{s 7→ 1, e 7→ 1, n 7→ 1, x 7→ 0.0001, rj 7→ 0, rw 7→ None}

fakultaet im while, nur dessen lokale Umgebung{x 7→ 1, rj 7→ 0, rw 7→ None}

nach Aufruf fakultaet und Zuweisung:{s 7→ 1, e 7→ 1, n 7→ 1, x 7→ 0.0001, rj 7→ 0, rw 7→ None)]

nach n = n+1{s 7→ 1, e 7→ 1, n 7→ 2, x 7→ 0.0001, rj 7→ 0, rw 7→ None)]

nach e = e+s{s 7→ 1, e 7→ 2, n 7→ 2, x 7→ 0.0001, rj 7→ 0, rw 7→ None)]

. . .

Bemerkung 3.4.17 Man sieht, dass Zuweisungen bzw. allein die Vermutung,dass welche erfolgen konnten, die Parallelisierbarkeit von Programmen verhin-dert.

Die Reihenfolge der Auswertung in Python spielt eine große Rolle und kannnicht verandert werden, ohne die Bedeutung der Programme zu andern.

In Python wird das Ausmaß der Seiteneffekte etwas zuruckgedrangt, auchwenn es noch ein normales Mittel der Programmierung ist.

3.4.5 Anzahl Auswertungsschritte mit Funktionsanwen-dungen und return

Die Anzahl der Auswertungsschritte einer Auswertung kann man auf Program-me mit Funktionsanwendung erweitern, wenn man vereinbart, dass returnselbst als eine Auswertung zahlt, wahrend das Uberspringen der nachfolgen-den Anweisungen, bis die Funktion verlassen wird, nicht mitzahlt. Ebenso zahltdie Initialisierung von internen Variablen nicht mit.

Beispiel 3.4.18 Anzahl der Schritte am Beispiel der rekursiven Fibonacci-Funktion.

def fib(n):if n == 0:

return 0elif n == 1:

return 1else: return (fib(n-2) + fib(n-1))

Anzahl Schritte fur fib(3):

Page 29: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 29

Anzahl Schritte welche Auswertung1 fib(3)-Aufruf1 if- Aufruf2 n == 01 if- Aufruf2 n == 11 return- Aufruf1 +- Aufruf2 n-2-Auswertung1 fib-Aufruf (fib(1))1 if- Aufruf (rekursiv in fib(1))2 n == 01 if- Aufruf2 n == 11 return- Aufruf: fib(1) zu Ende2 n-2-Auswertung1 fib-Aufruf (fib(2)). . . . . .

3.4.6 Argumentieren mittels operationeller Semantik

Man kann die Definition der operationellen Semantik verwenden, um Beweiseuber Python-Programme zu fuhren. Als Beispiel nehmen wir die Funktion, diedie Fibonacci-Zahlen berechnet.

def fibw (n):a,b = 0,1while n > 0:print b,a,b = b,a+bn = n-1

Wir zeigen: fibw(n) druckt Fibn als letzten Wert, unter der Voraussetzung,dass n > 0 und kein Uberlauf eintritt.

Man braucht noch eine Klarstellung, die nicht ganz offensichtlich ist: DieOperationen, die das Programm ausfuhrt, sind konzeptuell etwas anderes alsdie mathematischen Operationen. Wir konnen aber annehmen, dass nach einerUbersetzung der internen Datenstrukturen in mathematische Objekte sich diePython-Operatoren +,−, ∗, /, > wie die mathematischen Operationen verhal-ten, insbesondere da wir angenommen haben, dass kein Uberlauf eintritt.

Der Nachweis geschieht mit vollstandiger Induktion nach Anzahl derDurchlaufe des while-Rumpfs. Eigentlich muss man zunachst beweisen, dassdie Schleife terminiert. Das ist aber einfach, da der Wert n immer kleiner wird.Man braucht eine Idee, was man beweisen will. I.a. braucht man dafur einesogenannte Schleifeninvariante. Hier kann man diese leicht raten und erhalt:wenn man den Zeitpunkt vor Durchlauf der while-Schleife nimmt, und mit m

Page 30: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 30

den Wert der Eingabe bezeichnet:

a = Fibm−n, b = Fibm−n+1

Diese Schleifeninvariante wird jetzt mit vollstandiger Induktion nachgewiesen:Die Induktion nach Anzahl der Schleifendurchlaufe ergibt jetzt:

Basisfall m = n : a = Fibn−n = Fib0 = 0 stimmt. Und b = Fib1 = 1 stimmt.Induktionsfall: Wenn n > 0: Die Schleife wird durchlaufen.(Wir schreiben a, b, n fur die alten Werte.)print b andert den Zustand nicht.a,b = b,a+b ergibt: a = b, b = a + bn = n-1 ergibt n = n− 1.Vor der Schleife galt:

a = Fibm−n, b = Fibm−n+1

Wir berechnen a = b = Fibm−n+1 = Fibm−n. Das ist der erste Teil der Schlei-feninvariante.Die Berechnung fur b ergibt: b = a + b = Fibm−n + Fibm−n+1

Einsetzen des veranderten Index n ergibt: b = Fibm−n−1 + Fibm−n. Nach derDefinition der Fibonacci-Zahlen ist das gerade b = Fibm−n−1 + Fibm−n =Fibm−n+1.

Damit ist die Behauptung mit Induktion bewiesen. Wir mussen noch argu-mentieren, was das letzte gedruckte b ist. Offenbar ist dann n = 1. Es ergibtsich, dass b = Fibm−1+1 = Fibm gedruckt wurde.

Beispiel 3.4.19 Dieses Beispiel soll zeigen, dass man auch in Python mit ei-nem let doppelte Auswertungen vermeiden kann.

Die Funktion f(x, y) := x(1 + xy)2 + y(1 − y) + (1 + xy)(1 − y) laßt sicheffizienter berechnen, wenn man definiert:

a := 1 + xyb := 1− yf(x, y) := xa2 + yb + ab

Die entsprechende Definition sieht so aus:

def polyf(x,y):a = 1 + x *yb = 1 - yreturn (x * a**2 + y* b + a*b)

3.4.7 Lokale Funktionen und geschachtelten Gultigkeits-bereiche

Python erlaubt in der Version 2.3 geschachtelte Funktionsdefinitionen, d.h. sol-che, die innerhalb einer Funktionsdefinition gemacht werden und auf die lokalenVariablen der Ober-Funktion zugreifen durfen. Hier gibt es Beschrankungen beiZuweisungen: Externe Variablen (außer globale) durfen nicht verandert werden,aber sie durfen referenziert werden.

Page 31: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 31

Beispiel 3.4.20

def f(x):def g(y):

return x+y;return g(11);

Der Aufruf f(4)ergibt 15. Hier ist die textuelle Sichtbarkeit (lexikalischerGultigkeits-Bereich) entscheidend. Der Aufruf f(11) hat als Ruckgabewert denWert von g(11). Im Rumpf von g wird x+y ausgewertet: Da g die Variable xvon f sieht, ergibt sich 4+11, d.h. 15.Bei folgender unabhangiger Definition von f und g ergibt sich ein Fehler.

def f(x):return g(11);

def g(y):return x+y;

Der Grund ist, dass g die lokale Variable x von f nicht sieht. Das Prinzip,das dahintersteht ist der lexikalische Gultigkeits-Bereich von Variablen.Die Semantik von Funktionen soll erhalten bleiben, wenn man die formalen undlokalen Parameter einer Funktion nur in dieser Funktion umbenennt. Wurde derexterne Zugriff auf x oben noch funktionieren, so ware das nach Umbenennungnicht mehr moglich:

def f(z):return g(11);

def g(y):return x+y;

Die allgemein verwendete Variante der Variablensichtbarkeit ist der lexika-lische Gultigkeitsbereich, d.h., man darf nur Variablennamen verwenden, die imProgrammtext auf derselben oder einer hoheren Stufe verwendet werden.

Die andere Methode, die oben im Beispiel nicht funktionierte, und die manin Ausnahmefallen (mit global-Definition) verwendet, allerdings so, dass mansie wieder als Variante der lexikalische Sichtbarkeit sehen kann, nennt man dy-namischen Gultigkeitsbereich der Variablen (hier x). Die Funktion g sieht x inder Ablaufumgebung und versucht diese zu andern.

In Python wird das Prinzip fur globale Variablen verwendet, nachdem sieals solche deklariert wurden. Diese Methode hat ohne extra Deklaration schwer-wiegende Nachteile, da sie die theoretische Behandlung der Bedeutung von Pro-grammen sehr schwierig macht und zudem Namensabhangigkeiten einfuhrt.

Der Effekt der Auswertung in Python bei geschachtelten Funktionsdefinitio-nen kann so beschrieben werden:

• Wird ein undeklarierter Name referenziert, d.h. der Wert abgefragt, dannwird im nachsthoheren Gultigkeitsbereich gesucht.

Page 32: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 32

• Wird im innersten Gultigkeitsbereich ein Variablenname definiert, kannman nicht mehr auf den gleichen Variablennamen im hoheren Gultigkeits-bereich zugreifen, auch wenn man den ganz unten definierten Variablen-namen wieder loscht (mit del)

• Variablennamen aus hoheren Gultigkeitsbereichen konnen nicht geloschtwerden (mit del) und sie konnen auch nicht neu gebunden werden. (mitx = ...), ein solches Assignment fuhrt dazu, dass eine neue lokale Deklara-tion angelegt, und gleichzeitig auch dazu, dass samtliche Anweisungen imselben Gultigkeitsbereich, die x referenzieren, sich nur auf diesen Gultig-keitsbereich beziehen

Wir betrachten noch ein Beispiel:

Beispiel 3.4.21

def fff():v = 2;def ggg():

v = 3;return hhh ();

def hhh():return v

return ggg();

Hier ist die Frage, welchen Wert fff()zuruckgibt: 2 oder 3? Analyse ergibt,dass der lexikalische Gultigkeitsbereich der lokalen Variablen v der Rumpf vonfff ist außer dem Rumpf von ggg, ebenso der Rumpf von hhh. Damit ergibtsich 2 als Wert.

Betrachten wir das Prinzip der Umbenennung und das Python-Prinzip derLokalisierung aller veranderbaren Variablen, dann ist das v in ggg lokal in ggg,also kann man folgendermaßen umbenennen:

def fff():v = 2;def ggg():

w = 3;return hhh ();

def hhh():return v

return ggg();

Dann ergibt fff() immer noch 2.

3.5 Listenverarbeitung in Python

Es gibt Datentypen, die Sequenzen von Objekten implementieren: Tupel, Listenund Strings. Tupel und Listen werden analog zu Haskell dargestellt. Z.B. (1, 2, 3)

Page 33: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 33

ist das 3-Tupel aus den Zahlen 1,2,3, und [1, 2, 3] die Liste dazu. Tupel sind inder Lange festgelegt, wahrend Listen ihre Lange andern konnen.

Es gibt in Python die Listenfunktionen map, +, filter, und reduce, dievordefiniert sind. Sie haben analoge Ergebnisse und Funktionalitt wie die Funk-tionen map, ++, filter, und foldl in Haskell.Beachte: Die Listenfunktionen in Prfixschreibweise verndern die Liste nicht,whrend die Listenfunktionen in Attributschreibweise die Liste verndern knnen.Hier ein Auszug aus einer Dokumentation zu Python.

Operationen auf allen Arten von Folgen (Listen, Tupel, Strings) 2

Operation Resultat Bem.x in s True wenn ein Eintrag von s = x, sonst Falsex not in s False wenn ein Eintrag von s gleich x, sonst Trues + t Konkatenation von s und ts ∗ n n Kopien von s aneinander gehangts[i] i-es Element von s, Start mit 0 (1)s[i : j] Teilstuck s ab i (incl.) bis j (excl.) (1), (2)len(s) Lange von smin(s) Kleinstes Element von smax(s) Großtes Element von s

Bemerkungen

(1) Wenn i oder j negativ ist, dann ist der Index relativ zum Ende des String,d.h. len(s)+i oder len(s)+j wird eingesetzt. Beachte, dass -0 = 0.

(2) Das Teilstuck von s von i bis j wird definiert als die Folge von Elementenmit Index k mit i <= k < j. Wenn i oder j großer als len(s) sind, nehmelen(s). Wenn i weggelassen wurde, nehme len(s). Wenn i ≥ j, dann ist dasTeilstuck leer.

Operation auf Listen (mutable sequences)3

Operation Resultat Bemerkungens[i] = x item i von s ersetzt durch xs[i : j] = t Unterliste s von i bis j ersetzt durch tdel s[i : j] dasselbe wie s[i : j] = []s.append(x) dasselbe wie s[len(s) : len(s)] = [x]s.extend(x) dasselbe wie s[len(s) : len(s)] = x (5)s.count(x) Return: Anzahl der i mit s[i] == xs.index(x) Return: kleinstes i mit s[i] == x (1)s.insert(i, x) dasselbe wie s[i : i] = [x] wenn i >= 0s.remove(x) dasselbe wie del s[s.index(x)] (1)s.pop([i]) dasselbe wie x = s[i]; del s[i]; return x (4)s.reverse() Umdrehen von s in place (3)s.sort([cmpFct]) Sortiere s in place (2), (3)

2Aus Quick-Reference http://rgruet.free.fr/#QuickRef3Aus Quick-Reference http://rgruet.free.fr/#QuickRef

Page 34: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 34

Kommentare:

(1) Ergibt eine ValueError-Exception, wenn x nicht in s, d.h. wenn der Indexaußerhalb des gultigen Bereichs ist.

(2) Die sort()-Methode nimmt ein optionales Argument, das eine Vergleichs-funktion fur 2 Argumente ist, die -1, 0, or 1 als Wert ergeben muss,abhangig davon, ob das erste Elemente kleiner als, gleich oder großer alsdas zweite Argument sein soll. Beachte dass dadurch das Sortieren sehrviel langsamer wird.

(3) Die sort() und reverse()-Methoden andern die Liste in-place. Es gibt keinenRuckgabewert zur Warnung vor diesem Seiteneffekt.

(4) Die pop()-Methode wird nur fur Listen unterstutzt. Das optionale Argu-ment i wird automatisch als -1 definiert, so dass normalerweise das letzteElement entfernt und zuruckgegeben wird.

(5) Ergibt eine TypeError-Exception, wenn x kein Listenobjekt ist.

Weitere Funktionen, die nicht in Attributschreibweise angewendet werden:Operation Resultatmap(f,s) Liste [f s[0], f s[1], . . . ]filter(p,s) Liste der Elemente mit p(s[i]) = Truereduce(op,s) s[0] op s[1] op . . .reduce(op,s,init) Dasselbe wie init op (reduce(op,s))zip(s,t) Liste der Paare der Elemente von s,t.

Der Ruckgabewert (return . . . ) der Funktionen entspricht im allgemeinendem der Haskellfunktionen. Bei Seiteneffekten haben die Listen-Funktionen vonPython ein ganz anderes Verhalten als in Haskell. Haskell ist referentiell trans-parent, was bewirkt, dass die Listenargumente nach einer Funktionsanwendungunverandert sind, wahrend eine Funktionsanwendung bzw. Operatoranwendungin Python oft eine Veranderung der Listenstruktur, und damit der Werte vonArgumentvariablen nach sich zieht. Einige Funktionen sind nur dazu gemacht,genau dies zu bewirken. Z.B. reverse dreht die Python-Liste um.Wir geben zur Listenverarbeitung in Python einige Beispiele an:

>>> range(20)[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]>>> range(5,10)[5, 6, 7, 8, 9]>>> len(range(10))10>>> len(range(1000000))Traceback (most recent call last):File "<input>", line 1, in ?

MemoryError

Page 35: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 35

>>>len(xrange(1000000))1000000>>> a = [’a’, ’b’,’c’]>>> a[’a’, ’b’, ’c’]>>> b = [3,4,5]>>> a.extend(b)>>> a[’a’, ’b’, ’c’, 3, 4, 5] ## a ist modifiziert>>> b[3, 4, 5]>>> a.append(b)>>> a[’a’, ’b’, ’c’, 3, 4, 5, [3, 4, 5]]>>> a.reverse()>>> a[[3, 4, 5], 5, 4, 3, ’c’, ’b’, ’a’]>>> b[3, 4, 5] ## b bleibt>>> b.reverse()>>> a[[5, 4, 3], 5, 4, 3, ’c’, ’b’, ’a’] ## a ist modifiziert!

Variablen, die in einem Aufruf f(t1, . . . , tn) nicht erwahnt werden, konnentrotzdem nach der Auswertung dieses Aufrufs andere Werte haben. Das passiertdurch sogenanntes aliasing: der gleiche Speicherbereich hat verschiedene Namen.

Schleifen in Python

Schleifen kann man in Python programmieren mittels while oder for: Hier einBeispiel fur for:

>>> for i in range(1,11):... print(i)...12345678910>>>

Page 36: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 36

Beispiel zu vordefinierten Listenfunktionen

Beispiele zu den Listenfunktionen map, +, filter, und reduce, die analog zumap, ++, filter, und foldl in Haskell sind.Folgende Funktion listcopy erzeugt eine Kopie einer Liste:

def id(x): return x

def listcopy(x):return map(id, x)

def geradeq(n):return n%2 == 0

>>> filter(geradeq,range(20))[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]>>> map(quadrat,range(1,10))[1, 4, 9, 16, 25, 36, 49, 64, 81]>>>[1,2,3]+[11,12,13]>>>[1,2,3,11,12,13]

Die Summe aller Elemente einer Liste kann man ebenfalls analog zu denHaskell-Methoden berechnen:

def add(x,y): return x+y

>>> reduce(add,range(100))4950def mal(x,y): return x*ydef fak(n): return reduce(mal,range(1,n+1))

Alias-Namens-Problematik

Alias-Namens-Problematik (Aliasing): Der gleiche Speicherbe-reich wird von mehreren Variablen (-Namen) referenziert.Effekte: Der Wert (bzw. das Datenobjekt) zu einer Variablen x kannsich im Laufe der Programmbearbeitung verandern, ohne dass dieseVariable im Programm in einem Befehl, der den Speicher verandert,vorkommt.Allgemeiner kann auch ein Objekt unter dem Namen A erreichbarsein, und Teile des Objekts unter anderen Namen.

Das bedeutet, dass der einfache Schluss:B wurde im Aufruf nicht erwahnt, also ist es nach der Auswertung des Aufrufsnicht verandert! falsch sein kann.Ein weiteres Beispiel fur die Effekte der Alias-Namens-Problematik:

Page 37: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 37

>>> a = [3,2,1]>>> b = a>>> a[3, 2, 1]>>> b[3, 2, 1]>>> a.sort()>>> a[1, 2, 3]>>> b[1, 2, 3]

Um sich ein Bild von verketteten Listen zu machen, ist folgendes Darstel-lungsweise von Listen in einem Diagramm hilfreich, das die Situation nach derZuweisung a = [1,2,3]; b = a reprasentiert.

• Pfeile ausgehend von Namen bedeuten Bindungen: a 7→ . . .. Andere Pfeilekann man ebenfalls als Bindung ansehen, nur sind die Namen implizit.

• Eine Box reprasentiert ein Paar von zwei impliziten Namen. In einerImplementierung wird man das als Adressen (Zeiger) darstellen.Der erste Pfeil von der linken Zelle einer Box ausgehend ist die Bindungdes Listenelementes.Der zweite Pfeil ist die Bindung an die Restliste (Liste ohne das ersteElement).

• Die zweite Zelle der letzten Box symbolisiert einen Zeiger auf die leereListe (Nil).

a

1 2 3

b

Nil

Es ist zu beachten, dass bei a.extend(b) eine Kopie der Liste b an a an-gehangt wird. Bei append wird nur der Verweis genommen:

>>> a = [1,2,3]>>> b = [4,5,6]>>> a.append(b)>>> a[1, 2, 3, [4, 5, 6]]>>> b.reverse()

Page 38: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 38

>>> a[1, 2, 3, [6, 5, 4]]>>>

Bild des Speichers nach den obigen Befehlen:a

1 2 3

b

6 5 4

Nil

Nil

Python nutzt die Flexibilitat der Listendarstellung nicht aus, sondern eswird intern optimiert. D.h. Man hat Listenobjekte, die keine Teillisten haben,so dass man eigentlich ein anderes Diagramm verwenden konnte:

>>> a = [1,2,3]

a

1 2 3

Nil

>>> b = [4,5,6]>>> a.append(b)>>> a[1, 2, 3, [4, 5, 6]]>>> b.reverse()>>> a

Page 39: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 39

[1, 2, 3, [6, 5, 4]]>>>

a

b

1 2 3

6 5 4

Nil

Nil

Stacks und Queues

Ein Stack (bzw. Keller, Stapel) ist eine oft verwendete Datenstruktur. In Py-thon kann man einen Stack leicht implementieren, man kann sogar eine Listevon beiden Seiten als Stack verwenden:push e auf Stack a: a.insert(0,e)Benutzung der Listen von links:

a.insert(0,b) ##push(b)a.pop(0)

Benutzung der Listen von rechts:

a.append(b)a.pop()

Es tritt ein Fehler auf, wenn man vom leeren Stapel etwas herunternehmenwill.

Beispiel 3.5.1 Ein Beispiel, das sich eines Stacks bedient, ist die Verwendungeines Umgebungsstacks zur Auswertung von (Python-)Ausdrucken. Das Umdre-hen einer Liste wurde schon im Haskell-Kapitel besprochen.

Page 40: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 07.01.2005 40

Eine Schlange, Queue ist eine Datenstruktur, die sich wie eine Warteschlan-ge verhalt: Vorne wird bedient (abgearbeitet), und hinten stellen sich Leute an(first-in first out, fifo).

In Python kann man das effizient implementieren mit einer der folgendenKombination von Operationen.

a.insert(0,b), a.pop() oder a.append(b), a.pop(0).

3.5.1 Auswertungsregeln fur Listenfunktionen in Python:operationale Semantik

Dieser Paragraph soll verdeutlichen, dass man die operationale Semantik der Li-stenverarbeitung in einer imperativen Programmiersprache wie Python formaldarstellen kann, wobei man den Speicher nur als abstrakte Schnittstelle benotigt,Naturlich wird dies dann letztlich doch auf einen adressierbaren Speicher ab-gebildet, aber man kann danach konzeptuell trennen zwischen den minimalenErfordernissen der Auswertung und den zufalligen Eigenschaften der Implemen-tierung. Z.B. ist die Große der Adressen oder die Lokalitat eher eine Eigenschaftder Implementierung auf einem Rechner mit linearem Speicher.

Der Heap (Halde) dient dazu, Daten mit Struktur, die vom Programmmodelliert werden, darzustellen, und deren Veranderung korrekt zu beschreiben.

Dazu werden Heap-Variablen verwendet, deren Zweck es ist, Zeiger abstraktzu modellieren und Objekte bereitzustellen, die vom Programm aus referen-ziert werden konnen. 4. Um auch die Problematik der nicht mehr erreichbarenSpeicherteile korrekt zu behandeln, fuhren wir zusatzlich noch den Begriff derWurzelvariablen ein. Die Wurzelvariablen haben nur den Zweck, den garbage-collector zu unterstutzen.

Definition 3.5.2 Eine Halde (Heap) besteht aus folgenden Komponenten:

• Einer Menge VH von Heap-Variablen, d.h. einer Menge von Namen.

• Einer Menge von HH-Bindungen an Heapvariablen, wobei zwei Arten vonHH-Bindungen vorkommen:

– x 7→ v: eine HH-Bindung eines Wertes v (Zahl, Character oder Nil)an die Heap-Variable x, oder x 7→ ⊥ , x 7→ None.

– x 7→ [x1|x2]: eine HH-Bindung eines Paares (einer Box) aus zweiHeap-Variablen [x1|x2] an die Heap-Variable x.

Folgende Zusatzbedingungen mussen erfullt sein:Es darf pro Heap-Variable nur eine HH-Bindung im Heap sein.Jede Heap-Variable, die in einer Box vorkommt, muss auch eine HH-Bindungim Heap haben.

Meist werden einige Heapvariablen als Wurzel-Variablen markiert. Die Men-ge der Wurzelvariablen nennen wir VW ; wobei VW ⊆ VH gelten muss.

4Wie bei anderen Namen ist die Implementierung sinnvoll als Zeiger.

Page 41: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 41

Die beiden Formen der HH-Bindung kann man folgendermaßen darstellen:

x

v x1 x2

x

Jede Heapvariable reprasentiert ein komplexes Objekt, das man als gerich-teten Graphen sehen kann, wobei nur binare Verzweigungen vorkommen unddie Daten an den Blattern sind. Eine Programmvariable z wird dann durch die(PH-) Bindung {z 7→ x}, an ein Objekt gebunden, wobei x die Heapvariable ist,die das Objekt reprasentiert.

Wir wiederholen die Definition der Bindungen und Umgebung zu Program-men (siehe Def. 3.4.1), wobei statt Zahlen und Zeichen auch Objekte aus einerHalde verwendet werden.

Definition 3.5.3 Gegeben eine Halde H. Eine (Programmvariablen- bzw. PH-) Bindung (bzgl. der Halde H) ist ein Paar x 7→ y, wobei x eine Programm-Variable und y eine Heap-Variable ist.

Ein Umgebungsrahmen (Frame) zu einer Halde H ist eine endliche MengeR von PH-Bindungen zur Halde H. Pro Programm-Variablennamen kann nureine PH-Bindung in R enthalten sein.

Eine Umgebung (Variablenumgebung, Zustand) ist ein Tripel (R1, R2,H)von zwei Umgebungsrahmen, des lokalen Umgebungsrahmens R1 bezuglich derHalde H, und des globalen Umgebungsrahmens R2 bezuglich der Halde H; undder Halde H.

Wir nehmen an, dass die Wurzel-Variablen von H mindestens die Heap-Variablen sind, die in den Variablenumgebungen R1, R2 als Ziele vorkommen.

Oft ist die Umgebung als Stack organisiert, der die Auswertung von Aus-drucken unterstutzt. Unsere Modellierung erlaubt keine Werte direkt auf demStack. In realen Implementierung werden zur Optimierung oft Werte direkt aufdem Auswertungsstack abgelegt und verarbeitet, da dies effizienter ist.

Beispiel 3.5.4 Die Darstellung der Liste [100] nach dem Aufruf z1 = [100]mit Umgebung und Halde ist folgendermaßen:

lokaler Umgebungsrahmen {z1 7→ z2}globaler Umgebungsrahmen ∅Halde {z2 7→ [z3|z4]

z3 7→ 100z4 7→ Nil}

Page 42: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 42

z2

z3 z4

100 Nil

z1

Beispiel 3.5.5 Darstellung einer zyklischen Liste, wobei man diese nur sodrucken kann: [[[[[...]]]]]]

AA AB

NilDie Halde dazu sieht so aus: {AA 7→ [AA|AB], AB 7→ Nil}

Definition 3.5.6 Die ErreichbarkeitsrelationH,∗−→ des Heaps H kann man fol-

gendermaßen definieren:Eine Heap-Variable y ist von der Heap-Variablen x aus erreichbar, Notation

xH,∗−→ y, wenn x = y, oder wenn es eine endliche Folge von Variablen x1, . . . , xn

gibt, so dass x = x1 und y = xn und fur alle i = 1, . . . n − 1 entwederxi 7→ [xi+1|x′i+1] ∈ H oder xi 7→ [x′i+1|xi+1] ∈ H wobei x′i+1 irgendwelcheHeap-Variablen sein durfen.

Eine Heap-Variable x ist erreichbar (aktiv), wenn es eine Wurzelvariable x0

gibt mit x0

H,∗−→ x, andernfalls heißt sie unerreichbar (redundant).

Bemerkung 3.5.7 Die ErreichbarkeitsrelationH,∗−→ des Heaps H kann man

auch als die reflexiv-transitive Hulle der folgenden Relation definieren:

Wenn x 7→ [x1|x2] ∈ H, dann gilt xH,∗−→ x1 und x

H,∗−→ x2.

Die Bindungen von unerreichbaren Variablen kann man aus dem Heap ent-fernen. In einer Implementierung nennt man das garbage collection. Wurzelva-riablen sind i.a. die Heap-Variablen, auf die vom Stack aus referenziert wird unddie Heap-Variablen, auf die das Programm referenziert.

Page 43: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 43

Jede Variable der Halde reprasentiert einen Basiswert oder eine komplexereDatenstruktur. z.B.: In der Halde H = {x 7→ [x1|x2], x1 7→ 1, x2 7→ Nil)}reprasentiert die Halden-Variable x die Liste [1] und x2 die leere Liste.

x1 x2

x

1

Nil

Regeln der (erweiterten) operationalen Semantik

Der Aufwand dafur ist etwas hoher als bei der ersten Variante der operationalenSemantik von Python, die nur mit Zahlen umgehen konnte, da auch zyklischeStrukturen beschreibbar sein sollen.

Der Zustand besteht aus Bindungsumgebung und Heap. Die ubliche Umge-bung wird so verallgemeinert, dass jeder Programmvariablen eine Heapvariablezugeordnet wird, die ihrerseits auf einen (evtl. komplexeren) Wert im Heap ver-weist.

Jetzt konnen wir die Auswertung von Listenausdrucken und Listenfunktio-nen in Python mittels dieser Halden-Modellierung beschreiben: Wir verwendendie Klammer [[.]], um syntaktische Konstrukte einzuklammern, damit diese vonden Operationen deutlich getrennt werden, und verwenden die Notation

(Z, s) →e (Z ′, x)

um eine Auswertung des Ausdrucks s im Zustand Z zu bezeichnen, wobei x dieHeapvariable ist, die den zuruckgegebenen Wert reprasentiert und Z ′ der neueZustand ist.

Definition 3.5.8 Eine Auswertung des Listenausdrucks [a1, . . . , an] in einerHalde H folgt der Regel (n ≥ 1):

(Z; [[a1]]) →e (Z1;x1) (Z1; [[[a2, . . . , an]]]) →e (Z2;x2) Z2 = (R2,1, R2,2,H2)(Z; [[[a1, . . . , an]]]) →e

((R2,1, R2,2,H2 ∪ {x 7→ [x1|x2]});x

)Am Ende der Listen verwenden wir die spezielle Konstante Nil:

((R1, R2,H); [[[]]]) →e ((R1, R2,H ∪ {x 7→ Nil});x)

Die Auswertung des Listenausdrucks [a1, . . . , an] kann man so umschreiben:Zuerst wird a1 ausgewertet, danach a2 usw., bis an. Der Zustand am Endeist der letzte Zustand nach dieser Auswertung. In der Halde reprasentiert dieHaldenvariable x die ausgewertete Liste.

Page 44: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 44

Die Alias-Namens-Problematik tritt jetzt auch bei Zuweisung von Listenvaria-blen auf.Sei die Folge der Befehle gegeben:

a = [1,2]b = a

Dann entsteht folgende Struktur in der Umgebung und in der Halde: Nacha = [1,2]:

Bindungen: . . . , a 7→ u1, . . .Heap: {u1 7→ [u2|u3], u2 7→ 1, u3 7→ [u4|u5], u4 7→ 2, u5 7→ Nil, . . .}

Nach der weiteren Zuweisung b = a entsteht der Zustand

Bindungen: . . . , a 7→ u1, . . . , b 7→ u1, . . .Heap: {u1 7→ [u2|u3], u2 7→ 1, u3 7→ [u4|u5], u4 7→ 2, u5 7→ Nil, . . .}

Wenn jetzt innerhalb der Liste zu a etwas verandert wird, dann andert sichautomatisch etwas in der Liste, auf die b zeigt.

u1

u2 u3u4 u5 Nil

a

b

1 2

Jetzt konnen wir auch die Auswertung von Funktionen wie len, append,insert, . . . angeben. Fur die folgende Regel nehmen wir der Einfachheit halberan, dass a, b Programm-Variablen sind.

((R1, R2,H); [[a.append(b)]]) →e ((R1, R2,H′); None)

Wobei H = H0 ∪ {x 7→ Nil} der Heap vor der Auswertung ist. xsei letzte Heap-Variable der Liste zu a die auf Nil zeigt, b 7→ x1 istBindung in der Umgebung R1, undH ′ = H0 ∪ {x 7→ [x1|x2], x2 7→ Nil} mit der neu erzeugten Heap-Variablen x2.

D.h. die x-Bindung {x 7→ Nil} im Heap wird ersetzt durch {x 7→ [x1 | x2]},wobei x2 neu eingefuhrt wird.

Die Auswertung von a.append(b) kann man sich so veranschaulichen:

Page 45: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 45

Vorher:u1

Nila

b

x

x1 w

Nachher:

u1a

b

x

x1

Nilx2

w

x1

Dadurch wird (die Liste) b als letztes Element an die Liste angehangt.

Durch append-Anwendungen kann auch eine Liste entstehen, die unendlich tie-fe Verschachtelung hat, indem man z.B. a.append(a) ausfuhrt. Die zugehorigeHalde fur die Liste a = [1, 2] sieht so aus:

{u1 7→ [u2|u3], u2 7→ 1, u3 7→ [u4|u5], u4 7→ 2, u5 7→ [u1|u7], u7 7→ Nil, . . .}

Diese Halde ist ein Graph, der Zyklen hat. Man sieht auch, dass im Diagrammu1 zweimal vorkommt, aber die zwei Pfeile der gleichen Bindung entsprechen.

u1aNil

u2 u3 u4 u5 u1 u7

1 2

Als weiteres Beispiel die operationelle Semantik der Funktion insert. Furdiese Regel nehmen wir ebenfalls an, dass a, b Programm-Variablen sind.

((R1, R2,H); [[a.insert(i, b)]]) →e ((R1, R2,H′); None)

wobei xi die Heap-Variable zum i-ten Listen-Tail ist. H = H0 ∪ {xi 7→[ei+1|xi+1]}, b 7→ y1 ∈ R1, und H ′ = H0 ∪ {xi 7→ [y1|y2], y2 7→ [ei+1|xi+1]},wobei y2 neue Heap-Variable ist.

Page 46: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 46

ei xi ei+1 xi+1

ei xi ei+1 xi+1

nach insert:

y1 y2

...

...

vorher:

Der Fall, dass die Liste zu Ende ist, geht analog: H = H0 ∪ {xi 7→ Nil},b 7→ z1 ∈ S, und H ′ = H0 ∪ {xi 7→ [z1|z2], z2 7→ Nil}.

Die operationelle Semantik von map kann damit ebenfalls beschrieben wer-den, allerdings ist diese Funktion aus kleineren Operationen zusammengesetzt,so dass es einfacher ware, fur die Implementierung der Funktion map die opera-tionale Semantik der kleineren Operationen zu beschreiben.Zum Beispiel, indem man ersatzweise die folgende Funktion betrachtet.

def map_demo(f,xs):if len(xs) == 0:

return [];else:

wert = map_demo(f, xs[1:len(xs)]);wert.insert(0,f(xs[0])); ## Seiteneffektreturn wert;

Den Inhalt des Elements mit Index i der Liste a kann man mittels

a[i] = b

verandern.Wir geben hierfur die operationelle Semantik an, wobei wir annehmen, dass beine Programmvariable ist, und bei der Auswertung von Anweisungen rechtsnur den veranderten Zustand angeben.

((R1, R2,H); [[a[i] = b]]) → (R1, R2,H′)

wobei H = H0 ∪ {xi 7→ [y1|y2]}, xi ist die Heap-Variable zum i-ten Listen-Tail,b 7→ z ∈ R1, und H ′ = H0 ∪ {xi 7→ [z|y2]}.

Page 47: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 47

Indirektionen

Diese konnte man auch in der Halde erlauben. Es sind Bindungen von der Artx1 7→ x2, wobei x1, x2 beides Heap-Variablen sind. Deren Bedeutung ist, dass derWert von x1 erst uber die Referenz x2 und evtl. weitere Indirektionen gefundenwerden kann, wobei die Indirektionen fur die Programmiersprache nicht sichtbarsind.

Das formale Modell wurde dadurch etwas komplizierter, da man den Wertnicht direkt finden kann. Man muss auch Indirektionszyklen beachten, die eineNichtterminierung des Wertesuchens bewirken konnten.

In Implementierungen werden Indirektionen z.T. verwendet, aus verschie-denen Grunden: manche Operationen lassen sich leichter implementieren bzw.formulieren, und manchmal ist es effizienter, Indirektionen zu verwenden.

3.5.2 Implementierung der Halde in einem Speichermo-dell

Zunachst das Speichermodell in dem die Halde implementiert werden soll.

Definition 3.5.9 Einen RAM-Speicher S kann man definieren als endlicheFolge von Bytes (1 Byte = 8 Bit), (alternativ: von Zahlen zwischen 0 und28 − 1 = 255), nummeriert mit Indices 0, . . . , L − 1. L ist die Große des Spei-chers.Der gespeicherte Wert unter der Adresse i ist das Byte S(i), d.h. das i − teElement der Folge, wenn die Zahlung mit 0 beginnt.Die benotigte Adresslange in Bits ist dlog2(L)e.Die Zugriffsfunktion get(S, i, l) hat als Ergebnis die Subfolge von S der Lange lab i.

0 1 2 3 4 5

00101001

Eine Implementierung von Adressen eines Speichers mit Adresslange 32 Bitkann man durchfuhren, indem man eine Binarzahl, die durch eine Folge von 4Bytes reprasentiert wird, als Adresse im gleichen Speicher interpretiert.

Heapvariablen kann man dann implementieren als Folge von 4 By-tes (auch Wort genannt), die im Speicher direkt hintereinanderliegen, d.h.S(i), S(i + 1), S(i + 2), S(i + 3). Diese werden als Adressen interpretiert.

Ein Paar [x1|x2] von zwei Heapvariablen kann man als hintereinanderliegen-de Folge von 8 Bytes implementieren, d.h. S(i), . . . , S(i + 7). Eine Zahl kannman implementieren wie eine Adresse, nur wird der Wert als die binare Zahlselbst interpretiert.

Page 48: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 48

Beispiel 3.5.10 Hat man z.B. (mit Programmvariable b, Heapvariable x) eineBindung b 7→ x und x 7→ 4, so kann man x als 4-Byte Adresse implementieren.Wenn an der Stelle mit der Adresse x die Adresse i steht, dann ist der Wertder Heapvariablen x ist dann der Wert unter dieser Adresse, d.h. S(i).

Hat x die Adresse 100, dann erhalt man durch a = get(H, 100, 4) die Adressei, die zu x assoziiert ist, get(H, i, 1) ergibt dann den Wert, der bei korrekterImplementierung dann 4 ist.

100

b

200

x

100

4

200

In dieser Implementierung geht die Information verloren, welcher Typ vonDaten gemeint ist. Das sieht man daran, dass man damit Indirektionen, Paare,Nil, und Zahlen nicht unterscheiden kann. Je nach Implementierung benotigtman daher i.a. 5 noch eine Markierung (englisch: Tag), die diese verschiedenenDaten im Speicher voneinander unterscheidet.D.h. man konnte vereinbaren:

Adresse: 1 Byte Markierung (z.B. binar 1), und 4 Byte Adresse.Paar: 1 Byte Markierung (z.B. binar 2), und 8 Byte Adressen.Ganze Zahl: 1 Byte Markierung (z.B. binar 3), und 4 Byte Zahl.Nil: 1 Byte Markierung (z.B. binar 4).kein Wert: 1 Byte Markierung (z.B. binar 5).

Durch diese Haldenimplementierung haben wir eine abstrakte Schnittstelleangedeutet, die auch anders implementiert sein kann, ohne dass sich dieFunktionalitat der Halde bzw. der Umgebung andert.

Z.B. braucht folgendes nicht zu gelten:

• die Adressen eines Adresspaares (einer Box) [x1|x2] liegen direkt neben-einander.

• Adressen sind als aufsteigende Folge von Bytes reprasentiert.

• Der nachste Eintrag im Array (Feld) hat Adresse des aktuellen Feldes +4.

Auch die Lange der Adressen ist nicht festgelegt.5Diese Markierung wird nicht benotigt, wenn ein Programm vorher weiß, was dort stehen

soll.

Page 49: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 49

3.6 Felder, Arrays in Python

In Python sind die Listen (und Tupel) gleichzeitig eindimensionale Felder, dieauch heterogen (d.h. mit Elementen unterschiedlichen Typs) sein durfen. Mehr-dimensionale Arrays und Matrizen kann man mit einem Array von Arrays, d.h.mit Listen von Listen modellieren.

Die interne Implementierung eines Feldes macht es moglich, eine (praktisch)konstante Zugriffszeit zu realisieren, wenn der Index bekannt ist. Allerdings istdie Handhabung eines Feldes im Programm etwas aufwandiger, da man beiZugriffen stets den Index berechnen muss und da man im allgemeinen ein Feldnicht so ohne weiteres verkleinern oder erweitern kann.

Folgend einige Beispiele:

Beispiel 3.6.1 Transposition einer 2-dimensionalen quadratischen Matrix inPython

def transpose(a,n):for i in range(n):for j in range(i+1,n):a[i][j], a[j][i] = a[j][i] , a[i][j]

return a

def testtranspose(n):b = range(n)for i in range(n):

b[i] = range(n)for i in range(n):print b[i];

c = transpose(b,n)print " "; print "Nach Transposition:"for i in range(n):print c[i]

Beispiel 3.6.2 Matrixmultiplikation fur quadratische Matrizen und Determi-nante einer Matrix

def matrixMult(a,b):

leng = len(a);c = range(leng);for i in range(leng):

c[i] = range(leng);for j in range(leng):

c[i] [j] = 0;for i in range(leng):

for j in range(leng):sum = 0

Page 50: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 50

for k in range(leng):sum = sum + a[i][k]*b[k][j]

c[i][j] = sumreturn c

def testmatmult(leng):a = range(leng);b = range(leng);print a;print b;for i in range(leng):a[i] = range(leng);b[i] = range(leng);for j in range(leng):

a[i] [j] = i;b[i] [j] = j;

print a;print b;return (matrixMult(a,b))

###Determinante einer Matrix: rekursiv, exponentiell\begin{verbatim}def determinante(a):n = len(a);if n <= 0:

return 1;else:if n == 1:

return a[0][0]else:

sum = 0;flip = -1;for i in range(n):flip = (-1)*flip;b = matrixcopy(a);for j in range(n):

b[j].pop(0);b.pop(i);sum = sum + flip*a[i] [0] * determinante(b);

return(sum);

Beachte, dass es unter Benutzung der Gauß-Elimination einen ALgorithmusmit Laufzeit O(n3) gibt.

Page 51: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 51

3.6.1 Operationale Semantik: C

C ist eine wichtige und verbreitete Programmiersprache fur maschinennahesProgrammieren, insbesondere Betriebssysteme. C-Compiler ergeben effizientenCode. Der Abstraktionsgrad von C bzw. der C-Programme ist zwar hoher als dervon Assembler-Sprachen, aber nicht so hoch wie der von Python oder Haskell.

Wir untersuchen die Eigenschaften der operationalen Semantik von C an-hand eines Konstruktes.

Aus Kernighan/Ritchie: Programmieren in C:

”Diese Regeln sind außerordentlich kompliziert, denn sie mussen miteiner Mischung von Funktionen alten und neuen Stils fertigwerden.Wenn moglich, sollten Mischungen vermieden werden.“

”Die Reihenfolge, in der Argumente bewertet werden, ist nicht fest-gelegt; man beachte, dass sich verschiedene Ubersetzer hierin unter-scheiden. Die Argumente und Funktionsbezeichner werden jedochvollstandig bewertet [d.h. ausgewertet], mit allen Nebenwirkungen,bevor die Ausfuhrung der Funktion beginnt...“

Ein anderes Zitat zum Versuch einer Andeutung der (funktionalen) operatio-nellen Semantik von C:

”. . . In anderen Worten, wenn das n-te Kode-Fragment durch dieFunktion fn reprasentiert wird, und s0 ist der Anfangszustand, dannist der Endzustand definiert durch

sn = fn(fn−1(. . . f1(f0(s0)) . . .))

Die Ausfuhrung der einzelnen Funktionen erlaubt . . . Parallelismus.“

Wir wollen hier mal explizit ein Code-Fragment untersuchen, dass wir imPrinzip ala Python analysieren konnen. Dazu schnell die Erklarung eines C-Operators:i++ bewirkt, wenn ausgewertet, den Seiteneffekt i := i+1 wobei i Programm-variable sein muss. Die Nebenbedingung ist: Wenn es in einem Ausdruck vor-kommt, dann ist der Return-Wert i, und der Wert danach ist i+1. Um dieoperationale Semantik hinzuschreiben, nehmen wir mal an, dass das auch inPython geht. Die operationale Semantik ist dann:

v = wert(i, Z) w = v + 1 Z ′ = update(i, w, Z)(Z; [[i++]]) →e (Z ′; v)

Zum Vergleich hier die operationale Semantik des Ausdrucks ++i, der sich voni++ nur dadurch unterscheidet, dass der Returnwert bereits i+1 ist:

v = wert(i, Z) w = v + 1 Z ′ = update(i, w, Z)(Z; [[++i]]) →e (Z ′;w)

Als Beispiel fur i++ betrachte das C-Code-Fragment

Page 52: Algorithmische Abstraktion mit Python fileKapitel 3 Algorithmische Abstraktion mit Python 3.1 Allgemeines zu Python Python ist eine imperative und objekt-orientierte Programmiersprache

Praktische Informatik 1, WS 2004/05, Kapitel 3, vom 11.01.2005 52

i = 7;j = i++ * i++

Informelle Begrundung fur den erwarteten Zustand danach:Die Multiplikation wertet i++ zweimal aus: Der Wert des linken Argument ist7, des rechten Argument 8, da es um 1 erhoht wurde, danach hat i den Wert 9.Der Wert von j ist 7 ∗ 8 = 56.

Zunachst mal eine Uberraschung beim Ausprobieren (aus Dissertation Ro-nald Moore).Der C-Code

i = 7;j = i++ * i++;

kompiliert in gcc hinterlasst den Zustand j = 49, i = 8 statt j = 56, i = 9!Erklarungsversuch: der Compiler versucht, die Doppelauswertung von i++ zuvermeiden und andert daher das Programm ab zu:

i = 7;j = let x = i++ in x*x;

bzw. in let-freier Schreibweise:

i = 7;x = i++;j = x*x;

Die operationale Semantik ergibt jetzt:Nach Auswertung von x: {x 7→ 7, i 7→ 8, . . .}. Danach wertet man x*x aus underhalt 49, wie vom gcc berechnet.

Fazit: Die Verwirrung, die beim Programmierer entsteht, wird bewirkt durcheine (optimierende) Programmtransformation, die die operationale Semantiknur erhalt, wenn die Unterausdrucke keine Seiteneffekte bewirken; im Fall, dassdie Unterausdrucke Seiteneffekte bewirken, wird die operationale Semantik nichterhalten.