Aufwärmen · Aufgabe 1 von 7
Aufwärmen aus Modul B3: Eine Funktion ist ein Objekt, das Sie in einer Variablen ablegen können. Was gibt das aus?
def shout(text):
return text.upper() + "!"
say = shout
print(say("hi"), say.__name__)// I3.5 · ca. 35 Min. · Aufbau
Nach dieser Lektion schreiben Sie Dekoratoren, die jede Funktion umhüllen und ihren Namen behalten, merken wiederholte Aufrufe mit cache und lru_cache, füllen Argumente mit partial vor und falten Listen mit reduce.
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
def shout(text):
return text.upper() + "!"
say = shout
print(say("hi"), say.__name__)Vorhersagen · Aufgabe 2 von 7
def twice(func):
def wrapper():
func()
func()
return wrapper
@twice
def hello():
print("hello", end=" ")
hello()Üben · Aufgabe 3 von 7
import functools
def logged(func):
@functools.____(func)
def wrapper(*args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
return wrapper
@logged
def area(w, h):
"""Return the area of a rectangle."""
return w * h
print(area(2, 3), area.__name__, area.__doc__)Üben · Aufgabe 4 von 7
from functools import cache
calls = 0
@cache
def square(n):
global calls
calls += 1
return n * n
square(4)
square(4)
square(5)
print(calls)Üben · Aufgabe 5 von 7
Denksport · Aufgabe 6 von 7
def logged(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@logged
def area(w, h):
"""Return the area."""
return w * h
print(area.__name__, area.__doc__)Anwenden · Aufgabe 7 von 7
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
logged ist ein Dekorator, der jeden Aufruf ausgibt, der die Funktion erreicht. fib hat zwei Dekoratoren: außen @functools.cache, innen @logged. Das Protokoll zeigt also nur die Aufrufe, die der Cache nicht beantworten konnte. Die zweite Hälfte baut mit partial einen Binär-Parser und multipliziert mit reduce eine Liste. Callable[..., Any] ist die Annotation für „irgendeine Funktion“.
main.py
import functools
from collections.abc import Callable
from typing import Any
def logged(func: Callable[..., Any]) -> Callable[..., Any]:
"""Print every call of func with its arguments."""
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
print("call", func.__name__, args)
return func(*args, **kwargs)
return wrapper
@functools.cache
@logged
def fib(n: int) -> int:
"""Return the n-th Fibonacci number."""
return n if n < 2 else fib(n - 1) + fib(n - 2)
print(fib(5))
print(fib(6))
print(fib.cache_info())
print(fib.__name__, "-", fib.__doc__)
parse_binary = functools.partial(int, base=2)
print(parse_binary("1010"), parse_binary("111"))
product = functools.reduce(lambda acc, n: acc * n, [1, 2, 3, 4, 5], 1)
print(product)
Ausführen mit
python main.pyAusgabe
call fib (5,)
call fib (4,)
call fib (3,)
call fib (2,)
call fib (1,)
call fib (0,)
5
call fib (6,)
8
CacheInfo(hits=5, misses=7, maxsize=None, currsize=7)
fib - Return the n-th Fibonacci number.
10 7
120Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übung 1 von 3
Vervollständigen Sie den Dekorator shout. Er umhüllt eine Funktion, die einen String liefert, und macht das Ergebnis groß. Der wrapper muss alle Positions- und Schlüsselwortargumente weitergeben, und die dekorierte Funktion muss Namen und Docstring behalten. greet("ada") liefert dann "HELLO, ADA!".
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Rufen Sie das Original auf und ändern Sie, was es zurückgibt: func(*args, **kwargs).upper().
Setzen Sie @functools.wraps(func) in die Zeile über def wrapper.
wrapper nimmt *args und **kwargs und gibt beide unverändert weiter, so passt er zu jeder Funktion.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import functools
from collections.abc import Callable
from typing import Any
def shout(func: Callable[..., str]) -> Callable[..., str]:
"""Make the string result of func upper case."""
@functools.wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> str:
return func(*args, **kwargs).upper()
return wrapper
@shout
def greet(name: str, punctuation: str = "!") -> str:
"""Greet someone by name."""
return f"hello, {name}{punctuation}"
if __name__ == "__main__":
print(greet("ada"))
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
import functools
from collections.abc import Callable
from typing import Any
def shout(func: Callable[..., str]) -> Callable[..., str]:
"""Make the string result of func upper case."""
def wrapper(*args: Any, **kwargs: Any) -> str:
return func(*args, **kwargs)
return wrapper
@shout
def greet(name: str, punctuation: str = "!") -> str:
"""Greet someone by name."""
return f"hello, {name}{punctuation}"
if __name__ == "__main__":
print(greet("ada"))
test_main.py
from main import greet, shout
def test_upper():
"""greet('ada') wird groß geschrieben"""
got = greet("ada")
assert got == "HELLO, ADA!", f"greet('ada') lieferte {got!r}, erwartet: 'HELLO, ADA!'"
def test_keyword_argument():
"""Schlüsselwortargumente erreichen die Funktion"""
got = greet("bo", punctuation="?")
assert got == "HELLO, BO?", f"greet('bo', punctuation='?') lieferte {got!r}, erwartet: 'HELLO, BO?'"
def test_name_and_docstring():
"""greet behält Namen und Docstring"""
got = greet.__name__, greet.__doc__
assert got == ("greet", "Greet someone by name."), f"greet.__name__ und __doc__ sind {got!r}: Nutzen Sie functools.wraps"
def test_other_function():
"""shout funktioniert mit jeder Funktion, die einen String liefert"""
@shout
def join(*words):
return "-".join(words)
got = join("a", "b")
assert got == "A-B", f"ein dekoriertes join('a', 'b') lieferte {got!r}, erwartet: 'A-B'"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 2 von 3
count_paths(rows, cols) zählt die Wege durch ein Gitter mit rows Schritten nach unten und cols nach rechts, nur nach unten oder rechts. Die Funktion stimmt, berechnet aber dieselben kleineren Gitter immer wieder. Fügen Sie @cache aus functools hinzu, damit jedes Gitter einmal berechnet wird. Die Tests rufen count_paths.cache_info() auf, das nur eine gecachte Funktion hat.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
Ein Dekorator steht direkt in der Zeile über def.
cache ist schon importiert, die Zeile lautet also einfach @cache.
Die Rekursion ruft den Namen count_paths auf, der nach dem Dekorieren die gecachte Version ist. Also werden auch die inneren Aufrufe gecacht.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
from functools import cache
@cache
def count_paths(rows: int, cols: int) -> int:
"""Count the paths through a grid, moving only right or down."""
if rows == 0 or cols == 0:
return 1
return count_paths(rows - 1, cols) + count_paths(rows, cols - 1)
if __name__ == "__main__":
print(count_paths(2, 2))
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
from functools import cache
def count_paths(rows: int, cols: int) -> int:
"""Count the paths through a grid, moving only right or down."""
if rows == 0 or cols == 0:
return 1
return count_paths(rows - 1, cols) + count_paths(rows, cols - 1)
if __name__ == "__main__":
print(count_paths(2, 2))
test_main.py
from main import count_paths
def test_small_grids():
"""Ein 1-mal-1-Gitter hat 2 Wege, ein 2-mal-2-Gitter 6"""
got = count_paths(1, 1), count_paths(2, 2)
assert got == (2, 6), f"count_paths(1, 1) und count_paths(2, 2) lieferten {got!r}, erwartet: (2, 6)"
def test_straight_line():
"""Ohne Schritte nach unten gibt es einen Weg"""
got = count_paths(0, 5)
assert got == 1, f"count_paths(0, 5) lieferte {got!r}, erwartet: 1"
def test_bigger_grid():
"""Ein 10-mal-10-Gitter hat 184756 Wege"""
got = count_paths(10, 10)
assert got == 184756, f"count_paths(10, 10) lieferte {got!r}, erwartet: 184756"
def test_is_cached():
"""count_paths ist gecacht und nutzt Ergebnisse wieder"""
assert hasattr(count_paths, "cache_info"), "count_paths hat kein cache_info(): Setzen Sie @cache über def count_paths"
count_paths(3, 3)
hits = count_paths.cache_info().hits
assert hits > 0, f"der Cache meldet {hits} Treffer nach count_paths(3, 3)"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladenÜbung 3 von 3
Schreiben Sie zwei Funktionen. parse_base(base) gibt eine Funktion zurück, die einen String als Zahl in dieser Basis liest: parse_base(16)("ff") ist 255. Nutzen Sie partial mit int. merge_all(dicts) führt eine Liste von Dicts von links nach rechts zusammen, spätere Werte gewinnen, mit reduce und dem Operator |: [{"a": 1}, {"a": 3, "b": 2}] ergibt {"a": 3, "b": 2}. Eine leere Liste ergibt {}.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.
partial(int, base=base) ist int mit bereits gesetztem Schlüsselwort base.
a | b bildet ein neues Dict aus a und b, wobei die Werte von b gewinnen. reduce wendet das paarweise von links an.
Geben Sie reduce ein leeres Dict als Startwert, damit eine leere Liste {} liefert: reduce(lambda merged, d: merged | d, dicts, empty).
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
from collections.abc import Callable
from functools import partial, reduce
def parse_base(base: int) -> Callable[[str], int]:
"""Return a function that reads a string as a number in base."""
return partial(int, base=base)
def merge_all(dicts: list[dict[str, int]]) -> dict[str, int]:
"""Merge dicts from left to right; later values win."""
empty: dict[str, int] = {}
return reduce(lambda merged, d: merged | d, dicts, empty)
if __name__ == "__main__":
print(parse_base(16)("ff"))
print(merge_all([{"a": 1}, {"a": 3, "b": 2}]))
Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.py
from collections.abc import Callable
from functools import partial, reduce
def parse_base(base: int) -> Callable[[str], int]:
"""Return a function that reads a string as a number in base."""
return int
def merge_all(dicts: list[dict[str, int]]) -> dict[str, int]:
"""Merge dicts from left to right; later values win."""
return {}
if __name__ == "__main__":
print(parse_base(16)("ff"))
print(merge_all([{"a": 1}, {"a": 3, "b": 2}]))
test_main.py
from main import merge_all, parse_base
def test_parse_base():
"""Basis 2 liest 101 als 5, Basis 16 liest ff als 255"""
got = parse_base(2)("101"), parse_base(16)("ff")
assert got == (5, 255), f"parse_base(2)('101') und parse_base(16)('ff') lieferten {got!r}, erwartet: (5, 255)"
def test_merge():
"""Spätere Dicts gewinnen"""
got = merge_all([{"a": 1}, {"b": 2}, {"a": 3}])
assert got == {"a": 3, "b": 2}, f"merge_all lieferte {got!r}, erwartet: {{'a': 3, 'b': 2}}"
def test_merge_empty():
"""Ohne Dicts kommt ein leeres Dict"""
got = merge_all([])
assert got == {}, f"merge_all([]) lieferte {got!r}, erwartet: {{}}"
def test_inputs_unchanged():
"""merge_all ändert die übergebenen Dicts nicht"""
first = {"a": 1}
merge_all([first, {"a": 2}])
assert first == {"a": 1}, f"das erste Dict wurde zu {first!r} geändert"
Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.
Programm ausführen:
python main.pyPrüfungen ausführen (learnrun.py muss im selben Ordner liegen):
python learnrun.py testlearnrun.py herunterladendef logged(func):
def wrapper(*args, **kwargs):
print("calling", func.__name__)
return func(*args, **kwargs)
@logged
def greet(name):
return "hi " + name
print(greet("Ada"))
Was Python ausgibt
TypeError: 'NoneType' object is not callableWarum, und die Lösung
logged definiert wrapper, gibt ihn aber nie zurück. Also liefert logged None, und @logged setzt greet = None. Schreiben Sie return wrapper als letzte Zeile des Dekorators, auf derselben Einrückung wie def wrapper.
from functools import cache
@cache
def total(prices):
return sum(prices)
print(total([1, 2, 3]))
Was Python ausgibt
TypeError: unhashable type: 'list'Warum, und die Lösung
Der Cache ist ein Dict mit den Argumenten als Schlüssel, und eine Liste kann kein Dict-Schlüssel sein, weil sie veränderbar ist. Übergeben Sie stattdessen ein Tupel, total((1, 2, 3)), oder lassen Sie die Funktion ungecacht.
from functools import reduce
print(reduce(lambda a, b: a + b, []))
Was Python ausgibt
TypeError: reduce() of empty iterable with no initial valueWarum, und die Lösung
Ohne Elemente und ohne Startwert hat reduce nichts zurückzugeben. Geben Sie den Startwert als drittes Argument an: reduce(lambda a, b: a + b, [], 0) ergibt 0.
Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.