Python für Fortgeschrittene 2

Funktionales Programmieren I

Typen von Programmiersprachen:

  • Prozedural
    Programm besteht aus einer Liste von Anweisungen, die sequentiell abgearbeitet werden. Die meisten Programmiersprachen sind prozedural, z.B. C.
  • Deklarativ
    Im Programm wird nur spezifiziert, welches Problem gelöst werden soll, der Interpreter setzt dies dann in Anweisungen um, z.B. SQL
  • Objekt-orientiert
    Programme erzeugen und verwenden Objekte und manipulieren diese Objekte. Objekte haben interne Zustände, die durch Methoden gesetzt werden, z.B. Java, C++.
  • Funktional
    Zerlegen ein Problem in eine Reihe von Funktionen (vergleichbar mit mathematischen Funktionen, z.B. f(x) = y. Die Funktionen haben einen definierten Input und Output, aber keine internen Zustand, der die Ausgabe eines bestimmten Input beeinflusst, z.B. Lisp oder Haskell.

Weitere Merkmale des funktionalen Programmierens:

  • Funktionen können wie Daten behandelt werden, d.h. man kann einer Funktion als Parameter eine Funktion geben bzw. die Ausgabe einer Funktion kann eine Funktion sein.
  • Rekursion ist die primäre Form der Ablaufkontrolle, etwa um Schleifen zu erzeugen.
  • Im Zentrum steht die Manipulation von Listen.
  • 'Reine' funktionale Programmiersprachen vermeiden Nebeneffekte, z.B. einer Variablen erst einen Wert und dann einen anderen zuzuweisen, um so den internen Zustand des Programms zu verfolgen. Einige Funktionen werden aber nur wegen ihrer 'Nebeneffekte' aufgerufen, z.B. print() oder time.sleep() und nicht für die Rückgabewerte der Funktion.
  • Funktionale Programmiersprachen vermeiden Zuweisungen und arbeiten stattdessen mit Ausdrücken, also mit Funktionen, die Parameter haben und eine Ausgabe. Im Idealfall besteht das ganze Programm aus einer Folge von Funktionen, wobei die Ausgabe der einen Funktion zum Parameter der nächsten wird usw., z.B.:
    a = 3
    func3(func2(func1(a)))
  • Funktionale Programmiersprachen verwenden vor allem Funktionen, die auf anderen Funktionen arbeiten, die auf anderen Funktionen arbeiten.

Vorteile des funktionalen Programmierens:

  • Formale Beweisbarkeit (eher von akademischem Interesse
  • Modularität
    Funktionales Programmieren erzwingt das Schreiben von sehr kleinen Funktionen, die leichter wiederzuverwenden und modular einzusetzen sind.
  • Einfachheit der Fehlersuche und des Testens
    Da Ein- und Ausgabe stets klar definiert sind, sind Fehlersuche und das Erstellen von Unittests einfacher

Wie immer gilt in Python auch hier: Python ermöglicht die Verwendung des funktionalen Paradigmas, erzwingt es aber nicht durch Einschränkungen, wie es reine funktionale Programmiersprachen tun. Typischerweise verwendet man in Python prozedurale, objekt-orientierte und funktionale Verfahren, z.B. kann man objekt-orientiertes und funktionales Programmieren verwenden, indem man Funktionen definiert, die als Ein- und Ausgabe Objekte verwenden.

In Python wird das funktionale Programmieren u.a. durch folgende Komponenten realisiert:

  • Iteratoren
  • List Comprehension, Generator Expressions
  • Die Funktionen map(), filter()
  • Das itertools Modul

Iteratoren

An iterator is an object representing a stream of data; this object returns the data one element at a time. A Python iterator must support a method called __next__() that takes no arguments and always returns the next element of the stream. If there are no more elements in the stream, _ _next_ _() must raise the StopIteration exception. Iterators don’t have to be finite, though; it’s perfectly reasonable to write an iterator that produces an infinite stream of data. (Python Documentation)

Die Methode iter() versucht für ein beliebiges Objekt einen Iterator zurückzugeben. Der Iterator gibt bei jedem Aufruf ein Objekt der Liste zurück und setzt den Pointer der Liste um eines höher. Objekte sind iterierbar (iterable) wenn sie die Methode iter() unterstützen, z.B. Listen, Dictionaries, Dateihandles usw.


In [1]:
#beispiel
a = [1, 2, 3,]
my_iterator = iter(a)
my_iterator.__next__()


Out[1]:
1

In [2]:
my_iterator.__next__()


Out[2]:
2

Python erwartet in bestimmten Kontexten ein iterierbares Objekt, z.B. in der for-Schleife:


In [3]:
for i in a:
    print(str(i))


1
2
3

Das ist äquivalent zu


In [4]:
for i in iter(a):
    print(str(i))


1
2
3

Man kann sich die vollständige Ausgabe eines Iterators ausgeben lassen, wenn man ihn als Parameter der list()- oder tuple() Funktion übergibt.


In [5]:
#beispiel
a = [1, 2, 3,]
my_iterator = iter(a)
list(my_iterator)


Out[5]:
[1, 2, 3]

In [6]:
my_iterator = iter(a)
tuple(my_iterator)


Out[6]:
(1, 2, 3)

Frage: Warum habe ich im letzten Beispiel den Iterator neu erzeugt? Kann man das weglassen?

List Comprehension

List Comprehension sind ein Element (von vielen) des funktionalen Programmierens in Python. Der wichtigste Vorteil ist das Vermeiden von Nebeneffekten. Was heißt das? Anstelle des Verändern des Zustands einer Datenstruktur (z.B. eines Objekts), sind funktionale Ausdrücke wie mathematische Funktionen aufgebaut, die nur aus einem klaren Input und einen ebenso eindeutig definierten Output bestehen.

Prinzipielle Schreibweise:
[<expression> for <variable> in <iterable> <<if <condition> >>]

Im folgenden Beispiel ist es das Ziel, die Zahlen von 0 bis 9 ins Quadrat zu setzen. Zuerst die traditionelle Lösung mit einer for-Schleife, in deren Körper eine neue Datenstruktur aufgebaut wird.


In [7]:
#eine traditionelle for-Schleife:
squared = []
for x in range(10):
    squared.append(x**2)
squared


Out[7]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Und hier die Version mit List Comprehension:


In [1]:
[x**2 for x in range(10)]


Out[1]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In [3]:
#a + bx
#2 + 0.5x
#x = 5 bis x = 10
[x*0.5 + 2 for x in range(5, 11)]


Out[3]:
[4.5, 5.0, 5.5, 6.0, 6.5, 7.0]

Natürlich kann man den Rückgabewert von List Comprehensions auch in einer Variablen abspeichern.


In [9]:
squared = [x**2 for x in range(10)]
squared


Out[9]:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Geschachtelte Schleifen

Man kann in list comprehensions auch mehrere geschachtelte for-Schleifen aufrufen:


In [11]:
#Aufgabe: vergleiche zwei Zahlenlisten und gebe alle Zahlenkombinationen aus, die ungleich sind
#Erst einmal die traditionelle Lösung mit geschachtelten Schleifen:
combs = [] 
for x in [1,2,3 ]: 
    for y in [3,1,4]: 
        if x != y: 
            combs.append((x, y))
combs


Out[11]:
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Und nun als List Comprehension:


In [12]:
[(x,y) for x in [1,2,3] for y in [3,1,4] if x != y]


Out[12]:
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Aufgabe 1

Ersetzen Sie eine Reihe von Worten durch eine Reihe von Zahlen, die die Anzahl der Vokale anzeigen. Z.B.: "Dies ist ein Satz" -> "2 1 2 1".

Die Funktionen map(), filter()

map()

map(FunktionX, Liste)
Die Funktion FunktionX wird auf jedes Element der Liste angewandt. Ausgabe ist ein Iterator über eine neue Liste mit den Ergebnissen


In [13]:
a = ["ein Haus", "eine Tasse", "ein Kind"]
list(map(len, a))


Out[13]:
[8, 10, 8]

prozedurale Schreibweise:


In [14]:
for i in a:
    print(len(i))


8
10
8

Aufgabe 2

Verwenden Sie map() um in einer Liste von Worten jedes Wort in Großbuchstaben auszugeben. Diskutieren Sie evtl. Probleme mit einem Nachbarn.

Aufgabe 3 (optional)

Lösen Sie Aufgabe 1 mit map()

filter()

filter(FunktionX, Liste)
Die Funktion FunktionX wird auf jedes Element der Liste angewandt. Konstruiert einen neuen Iterator, in den die Elemente der Liste aufgenommen werden, für die die FunktionX den Ausgabewert True hat.
Bsp.:


In [15]:
#returns True if x is an even number
def is_even(x): 
    return (x % 2) == 0 

b = [2,3,4,5,6]
list(filter(is_even, b))


Out[15]:
[2, 4, 6]

Aufgabe 4

Verwenden Sie filter, um aus dem folgenden Text eine Wortliste zu erstellen, in der alle Pronomina, Artikel und die Worte "dass", "ist", "nicht", "auch", "und" nicht enthalten sind:
"Ich denke auch, dass ist nicht schlimm. Er hat es nicht gemerkt und das ist gut. Und überhaupt: es ist auch seine Schuld. Ehrlich, das ist wahr."

Das itertools-Modul

Die Funktionen des itertools-Moduls lassen sich einteilen in Funktionen, die:

  • die einen neuen Iterator auf der Basis eines existierenden Iterators erzeugen.
  • die Teile der Ausgabe eines Iterators auswählen.
  • die die Ausgabe eines Iterators gruppieren.
  • die Iteratoren kombinieren

Neuen Iterator erzeugen

Diese Funktionen erzeugen einen neuen Iterator auf der Basis eines existierenden:
itertools.count(),itertools.cycle(), itertools.repeat(), itertools.chain(), itertools.isslice(), itertools.tee()

itertools.cycle(iterator) Gibt die Liste der Elemente in iterator in einer unendlichen Schleife zurück


In [16]:
import itertools
#don't try this at home:
#list(itertools.cycle([1,2,3,4,5]))

itertools.repeat(iterator, [n]) wiederholt die Elemente in iterator n mal.


In [17]:
import itertools
list(itertools.repeat([1,2,3,4], 3))


Out[17]:
[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]

itertools.chain(iterator_1, iterator_2, ...) Erzeugt einen neuen Iterator, in dem die Elemente von iterator_1, _2 usw. aneinander gehängt sind.


In [18]:
a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9]
list(itertools.chain(a, b, c))


Out[18]:
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Aufgabe 5

Verknüpfen Sie den Inhalt dreier Dateien zu einem Iterator

Teile der Ausgabe eines Iterators auswählen.

itertools.filterfalse(Prädikat, iterator) ist das Gegenstück zu filter(). Ausgabe enthält alle Elemente, für die das Prädikat falsch ist.

itertools.takewhile(Prädikat, iterator) - gibt solange Elemente aus, wie das Prädikat wahr ist

itertools.dropwhile(Prädikat, iter)entfernt alle Elemente, solange das Prädikat wahr ist. Gibt dann den Rest aus.

itertools.compress(Daten, Selektoren) Nimmt zweei Iteratoren un dgibt nur die Elemente des ersten (Daten) zurück, für die das entsprechende Element im zweiten (Selektoren) wahr ist. Stoppt, wenn einer der Iteratoren erschöpft ist.

Iteratoren kombinieren

itertools.combinations(Iterator, r) gibt alle r-Tuple Kombinationen der Elemente des Iterators wieder. Beispiel:


In [19]:
tuple(itertools.combinations([1, 2, 3, 4], 2))


Out[19]:
((1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4))

itertools.permutations(iterator, r) gibt alle Permutationen aller Elemente unabhängig von der Reihenfolge in Iterator wieder:


In [20]:
tuple(itertools.permutations([1, 2, 3, 4], 2))


Out[20]:
((1, 2),
 (1, 3),
 (1, 4),
 (2, 1),
 (2, 3),
 (2, 4),
 (3, 1),
 (3, 2),
 (3, 4),
 (4, 1),
 (4, 2),
 (4, 3))

Aufgabe 7

Wieviele Zweier-Permutationen sind mit den graden Zahlen zwischen 1 und 101 möglich?

The operator module

Mathematische Operationen: add(), sub(), mul(), floordiv(), abs(), ...
Logische Operationen: not(), truth()
Bit Operationen: and
(), or(), invert()
Vergleiche: eq(), ne(), lt(), le(), gt(), and ge()
Objektidentität: is
(), is_not()


In [21]:
a = [2, -3, 8, 12, -22, -1]
list(map(abs, a))


Out[21]:
[2, 3, 8, 12, 22, 1]

Lambda-Funktionen

lambda erlaubt es, kleine Funktionen anonym zu definieren. Nehmen wir an, wir wollen in einer List von Zahlen alle Zahlen durch 100 teilen und mit 13 multiplizieren. Dann könnten wir das so machen:


In [22]:
def calc(n):
    return (n * 13) / 100

a = [1, 2, 5, 7]
list(map(calc, a))


Out[22]:
[0.13, 0.26, 0.65, 0.91]

Diese Funktion können wir mit Lambda nun direkt einsetzen:


In [23]:
list(map(lambda x: (x *  13)/100, a))


Out[23]:
[0.13, 0.26, 0.65, 0.91]

Allerdings gibt es sehr unterschiedliche Meinungen darüber, ob auf diese Weise guter Code entsteht. Ich finde diesen Ratschlag anz gut:

  • Write a lambda function.
  • Write a comment explaining what the heck that lambda does.
  • Study the comment for a while, and think of a name that captures the essence of the comment.
  • Convert the lambda to a def statement, using that name.
  • Remove the comment.

Hausaufgabe

1) Geben Sie alle Unicode-Zeichen zwischen 34 und 250 aus und geben Sie alle aus, die keine Buchstaben oder Zahlen sind

