Kapitel 11: Funktionen (Teil 3): Verschachtelung und...

38
Kapitel 11: Funktionen (Teil 3): Verschachtelung und funktionale Programmierung Grundlagen der Programmierung 1 Holger Karl Wintersemester 2016/2017 Inhaltsverzeichnis Inhaltsverzeichnis 1 Abbildungsverzeichnis 2 Liste von Definitionen u.ä. 2 11.1 Überblick ............................... 3 11.2 Verschachtelte Funktionen ..................... 3 11.3 Closures ............................... 12 11.4 Anonyme Funktionen: lambda-Ausdrücke ............ 19 11.5 Funktionaler Programmierstil ................... 21 11.6 Generatoren ............................. 24 11.7 Decorators .............................. 29 1

Transcript of Kapitel 11: Funktionen (Teil 3): Verschachtelung und...

Kapitel 11: Funktionen (Teil 3):Verschachtelung und funktionale

ProgrammierungGrundlagen der Programmierung 1

Holger Karl

Wintersemester 2016/2017

Inhaltsverzeichnis

Inhaltsverzeichnis 1

Abbildungsverzeichnis 2

Liste von Definitionen u.ä. 211.1 Überblick . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311.2 Verschachtelte Funktionen . . . . . . . . . . . . . . . . . . . . . 311.3 Closures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1211.4 Anonyme Funktionen: lambda-Ausdrücke . . . . . . . . . . . . 1911.5 Funktionaler Programmierstil . . . . . . . . . . . . . . . . . . . 2111.6 Generatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2411.7 Decorators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

1

11.8 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . 38

Abbildungsverzeichnis

11.1 Verschachtelte Funktionen, Anlegen der äußeren Funktion . . . 411.2 Verschachtelte Funktionen, Anlegen der inneren Funktion . . . 511.3 Verschachtelte Funktionen, Aufruf der inneren Funktion . . . . 511.4 Effekt von nonlocal . . . . . . . . . . . . . . . . . . . . . . . . 1111.5 Nachschlageregel LEGB . . . . . . . . . . . . . . . . . . . . . . 1211.6 Aufruf einer Funktion, die als Funktionsergebnis bekannt wurde 1411.7 Zurückgegebene Funktion mit Parameter: Vor Aufruf . . . . . . 1611.8 Zurückgegebene Funktion mit Parameter: Im Aufruf . . . . . . 1611.9 Zwei Closures . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1811.10 LISP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2411.11 Popularität reiner funktionaler Sprachen . . . . . . . . . . . . . 2411.12 Master of a tiny universe . . . . . . . . . . . . . . . . . . . . . . 37

Liste von Definitionen u.ä.

11.1 Definition (Nachschlageregel für Namen: LEGB) . . . . . . . . 1211.2 Definition (Closure) . . . . . . . . . . . . . . . . . . . . . . . . 1811.3 Definition (λ-Ausdrücke: lambda) . . . . . . . . . . . . . . . . 2011.4 Definition (Das Decorator-Muster) . . . . . . . . . . . . . . . . 32

2

11.1. Überblick 3

11.1 Überblick

11.1.1 Was bisher geschah

• Wir haben uns grundlegende Techniken zur Gestaltung und Nutzungvon Funktionen angeschaut

• Wir haben Funktionen im Kontext von Klassen als Methoden verwendet(eine Funktion, die an/mit einem Objekt arbeitet)

• Wir haben angedeutet, dass eine Funktion eigentlich auch nur einObjekt(von einem recht speziellen Typ) ist

11.1.2 Dieses Kapitel

• Wir erweitern die Möglichkeiten, wie manmit Funktionen arbeiten kann– Insbes. die Vorstellung, dass eine Funktion ein Objekt ist, auf dasman eine Referenz erzeugen und herumreichen kann

• Darauf aufbauend erarbeiten wir typische Programmiertechniken zurfunktionalen Programmierung

11.2 Verschachtelte Funktionen

11.2.1 Vorüberlegung: def ist eine Anweisung

• def ist eine Anweisung, die eine Funktion mit einem Namen versieht– Der Code der Funktion wird intern abgelegt– In einem Objekt einer geeigneten Klasse gespeichert

• Im Block einer Funktion stehen Anweisungen• Darf dann dort auch ein def stehen?

11.2.2 def in Funktionsblock

Syntaktisch geht das natürlich – Semantik?

1 def f():2 print("f1")3

4 def g():5 print("g!")

4 Liste von Definitionen u.ä.

6

7 g()8 print("f2")9

10 f()11 print("nach f")

f1g!f2nach f

(PT link)

11.2.3 def in Funktionsblock: Semantik

Bei geschachtelter Funktionsdefinition:

• Der Name der inneren Funktion ist im Namensraum der äußeren Funk-tion nach Ausführung der inneren def-Anweisung bekannt

• Dieser Name kann – wie jeder andere Funktionsname auch – zumAufrufbenutzt werden

• Nach Rückkehr der äußeren Funktion verschwindet der Name g– Im Beispiel: damit ist diese Funktion auch nicht aufrufbar!

Visualisierung

1. Erzeugen von fMit Ausführung der def-Anweisung in Zeile 1 wird das Objekt für dieFunktion f angelegt und der Name f (im globalen Namensraum) refe-renziert es. Abbildung 11.1 zeigt diesen Zustand.

Abbildung 11.1: Verschachtelte Funktionen, Anlegen der äußeren Funktion

11.2. Verschachtelte Funktionen 5

2. Erzeugen von gAbbildung 11.2 zeigt den nächsten Schritt. Nach dem Aufruf von f wirdin f selbst die Zeile 4 ausgeführt. Dadurch wird ebenfalls ein Objektfür eine Funktion angelegt. Dieses Objekt unterscheidet sich (zunächst)nicht besonders von einem Funktionsobjekt, dass im globalen Scopeerzeugt wurde (aber siehe später Abschnitt 11.3).Für dieses Objekt wird der Name g vereinbart; dieser Name existiert nurim Namensraum von f (präziser: im Namensraum des ersten Aufrufsvon f, in der Abbildung als f1 angegeben – f1 steht hier für Frame 1,nicht für Funktion f).

Abbildung 11.2: Verschachtelte Funktionen, Anlegen der inneren Funktion

3. Aufruf von g aus f herausDer eigentlich Aufruf von g aus f heraus (Zeile 7) ist dann ein ganznormaler Funktionsaufruf; hier passiert nichts außergewöhnliches (Ab-bildung 11.3). Dass es sich um eine Funktion handelt, die in f vereinbartwurde und dass der Name g nur im lokalen Namensraum bekannt ist,spielt dabei keine Rolle.

Abbildung 11.3: Verschachtelte Funktionen, Aufruf der inneren Funktion

6 Liste von Definitionen u.ä.

11.2.4 Verschachtelte Funktionen: Argumente, Rückgabewert

Argumentübergabe und Rückgabewert funktionieren ganz normal

1 def f():2 def g(x):3 print(x+1)4 return x+25

6 y = g(17)7 print(y)8

9 f()

1819

11.2.5 Verschachtelte Funktionen: Zugriff auf Variablen?

• Wie funktioniert der Zugriff auf Variablen außerhalb des eigenen Na-mensraum aus einer inneren Funktion heraus?

• Im wesentlichen: Regeln wie bisher

11.2.6 Zugriff auf globale Variablen, lesend

Ohne besondere Vorkehrungen, wenn nur lesen

1 def f():2 def g():3 print(x)4 g()5

6 x = 17 f()

1

11.2.7 Zugriff auf globale Variablen, schreiben

Schreibender Zugriff: Erfordert Deklaration mit global

11.2. Verschachtelte Funktionen 7

1 def f():2 def g():3 global x4 x = x + 15 print(x)6 g()7

8 x = 19 f()

2

11.2.8 Zugriff auf Variablen der äußeren Funktion? global?

Spannender Punkt: Was ist mit Variable, die nicht global, aber in der äußerenFunktion vereinbart ist?

Zugriff mit global?

1 def f():2 def g():3 global x4 x = x + 15 print(x)6 x = 17 g()8

9 f()

Fehlermeldung

Das scheitert: Es gibt kein globales x, also kann der Zugriff hier nicht gelingen.

1 Traceback (most recent call last):2 File "<stdin>", line 9, in <module>3 File "<stdin>", line 7, in f4 File "<stdin>", line 4, in g5 NameError: name ’x’ is not defined

8 Liste von Definitionen u.ä.

11.2.9 Zugriff auf Variablen der äußeren Funktion? Direkt?

1 def f():2 def g():3 x = x + 14 print(x)5 x = 16 g()7

8 f()

Fehlermeldung

Scheitern ist plausibel: Nach bisherigen Regeln wird im lokalenNamensraumvon g gesucht; dort gibt es kein x. Also Fehler.

1 Traceback (most recent call last):2 File "<stdin>", line 8, in <module>3 File "<stdin>", line 6, in f4 File "<stdin>", line 3, in g5 UnboundLocalError: local variable ’x’ referenced before assignment

11.2.10 Zugriff auf Variablen der äußeren Funktion – Reihenfolge?

Liegt es vielleicht an der Reihenfolge?

• Im Beispiel eben war bei der Definition von g die Variable x noch garnicht existent

• Muss es das x schon geben, wenn g definiert wird? Damit g daraufzugreifen kann?

1 def f():2 x = 13 def g():4 print(x)5 g()6

7 f()

1

11.2. Verschachtelte Funktionen 9

Ja!

Offenbar wird auch der Namensraum einer umschließenden Funktion durch-sucht!

11.2.11 Modifizierender Zugriff auf Variablen der äußeren Funktion?

Kann man solche umschließenden Variablen auch verändern?

1 def f():2 x = 13 def g():4 x += 15 print(x)6 g()7

8 f()

Offenbar nein

Das ist analog zur Situation bei global

Fehlermeldung

1 Traceback (most recent call last):2 File "<stdin>", line 8, in <module>3 File "<stdin>", line 6, in f4 File "<stdin>", line 4, in g5 UnboundLocalError: local variable ’x’ referenced before assignment

11.2.12 Neues Schlüsselwort: nonlocal

Analog zu global: Neues Schlüsselwort, um umschließende Variablen zuverändern

• nonlocal• Unterschied zu global: Die Variable muss schon existieren! (durchZuweisung, siehe oben)

1 def f():2 x = 13 def g():

10 Liste von Definitionen u.ä.

4 nonlocal x5 x += 16 print(x)7 g()8

9 f()

2

11.2.13 Beispiel: Tiefere Verschachtelung (1)

1 def f():2 x = 13 def g():4 def h():5 nonlocal x6 x += 17 print("x in h:", x)8 h()9 print("x in g:", x)10 g()11 print("x in f:", x)12

13 f()

x in h: 2x in g: 2x in f: 2

11.2.14 Beispiel: Tiefere Verschachtelung (2)

1 def f():2 x = 13 def g():4 x = 425 def h():6 nonlocal x7 x += 18 print("x in h:", x)9 h()10 print("x in g:", x)

11.2. Verschachtelte Funktionen 11

11 g()12 print("x in f:", x)13

14 f()

x in h: 43x in g: 43x in f: 1

(PT link)

Anmerkung: x aus f?

Es gibt keine Möglichkeit, von h aus auf das von f angelegte x zuzugreifen.Dieses x wird durch das x von g verdeckt!

Visualisierung

Es lohnt sich, hier kurz Abbildung 11.4 zu betrachten. Die Abbildung zeigtden Zustand nach Ausführung der Addition in h, Zeile 7. Dabei fällt auf, dasses

• einerseits natürlich eine Variable x im Frame f1 von f gibt, die auf denWert 1 verweist;

• anderseits auch eine Variable x im Frame f2 von g gibt, die auf denWert43 (nach der Addition) weist;

• aber keine Variable x im Frame von h gibt; nonlocal sorgt dafür, dassder Namespace von g hier benutzt wird.

Abbildung 11.4: Effekt von nonlocal

12 Liste von Definitionen u.ä.

11.2.15 Nachschlageregel: LEGB

• Bisher einfache Nachschlagregel für Namensräume: Erst lokal, dannglobal

• Tatsächlich etwas umfangreicher

Definition 11.1 (Nachschlageregel für Namen: LEGB). Python sucht Namenin diesen Namensräumen, in dieser Reihenfolge:1. dem lokalen Namensraum (einer Funktion): L2. dem Namensraum umgebender (enclosing) Funktionen: E3. dem globalen Namensraum: G4. dem Namensraum eingebauter (vordefinierter) Namen (build-in): BKurz: Die Reihenfolge ist LEGB

11.2.16 Nachschlageregel: LEGB – Illustration

Abbildung 11.5: Nachschlageregel LEGB

Abbildung 11.5 illustriert die Nachschlageregel LEGB: von innen nach aussen.

11.3 Closures

11.3.1 Vorüberlegung: Referenzen auf Funktionen?

• Kann man Funktionsnamen wie normale Namen behandeln?• Kann man diese kopieren, und trotzdem noch aufrufen?

11.3. Closures 13

1 def f():2 print("f!")3

4 g = f5 g()

f!

Was passiert hier?

• f ist ein Name für ein Funktionsobjekt• Die Anweisung g = f lässt den Namen g auf das gleiche Objekt refe-renzieren

• Also wird der Aufruf g() auch die gleiche Funktion aufrufen– Der Name der referenzierenden Variable ist irrelevant!

11.3.2 Vorüberlegung 2: Funktionsreferenzen als Rückgabewert?

• Wenn man Funktionsreferenzen kopieren kann• Dann kannman sie doch bestimmt auch als Rückgabewert einer Funktionverwenden?

1 def f():2 def g():3 print("innere Funktion!")4 return g5

6 h = f()7 h()

(PT link)

innere Funktion!

Natürlich!

• Die Semantik ist ganz banal!• Nach Aufruf von f ist h ein weiterer Name für das Funktionsobjektgeworden, das ursprünglich unter dem Namen g eingeführt wurde

14 Liste von Definitionen u.ä.

Visualisierung

Auch Abbildung 11.6 zeigt, dass hier eigentlich nichts spannendes passiert.Der Umstand, dass das Funktionsobjekt g innerhalb einer Funktion erzeugtwurde, ist genauso viel oder wenig relevant wie das bei einem normalen Objektder Fall wäre.

Abbildung 11.6: Aufruf einer Funktion, die als Funktionsergebnis bekanntwurde

11.3.3 Analogie: Normales Objekt als Rückgabewert

Zur Analogie: Dieser Code ist ja klar

• Eine Liste kann man nicht aufrufen, sondern ausgeben• Aber sonst ist das genau das gleiche!

1 def f():2 g = ["ein", "Beispiel", "Objekt"]3 return g4

5 h = f()6 print(h)

11.3.4 Funktionen mit Parametern als Rückgabe?

Was passiert, wenn wir eine Funktion zurückgeben, die Parameter benutzt?

1 def f():2 def g(x):3 print("innere Funktion!", x)4 return g5

6 h = f()7 h("Wert für x")

11.3. Closures 15

innere Funktion! Wert für x

Parameter bleibt bestehen

Dann hat die Funktion natürlich einen Parameter

• Aufruf unter neuem Namen erfordert diesen Parameter

11.3.5 Funktion mit Zugriff auf nichtlokale Variablen als Rückgabe

Spannender:

• Innere Funktion greift auf Parameter der äußeren Funktion zu• Innere Funktion wird zurückgeben• und dann aufgerufen

Erwartetes Verhalten?

1 def f():2 n = 53 def g(x):4 print("innere Funktion!", x*n)5 return g6

7 h = f()8 h("Wert für x")

innere Funktion! Wert für xWert für xWert für xWert für xWert für x

(PT link)

Äußerer Parameter bleibt erhalten!

• Offenbar behält die Funktion, die unter h im globalen Scope bekanntist, Zugriff auf die Variable n

• Erstaunlich! n im Namensraum von f, aber f schon beendet!?

Visualisierung

Abbildung 11.7 zeigt hier den Zustand beim Ablauf von f, kurz vor Rückgabevon g an den Aufruf in Zeile 7. Man sieht schön das angelegte Funktionsobjektfür g (mit dem Parameter x) und den Namen n für die Zahl 5.

16 Liste von Definitionen u.ä.

Abbildung 11.7: Zurückgegebene Funktion mit Parameter: Vor Aufruf

Abbildung 11.8 visualisiert dann die Ausführung der Funktion h (was g ist).g wird mit dem String Wert für x als Wert für den Parameter x aufgerufen.Zusätzlich behält g Zugriff auf den Wert von n (hier nicht schön visualisiert;man sieht aber, dass der Frame f1 noch existiert.

Abbildung 11.8: Zurückgegebene Funktion mit Parameter: Im Aufruf

11.3.6 Nicht-lokale Variable selbst als Parameter

• In obigem Beispiel: n hatte einen festen Wert• Aber wo der Wert für n herkommt ist egal

– Er muss lediglich vor dem def von g vorhanden sein• Es könnte selbst ein Funktionsparameter sein?

1 def f(n):2 def g(x):3 print("innere Funktion!", x*n)4 return g5

6 h = f(3)7 h("Wert für x")

11.3. Closures 17

innere Funktion! Wert für xWert für xWert für x

11.3.7 Beispiel – Aufgeräumt

1 def vervielfacher_fabrik(n):2 def vervielfacher(x):3 return x*n4

5 return vervielfacher6

7 doppler = vervielfacher_fabrik(2)8 fuenffacher = vervielfacher_fabrik(5)9

10 print(doppler("Mehrfach ausgeben! "))11 print(fuenffacher("Mehrfach ausgeben! "))

Mehrfach ausgeben! Mehrfach ausgeben!Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben!

(PT link)

11.3.8 Muster: Fabrik (Factory)

• Beispiel hatte mehrere Bestandteile– Eine ...-fabrik Funktion: Sie erzeugt eine andere Funktion,richtig parametriert

– DerBauplan für die zu erzeugende Funktion (hier:vervielfacher)* Bauplan wird konkretisiert durch Einsetzen von Werten fürParameter (hier: n)

– Mehrfacher Aufruf der Fabrik mit unterschiedlichen Parametern* Ergibt maßgeschneiderte Funktionen, die aufgerufen werden

• Das sog. factory pattern

11.3.9 Realisierung: Closures

• Das erstaunliche: Die produzierten Funktionen merken sich die Werteder Parameter der Fabrik-Funktion!

• Dazu werden die Frames (Namensräume) dieser Fabrikfunktionsauf-rufe nicht entsorgt, sondern an die produzierte Funktion (doppler,fuenffacher, . . . ) gekoppelt

• Diese Datenstruktur (Kopplung von Frames an einen Funktionsnamen)heißt Closure

• Closure merken sich den relevanten Zustand – ein state retention Ansatz

18 Liste von Definitionen u.ä.

Definition 11.2 (Closure). Ein Closure ist eine Funktion, deren nicht-lokaleNamen an Objekte gebunden wurden.

Visualisierung

Abbildung 11.9 zeigt die beiden im Beispiel entstandenen Closures. Die bei-den Funktionsnamen doppler und fuenffacher referenzieren jeweils zweiFunktionsobjekte. Der entscheidende Punkt ist nun die Angabe parent beidiesen Objekten: sie verweisen auf den Namensraum, in dem die fehlendenVariablen (hier: n) gesucht werden sollen. Dies sind die beiden Namensräume,die durch die beiden Aufrufe von vervielfacher_fabrik erzeugt wurden.

Abbildung 11.9: Zwei Closures

Anmerkung: Terminologie

Je nach Literaturquelle wird auch das Programmiermuster selbst als Closurebezeichnet, nicht nur die Realisierungstechnik. Das macht letztlich keinengroßen Unterschied.

Anmerkung: Vorkommen von Closures

Closures kommen häufig in funktionalen Sprachen vor. Neben Python istJavascript ein populäres Beispiel, bei dem häufig Closures benutzt werden,um die Reaktion auf Nutzereingaben oder Daten vom Server zu beschreiben.Auch in Sprachen wie Lisp oder Ruby sind Closures ein populäres, elegantesSprachmittel.

Sprachen wie Java, C++, C# und Abkömmlinge stellen in der Regel nur verein-fachte Strukturen zur Verfügung und können das Konzept der Closures nichtvollständig abbilden.

11.4. Anonyme Funktionen: lambda-Ausdrücke 19

11.3.10 Nutzen: Fabriken und Closures

• Beispiel oben ist zugeben etwas künstlich• Typischer Einsatz von Closures:

– Graphische Nutzerschnittstellen: Funktionen, die abhängig vonNutzereingabe erzeugt werden

– Web-Anwendungen: Funktionen erzeugen, die auf Veränderungenreagiert (sog. callbacks)* Häufig in event handlers in Javascript!

11.4 Anonyme Funktionen: lambda-Ausdrücke

11.4.1 Funktionsdefinition bisher: def

• def ist Anweisung– Erzeugt ein Funktionsobjekt– Legt einen Namen im entsprechenden Namensraum an– Verbindet Namen mit erzeugtem Objekt

• Was, wenn man den Namen eigentlich gar nicht braucht?– Sondern nur das Funktionsobjekt an sich?– Z.B., weil es in einer Factory sowieso direkt zurückgegeben wird?– Oder nur an einer einzigen Stelle gebraucht wird, z.B. in einer listcomprehension?

11.4.2 Funktionsname in Factory: Nötig?

Beispiel oben: Name vervielfacher eigentlich nicht sehr nützlich

1 def vervielfacher_fabrik(n):2 def vervielfacher(x):3 return x*n4

5 return vervielfacher6

7 doppler = vervielfacher_fabrik(2)8 fuenffacher = vervielfacher_fabrik(5)9

10 print(doppler("Mehrfach ausgeben! "))11 print(fuenffacher("Mehrfach ausgeben! "))

20 Liste von Definitionen u.ä.

Mehrfach ausgeben! Mehrfach ausgeben!Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben!

11.4.3 Anonyme Funktionen: λ-Ausdrücke

• Das kommt häufig vor, verdient also Sprachunterstützung

Definition 11.3 (λ-Ausdrücke: lambda). • Ausdruck, der eine anonymeFunktion vereinbart– Keine Anweisung!– Präziser: der ein Funktionsobjekt erzeugt und eine Referenz aufdieses Objekt zurückgibt

• Eigenschaften– Anonyme Funktion darf beliebig viele Parameter haben– Aber die Funktion darf nur aus einem Ausdruck bestehen; sie darfkeine Anweisungen nutzen

• Syntax:– lambda Liste von Parameter : Zu berechnenderAusdruck

11.4.4 λ-Ausdrücke – Beispiele

Funktion, die drei Zahlen addiert

1 adder = lambda a, b, c : a + b + c2 print(adder(1, 2, 3))

6

lambda in Factory

Viel besser lesbare Closure-Version:

1 def vervielfacher_fabrik(n):2 return lambda x: x*n3

4 doppler = vervielfacher_fabrik(2)5 fuenffacher = vervielfacher_fabrik(5)6

7 print(doppler("Mehrfach ausgeben! "))8 print(fuenffacher("Mehrfach ausgeben! "))

11.5. Funktionaler Programmierstil 21

Mehrfach ausgeben! Mehrfach ausgeben!Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben! Mehrfach ausgeben!

11.4.5 λ-Ausdrücke – Funktionen an Objekte übergeben

• Objekte brauchen manchmal eine Funktion, die sie aufrufen können– Häufig: GUI-Frameworks

• Funktion wird nur als Referenz in Objekt gespeichert• Funktion bei Methodenaufruf bei lambda erzeugen

1 class SomeClass:2 def __init__(self, name, fct):3 self.name = name4 self.fct = fct5

6 def do(self):7 self.fct()8

9

10 o = SomeClass(name="somename",11 fct = lambda: some expression here)12 o.do()

11.5 Funktionaler Programmierstil

11.5.1 Beobachtung: Funktionen sind first-class citizens

• Python behandelt Funktionen wie ganz normale Objekte, mit allerleiOperationen darauf– Funktionen als Parameter, als Rückgabewerte, speichern in Varia-blen oder Datenstrukturen, . . .

• Solche Funktionen sind first-class citizens• Dies erlaubt funktionale Programmierung als Programmierparadigma

11.5.2 Funktionale Programmierung – Typische Merkmale

• Funktionen sind first-class citizens– Impliziert: Funktionen können auf Funktionen angewendet werden

• Rekursion• Keine Seiteneffekte

22 Liste von Definitionen u.ä.

11.5.3 Beispiel: map, filter

• Häufiger Stil in funktionalen Sprachen:– Daten in Listen ablegen– Listen filtern gemäß einer Filterfunktion– Funktion auf jedes Element einer Aufzählung (iterable) anwenden– Liste zu einem einzelnen Wert reduzieren

• Sog.map/filter/reduce-Muster

11.5.4 Iterables

• Idee nicht nur auf Listen eingeschränkt• Warum nicht auf Tuple, Mengen, . . . ?• Alles, was man der Reihe nach durchlaufen kann• Formalisiert: Klasse Iterable

– Unterklassen: list, str, tuple, dict, Dateien, . . .

11.5.5 filter

• filter: eingebaute Funktion• Signatur: filter(function, iterable)

– Wende die Funktionfunction auf jedes Element einer Aufzählungan

– Resultat: Aufzählung mit den Elementen, bei denen die Funktionwahr ergab

• Beispiel:

1 l = [1, 2, 3, 4, 5]2 l2 = filter(lambda x: x % 2 == 1, l)3 print(list(l2))

[1, 3, 5]

11.5.6 map

• map: eingebaute Funktion• Signatur: map(function, iterable)

– Wende die Funktion function auf jedes Element eines Objektesan, dass zu einer von iterable abgeleiteten Klasse gehört

– Resultat: Aufzählung der Funktionsergebnisse• Beispiel:

11.5. Funktionaler Programmierstil 23

1 l = [1, 2, 3, 4, 5]2 l2 = map(lambda x: x*x, l)3 print(list(l2))4

5 print(" -- ".join(map(str,l)))

[1, 4, 9, 16, 25]1 -- 2 -- 3 -- 4 -- 5

Anmerkung

Konvertierung in Liste nur nötig für print

11.5.7 Weitere Operationen auf iterables

Reichlich Beispiele, siehe Übungen:

• zip• reduce

11.5.8 λ-Ausdrücke und flexible Argumente

Eine Funktion, die beliebig viele Argumente beliebigen Types mit & dazwi-schen ausgibt?

1 out = lambda *x: print(" & ".join(map(str, x)))2 out(1, 2, 3, "hallo", "gp1")

1 & 2 & 3 & hallo & gp1

11.5.9 Vergleich: list comprehensions und map/filter

• Die Beispiele oben sind in Python einfacher und kompakter als listcomprehensions aufzuschreiben

• Allerdings: dadurch wird die komplette Liste erzeugt – Sinnvoll?

1 l = [x for x in range(100000) if x < 2][0:1]2 print(l)

24 Liste von Definitionen u.ä.

Abbildung 11.10: LISP

Abbildung 11.11: Popularität reiner funktionaler Sprachen

11.5.10 Evolution funktionaler Sprache: Großvater LISP

11.5.11 Rein funktionale Sprachen

11.6 Generatoren

11.6.1 Funktionen auf große Listen anwenden?

• Große Eingaben brauchen viel Platz– Ggf. kann das Erzeugen der Eingabe schon allen Speicherplatz ver-brauchen

• Mit endlichen Listen kann man keine unendlichen Folgen erzeugen– Beispiel: Alle Primzahlen

• Vielleicht

Idee: Werte nach und nach erzeugen

• Statt komplette Liste zu berechnen, nur jeweils den nächstenWert• Auf Aufforderung – als Funktionsaufruf?

11.6.2 Werte auf Aufforderung – Skizze?

11.6. Generatoren 25

1 def magic_prime_production(start):2 if erster Aufruf:3 letzte_zahl = start4 res = nächste Primzahl größer als letzte_zahl5 letzte_zahl = res6 return res7

8 # Nutzung in for Schleife??9 for p in magic_prime_production():10 print(p)

11.6.3 Werte auf Aufforderung – Realisierung?

• Mit Funktionsaufruf wie bisher geht das so nicht• Funktion hat keine Vorstellung, was der letzte Wert ist• Wir müssten das der Funktion beibringen? . . . ?

– Analogie: Closures? Hat sich auch Zustand gemerkt?

Realisierung: Closure-artig

• Mit Closure-artiger Zustandsspeicherung lässt sich das Problem lösen• Siehe auch Programmieraufgabe!• Aber syntaktisch unhandlich – also Anweisung!

11.6.4 Werte auf Aufforderung – Generatoren

Syntaktischer Zucker: Generatoren

• Schlüsselwort: yield und Generatorenfunktionen• Idee: Ersetze in einer Funktion return durch yield

– Liefert Wert an Aufrufer– undmerkt sich Zustand für nächsten Aufruf!

* Zustand: Werte lokaler Variablen; Zeilennummer; . . .

11.6.5 Generator: Einfaches Beispiel

Generator wird die Werte 1, 2, 3 liefern. Danach nichts.

1 def gen():2 yield 13 yield 24 yield 3

26 Liste von Definitionen u.ä.

11.6.6 Generatoren: Nutzung

• Aufruf: Unterscheidung erster und folgende Aufrufe notwendig– Erster Aufruf: normaler Funktionsaufruf, initialisiert

* Gibt ein Generator-Objekt– Folgender Aufruf: Funktion next auf dieses Generator-Objekt an-wenden

11.6.7 Generator: Einfaches Beispiel – Nutzung

Generator wird die Werte 1, 2, 3. Danach StopIteration-Exception.

1 def gen():2 yield 13 yield 24 yield 35

6 g = gen()7 print(g)8 print(next(g))9 print(next(g))10 print(next(g))11 try:12 print(next(g))13 except StopIteration:14 print("da kommt nichts mehr")

<generator object gen at 0x10301b3b8>123da kommt nichts mehr

11.6.8 Generator: Nutzung in Schleife

Spannender Punkt: Generatoren können wie Iterables in Schleife benutztwerden

• Syntax dadurch deutlich einfacher• StopIteration führt zum Schleifenende

1 def gen():2 yield 1

11.6. Generatoren 27

3 yield 24 yield 35

6 for v in gen():7 print(v)

123

11.6.9 Beispiel: Unendlicher Generator

Produziere alle Vielfachen von 3 ab Startwert:

1 def gen(start):2 # wir wollen alle Vielfache, also Endlosschleife:3 while True:4 if start % 3 == 0:5 yield start6 print("Restarting with value: ", start)7 start += 18

9 for v, i in zip(gen(16), range(5)):10 print("Wert: ", v)

Verständnisfragen

• Wozu range und zip in Beispiel?• Wann werden Aufrufe von gen durchgeführt?• Wieviele Schleifendurchläufe?

11.6.10 Beispiel: Generator als Closure

1 def gengen(multiplier):2 def gen(start):3 while True:4 if start % multiplier == 0:5 yield start6 start += 17

8 return gen9

28 Liste von Definitionen u.ä.

10 dreis = gengen(3)11 viers = gengen(4)12

13 for d, v, i in zip(dreis(10), viers(17), range(2)):14 print (d, v)

12 2015 24

Ausgabe?

1 pingo_title = "WAs wird hier ausgebenen?"2 pingo_type = "single"3 pingo_questions = ["12, 15 und 20, 24", "12, 20 und 15, 24", "10, 17 und 13, 21"]4 pingo_duration = "60"5

6 %pingo

11.6.11 Wesentlicher Punkt: Speicherverbrauch

• Natürlich kannman das auch über eine list comprehension hinschreiben• Aber Speicherverbrauch:

– List comprehension erzeugt die gesamte Liste vor Verarbeitung;muss im Speicher stehen

– Generator: nur Platz für eine einzige Iteration notwendig* Also: start, und die Closure für den Generator

11.6.12 Generators für List comprehensions

Kombination der Vorteile?

• Elegante Syntax der List comprehension?• Ohne den Speicherverbrauch?

Generatoren statt Listen!

Kleine syntaktische Änderung: Wir ersetzen [] durch () in Schleifen

1 print("Mit Liste: ")2 for i in [x for x in range(17, 40) if x % 3 == 0]:3 print(i, end=", ")4

5 print("\nMit Generator: ")

11.7. Decorators 29

6 for i in (x for x in range(17, 40) if x % 3 == 0):7 print(i, end=", ")

Mit Liste:18, 21, 24, 27, 30, 33, 36, 39,Mit Generator:18, 21, 24, 27, 30, 33, 36, 39,

11.7 Decorators

11.7.1 Ausgangspunkt

Wir haben folgende Eigenschaften kennengelernt:

• Funktionen sind first-class citizens in Python• Funktionsnamen sind eigentlich auch nur Variablen, die ein Funktions-objekt referenzieren

• Solche Referenzen können aus Funktionen zurückgegeben werden• Und Referenzen können Funktionsnamen zugewiesen werden

Was kann man damit anstellen?

11.7.2 Überlegung 1: Wir können aus einer Funktion eine neue Funkti-on bauen

• Beispiel: Wir möchten die Ausgaben einer Funktion fmit == einrahmen• Ansatz: Baue eine neue Funktion, die

– = ausgibt– f aufruft– und nochmal = ausgibt

• Diese neue Funktion bauen wir als Closure und geben eine Referenzdarauf zurück

Schauen wir uns den Code in mehreren Schritten an!

11.7.3 Eingerahmte Ausgabe – Version 1

Version 1: Eine einfache factory

1 def f():2 print("Ausgabe von f")

30 Liste von Definitionen u.ä.

3

4 def einrahmer_fabrik():5 def einrahmer():6 print("=====")7 f()8 print("=====")9

10 return einrahmer11

12 f_neu = einrahmer_fabrik()13 f_neu()

=====Ausgabe von f=====

11.7.4 Eingerahmte Ausgabe – Version 2

Version 1 ist unnötig speziell

• Factory-Muster kann doch Parameter nehmen (Closures!)

1 def f():2 print("Ausgabe von f")3

4 def g():5 print("Ausgabe von g")6

7 def einrahmer_fabrik(fct):8 def einrahmer():9 print("=====")10 fct()11 print("=====")12

13 return einrahmer14

15 f_neu = einrahmer_fabrik(f)16 g_neu = einrahmer_fabrik(g)17

18 f_neu()19 g_neu()

=====

11.7. Decorators 31

Ausgabe von f==========Ausgabe von g=====

11.7.5 Eingerahmte Ausgabe – Version 3

Den einrahmenden Text kann man auch zum Parameter machen

1 def f():2 print("Ausgabe von f")3

4 def g():5 print("Ausgabe von g")6

7 def einrahmer_fabrik(fct, s="==========="):8 def einrahmer():9 print(s)10 fct()11 print(s)12

13 return einrahmer14

15 f_neu = einrahmer_fabrik(f, "***********")16 g_neu = einrahmer_fabrik(g)17

18 f_neu()19 g_neu()

***********Ausgabe von f***********===========Ausgabe von g===========

11.7.6 Eingerahmte Ausgabe – Version 4

Brauchen wir die ursprüngliche Version von f noch? Nein?

• Dann brauchen wir auch keinen neuen Namen!

32 Liste von Definitionen u.ä.

1 def f():2 print("Ausgabe von f")3

4 def einrahmer_fabrik(fct, s="==========="):5 def einrahmer():6 print(s)7 fct()8 print(s)9

10 return einrahmer11

12 # Wir lassen die Variable f ein neues Funktionsobjekt referenzieren:13 f = einrahmer_fabrik(f, "***********")14

15 f()

***********Ausgabe von f***********

11.7.7 Muster

Dieses Muster tritt häufig auf, ist extrem nützlich

• Einer Funktion wird neues Verhalten hinzugefügt, ohne dass dies füreinen Nutzer der Funktion sichtbar ist!

• Die Signatur der Funktion verändert sich nicht; nicht einmal der Name• Nützlich, um vorhandenen Code anzupassen

11.7.8 Dekoration (decorator)

Definition 11.4 (Das Decorator-Muster). Das Decorator-Muster beschreibteine typische Struktur, mit der eine Funktion mit weiterer Funktionalitätversehen werden kann, ohne die Funktion oder den nutzenden Code zu verän-dern.

11.7.9 Decorators: Anwendungen

• Vorhandenen Code verändern, patchen• Fehlersuche, Leistungsmessung• Komplexere Code-Strukturen

– GUI-Frameworks

11.7. Decorators 33

* Beispiel: einer Funktion zum Zeichnen eines Fenster werdenals decorators Funktionen zum Zeichnen von Scrollbars hinzu-gefügt· Funktion selbst und Aufrufer merken nicht, dass das Fen-ster Scrollbars hat

– Webframework (Beispiel: Django)* Die Handhabung von URLs wird durch Dekoratoren angepasst

11.7.10 Decorators für Methoden

• Methoden einer Klasse sind Funktion• Also können sie dekoriert werden

11.7.11 Decorators: Syntaktischer Zucker

• Decorators stellen sich als extrem nützlich und vielseitig heraus• Aber auch unhandlich

– Beispiel oben: Die Funktion f wird 3x erwähnt? DRY?– Die Dekoration kann an ganz anderer Stelle geschehen; unüber-sichtlich

• Kompakter? Sprachunterstützung?

11.7.12 Decorators: Syntaktischer Zucker (2)

Syntax:

• Die dekorierende Funktion wird wie üblich vereinbart• Sie nimmt einen Parameter: die aufzurufende (zu dekorierende) Funkti-on

• Die zu dekorierende Funktion wird durch voranstellen von@Dekora-torfunktion dekoriert

11.7.13 Decorator: kompakte Syntax – Beispiel

1 def einrahmer_fabrik(fct):2 def einrahmer():3 print("=====")4 fct()5 print("=====")6

7 return einrahmer8

9 @einrahmer_fabrik10 def f():11 print("Ausgabe von f")

34 Liste von Definitionen u.ä.

12

13 f()

=====Ausgabe von f=====

11.7.14 Decorator: kompakte Syntax – Beispiel mit typischen Namen

Die _fabrik-Terminologie wird bei Dekoratoren typischerweise weggelassen

1 def einrahmer(fct):2 def _einrahmer():3 print("=====")4 fct()5 print("=====")6

7 return _einrahmer8

9 @einrahmer10 def f():11 print("Ausgabe von f")12

13 f()

=====Ausgabe von f=====

11.7.15 Decorators: Syntaktischer Zucker (3)

@-Syntax:

• Kompakt, leicht lesbar, elegant, häufig benutzt– Sie werden kaum ein nicht-triviales Python-Programm ohne de-corators finden

• Nachteil: Parameter an dekorierende Funktion übergeben ist umständ-lich

11.7.16 @-Decorators: Beispiel Benchmarking

Wie bestimmt man Zeitverbrauch einer Funktion?

11.7. Decorators 35

• Man schaut in einem Dekorator vor und nach dem Aufruf der Funktionauf die Uhr!

1 import time2

3 def benchmark(fct):4 def _benchmark(*args, **kwargs):5 tstart = time.time()6 r = fct(*args, **kwargs)7 tend = time.time()8

9 print("{} (args: {}, kwargs: {}) verbrauchte {} Sekunden".format(10 fct.__name__, args, kwargs, tend-tstart))11

12 return r13

14 return _benchmark15

16 @benchmark17 def f1(s):18 print("Anfang f1")19 time.sleep(s)20 print("Ende f1")21

22

23 f1(0.2)24 f1(0.6)

Anfang f1Ende f1f1 (args: (0.2,), kwargs: {}) verbrauchte 0.20345807075500488 SekundenAnfang f1Ende f1f1 (args: (0.6,), kwargs: {}) verbrauchte 0.6047120094299316 Sekunden

11.7.17 @-Decorator: Property

Tatsächlich kennen wir schon einen Decorator: Property

• Bisher nur als eingebaute Funktion property• Es fehlt also nur noch der Zucker

– Getter: mit @property dekorieren; Funktionsname wird Nameder Property

36 Liste von Definitionen u.ä.

– Setter: mit @Propertyname.setter dekorieren; Funktionsnamemuss Name der Property sein, mit value

1 class Temperature:2 def __init__(self, temp=0):3 # auch Methoden der Klasse greifen normal auf das Attribut zu:4 self._temperature = temp5

6 @property7 def temperature(self):8 print("Lesender Zugriff")9 return self._temperature10

11 @temperature.setter12 def temperature(self, value):13 print("Schreibender Zugriff")14 if value < 0:15 raise ValueError("Keine negativen Temperaturen in Kelvin!")16 else:17 self._temperature = value18

19 t1 = Temperature(17)20 print(t1.temperature)21 t1.temperature = 4222 print(t1.temperature)

Lesender Zugriff17Schreibender ZugriffLesender Zugriff42

Für Fortgeschrittene

Warum kann man hier scheinbar eine Funktion doch überschreiben? Das gehtdoch angeblich nicht?

11.7.18 @-Decoratoren verketten?

Siehe auch: Quelle des Beispiels

11.7. Decorators 37

1 def star(func):2 def inner(*args, **kwargs):3 print("*" * 30)4 func(*args, **kwargs)5 print("*" * 30)6 return inner7

8 def percent(func):9 def inner(*args, **kwargs):10 print("%" * 30)11 func(*args, **kwargs)12 print("%" * 30)13 return inner14

15 @star16 @percent17 def printer(msg):18 print(msg)19

20 printer("Hallo GP1!")

******************************%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Hallo GP1!%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%******************************

11.7.19 Decorators: Ausblick

• DasDekorieren vonMethoden einer Klasse ist typisch (siehe auch@property)• Man kann auch Klassen zu decorators machen, nicht nur Funktionen• Siehe auch eine lange Liste mit Beispielen

11.7.20 War doch gar nicht so schwer

Abbildung 11.12: Master of a tiny universe

38 Liste von Definitionen u.ä.

11.8 Zusammenfassung

11.8.1 Zusammenfassung

• Funktionen können Funktionen definieren– Erfordert Erweiterung der Nachschlageregel für Namen: LEGB

• Macht ein Programmierparadigma Funktionen zu first-class citizens, er-öffnen sich viele elegante Möglichkeiten– Fabriken / Closures, um Funktionen zur Laufzeit zu erzeugen– Generatoren, um große (unendliche) Aufzählungen bei Bedarf zuerzeugen

– Decorators, um Funktionen zur Laufzeit zu modifizieren• Eine elegante Syntax ist dabei für praktische Nützlichkeit wichtig

– lambda-Ausdrücke für anonyme Funktionen, @-Notation für De-corators

11.8.2 Und nun?

• Was waren diese seltsamen import-Anweisungen??