2) Wie könnte man alle Dateien mit der Endung *.txt in einem Unterverzeichnis hintereinander ausgeben?

3) Schauen Sie sich in der Python-Dokumentation die Funktionen sort und itemgetter an. Wie kann man diese so kombinieren, dass man damit ein Dictionary nach dem value sortieren kann. (no stackoverflow :-)










In [ ]:

Lösungen

Aufgabe 1


In [24]:
#zählt die Vokale eines strings
def cv(word):
    return sum([1 for a in word if a in "aeiouAEIOUÄÖÜäöü"])

a = "Dies ist eine Lüge, oder nicht?"
[cv(w) for w in a.split()]


Out[24]:
[2, 1, 3, 2, 2, 1]







Aufgabe 2


In [25]:
#uppeditys the string word 
def upper(word):
    return word.upper()

a = ["dies", "ist", "Ein", "satz"]
list(map(upper, a))


Out[25]:
['DIES', 'IST', 'EIN', 'SATZ']









Aufgabe 3


In [26]:
def cv(word):
    return sum([1 for a in word if a in "aeiouAEIOUÄÖÜäöü"])

a = "Dies ist eine Lüge, oder nicht?"

list(map(cv, a.split()))


Out[26]:
[2, 1, 3, 2, 2, 1]









Aufgabe 4


In [27]:
import re

#returns True if word is a function word
def is_no_function_word(word):
    f_words = ["der", "die", "das", "ich", "du", "er", "sie", "es", "wir", "ihr", "dass", "ist", "hat", "auch", "und", "nicht"]
    if word.lower() in f_words:
        return False
    else: 
        return True
   
    
text = """Ich denke auch, dass ist nicht schlimm. Er hat es nicht gemerkt und das ist gut. 
          Und überhaupt: es ist auch seine Schuld. Ehrlich, das ist wahr."""

list(filter(is_no_function_word, re.findall("\w+", text)))


Out[27]:
['denke',
 'schlimm',
 'gemerkt',
 'gut',
 'überhaupt',
 'seine',
 'Schuld',
 'Ehrlich',
 'wahr']

In [ ]: