Aufwärmen · Aufgabe 1 von 7
// A1.6 · ca. 40 Min. · Vertiefung
Build: Attribut-Hooks, Klassenerzeugung, Vector und Matrix
Nach dieser Lektion greifen Sie in gescheiterte und in alle Attributzugriffe ein, führen Code für jede neue Unterklasse aus, erkennen, wann eine Metaklasse passt, und haben Vector und Matrix mit Operatoren gebaut.
Lektion 6 von 6 in A1 Das Datenmodell
Danach können Sie
- Zwischen __getattr__ und __getattribute__ wählen und beide ohne Rekursion schreiben
- __init_subclass__ mit Klassen-Schlüsselwörtern nutzen, um Unterklassen zu prüfen oder zu registrieren
- Erklären, was eine Metaklasse ist und wann sie läuft, im Vergleich zu __init_subclass__
Vorhersagen · Aufgabe 2 von 7
Sagen Sie voraus, bevor Sie weiterlesen: Die Klasse definiert __getattr__. Was gibt das aus?
class Fallback: color = "red" def __getattr__(self, name): return f"<{name} missing>" f = Fallback() print(f.color, f.size)Üben · Aufgabe 3 von 7
Ergänzen Sie den Hook, sodass fehlende Attribute "default" liefern, vorhandene aber ihren Wert behalten.
class Defaults: known = "set" def ____(self, name): return "default"def (self, name):Üben · Aufgabe 4 von 7
Counter überschreibt __getattribute__. Was gibt das aus?
class Counter: def __init__(self): self.value = 1 def __getattribute__(self, name): print("get", name, end="; ") return object.__getattribute__(self, name) c = Counter() print(c.value)Üben · Aufgabe 5 von 7
Ordnen Sie jedem Hook zu, wann Python ihn aufruft.
Denksport · Aufgabe 6 von 7
Knobelaufgabe. B erbt von A, A erbt von Base. Was gibt das aus?
class Base: count = 0 def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) Base.count += 1 class A(Base): pass class B(A): pass print(Base.count)Anwenden · Aufgabe 7 von 7
Mini-Aufgabe. Schreiben Sie eine Basisklasse Shape, die jede Unterklasse prüft, sobald deren class-Anweisung läuft: Eine Unterklasse ohne Methode area löst TypeError("<Name> must define area()") aus, und der Name jeder akzeptierten Unterklasse kommt in Shape.registry.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
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
Datensätze mit Feldern aus der Klassenzeile
Record nutzt zwei Hooks. __init_subclass__ liest das Schlüsselwort fields aus jeder Klassenzeile und registriert die Unterklasse. __getattr__ macht die gespeicherten Werte zu Attributen und läuft nur, wenn die normale Suche scheitert; book.fields kommt also weiter aus der Klasse. Fügen Sie eine Unterklasse Movie hinzu, oder lesen Sie book._data, bevor __init__ gelaufen ist.
main.py
class Record:
"""Base class: each subclass names its fields in the class line."""
fields: tuple[str, ...] = ()
kinds: dict[str, type["Record"]] = {}
def __init_subclass__(cls, /, fields: str = "", **kwargs: object) -> None:
super().__init_subclass__(**kwargs)
cls.fields = tuple(fields.split())
Record.kinds[cls.__name__.lower()] = cls
def __init__(self, *values: object) -> None:
if len(values) != len(self.fields):
raise TypeError(f"{type(self).__name__} needs {len(self.fields)} values")
self._data = dict(zip(self.fields, values))
def __getattr__(self, name: str) -> object:
# Only called when the normal lookup has failed.
if name.startswith("_"):
raise AttributeError(name) # never look up _data through itself
try:
return self._data[name]
except KeyError:
raise AttributeError(f"{type(self).__name__!r} has no field {name!r}") from None
class Book(Record, fields="title author"):
pass
class Song(Record, fields="title artist year"):
pass
print(Record.kinds)
book = Book("Dune", "Herbert")
print(book.title, book.author, book.fields)
try:
book.year
except AttributeError as err:
print(err)
print(type(Book).__name__)
Ausführen mit
python main.pyAusgabe
{'book': <class '__main__.Book'>, 'song': <class '__main__.Song'>}
Dune Herbert ('title', 'author')
'Book' has no field 'year'
type- fields="title author" ist ein Klassen-Schlüsselwort; Python reicht es an Record.__init_subclass__ weiter.
- Die Prüfung auf "_" verhindert, dass __getattr__ self._data über sich selbst sucht, wenn _data noch nicht existiert.
- Der KeyError wird zu AttributeError, also liefert hasattr(book, "year") False.
- type(Book) ist type: Dafür war keine Metaklasse nötig.
Ändern und ausführen
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.
Übungen
Übung 1 von 3
Schritt 1: ein Vector-Container mit benannten Komponenten
Modul-Build, Schritt 1 von 3. Geben Sie Vector das Container-Protokoll aus A1.1 und A1.3: __repr__ als Vector(3, 4), __len__, __iter__, __getitem__, __eq__ und ein passendes __hash__. Ergänzen Sie dann __getattr__, sodass v.x, v.y und v.z die Komponenten 0 bis 2 lesen, sofern es sie gibt; jeder andere fehlende Name löst AttributeError aus.
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.
Hinweise
Hinweis 1
Bauen Sie die repr mit ", ".join(map(repr, self._components)), damit eine Komponente Vector(1.5) ergibt.
Hinweis 2
Hashen Sie dasselbe Tupel, das __eq__ vergleicht: hash(self._components).
Hinweis 3
In __getattr__: index = self._names.find(name); nur gültig, wenn len(name) == 1 und 0 <= index < len(self._components). Prüfen Sie len(name) zuerst: So rekursiert ein fehlendes _components nie.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
from collections.abc import Iterator
class Vector:
"""An immutable vector of numbers."""
_names = "xyz"
def __init__(self, *components: float) -> None:
self._components = tuple(components)
def __repr__(self) -> str:
return f"Vector({', '.join(map(repr, self._components))})"
def __len__(self) -> int:
return len(self._components)
def __iter__(self) -> Iterator[float]:
return iter(self._components)
def __getitem__(self, index: int) -> float:
return self._components[index]
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self._components == other._components
def __hash__(self) -> int:
return hash(self._components)
def __getattr__(self, name: str) -> float:
index = self._names.find(name)
if len(name) == 1 and 0 <= index < len(self._components):
return self._components[index]
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
if __name__ == "__main__":
v = Vector(3, 4)
print(v, len(v), list(v), v[1])
print(v.x, v.y, v == Vector(3, 4), {v, Vector(3, 4)})
try:
v.z
except AttributeError as err:
print(err)
Auf dem eigenen Computer ausführen
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 Iterator
class Vector:
"""An immutable vector of numbers."""
_names = "xyz"
def __init__(self, *components: float) -> None:
self._components = tuple(components)
# Step 1 of the build. Add:
# __repr__ -> "Vector(3, 4)"
# __len__, __iter__ and __getitem__ over the components
# __eq__ (NotImplemented for non-vectors) and a matching __hash__
# __getattr__: v.x, v.y, v.z give components 0, 1, 2 if the vector
# has that many; any other name raises AttributeError.
if __name__ == "__main__":
v = Vector(3, 4)
print(v)
test_main.py
from main import Vector
def test_repr():
"""repr zeigt die Komponenten"""
assert repr(Vector(3, 4)) == "Vector(3, 4)", f"repr ist {Vector(3, 4)!r}"
assert repr(Vector(1.5)) == "Vector(1.5)", f"repr eines Vektors mit einer Komponente ist {Vector(1.5)!r}"
def test_sequence_protocol():
"""len, Iteration und Indizierung funktionieren"""
v = Vector(3, 4, 5)
got = (len(v), list(v), v[-1])
assert got == (3, [3, 4, 5], 5), f"(len(v), list(v), v[-1]) ist {got!r}"
def test_equality_and_hash():
"""Gleiche Vektoren sind gleich und haben gleiche Hashes"""
assert Vector(1, 2) == Vector(1, 2), "Vector(1, 2) != Vector(1, 2)"
assert Vector(1, 2) != Vector(2, 1), "Vector(1, 2) == Vector(2, 1)"
assert len({Vector(1, 2), Vector(1, 2)}) == 1, "gleiche Vektoren müssen gleiche Hashes haben"
assert Vector(1, 2) != (1, 2), "ein Vector sollte nicht gleich einem Tupel sein"
def test_named_components():
"""x, y und z lesen die ersten drei Komponenten"""
v = Vector(3, 4, 5)
got = (v.x, v.y, v.z)
assert got == (3, 4, 5), f"(v.x, v.y, v.z) ist {got!r}"
def test_missing_names_raise():
"""z bei einem 2D-Vektor und unbekannte Namen lösen AttributeError aus"""
for name in ["z", "w", "xy"]:
try:
getattr(Vector(3, 4), name)
except AttributeError:
continue
raise AssertionError(f"Vector(3, 4).{name} sollte AttributeError auslösen")
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
Schritt 2: Vektor-Operatoren
Schritt 2 von 3. Ergänzen Sie die Operatoren aus A1.2: v + w, v - w und -v komponentenweise, v * 2 und 2 * v, v @ w als Skalarprodukt und abs(v) als Länge. + und @ lösen bei verschiedenen Längen ValueError("lengths differ: 2 and 3") aus. Jeder Operator gibt für nicht unterstützte Operandentypen NotImplemented zurück.
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.
Hinweise
Hinweis 1
Eine Hilfsmethode _same_length(other) hält die Längenprüfung an einer Stelle.
Hinweis 2
v - w kann die anderen nutzen: return self + -other.
Hinweis 3
Akzeptieren Sie in __mul__ nur int und float, dann schreiben Sie __rmul__ = __mul__. Ohne Typprüfung würde v * "a" still einen Vektor aus Strings bauen.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import math
from collections.abc import Iterator
class Vector:
"""An immutable vector of numbers."""
_names = "xyz"
def __init__(self, *components: float) -> None:
self._components = tuple(components)
def __repr__(self) -> str:
return f"Vector({', '.join(map(repr, self._components))})"
def __len__(self) -> int:
return len(self._components)
def __iter__(self) -> Iterator[float]:
return iter(self._components)
def __getitem__(self, index: int) -> float:
return self._components[index]
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self._components == other._components
def __hash__(self) -> int:
return hash(self._components)
def __getattr__(self, name: str) -> float:
index = self._names.find(name)
if len(name) == 1 and 0 <= index < len(self._components):
return self._components[index]
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
def _same_length(self, other: "Vector") -> None:
if len(self) != len(other):
raise ValueError(f"lengths differ: {len(self)} and {len(other)}")
def __add__(self, other: object) -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
self._same_length(other)
return Vector(*(a + b for a, b in zip(self, other)))
def __sub__(self, other: object) -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
return self + -other
def __neg__(self) -> "Vector":
return Vector(*(-a for a in self))
def __mul__(self, scalar: object) -> "Vector":
if not isinstance(scalar, (int, float)):
return NotImplemented
return Vector(*(a * scalar for a in self))
__rmul__ = __mul__
def __matmul__(self, other: object) -> float:
if not isinstance(other, Vector):
return NotImplemented
self._same_length(other)
return sum(a * b for a, b in zip(self, other))
def __abs__(self) -> float:
return math.hypot(*self)
if __name__ == "__main__":
v, w = Vector(3, 4), Vector(1, 2)
print(v + w, v - w, -w)
print(v * 2, 2 * v, v @ w, abs(v))
try:
v + Vector(1, 2, 3)
except ValueError as err:
print(err)
Auf dem eigenen Computer ausführen
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 math
from collections.abc import Iterator
class Vector:
"""An immutable vector of numbers."""
_names = "xyz"
def __init__(self, *components: float) -> None:
self._components = tuple(components)
def __repr__(self) -> str:
return f"Vector({', '.join(map(repr, self._components))})"
def __len__(self) -> int:
return len(self._components)
def __iter__(self) -> Iterator[float]:
return iter(self._components)
def __getitem__(self, index: int) -> float:
return self._components[index]
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self._components == other._components
def __hash__(self) -> int:
return hash(self._components)
def __getattr__(self, name: str) -> float:
index = self._names.find(name)
if len(name) == 1 and 0 <= index < len(self._components):
return self._components[index]
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
# Step 2 of the build. Add operators; each returns NotImplemented for
# operands it does not support:
# v + w, v - w and -v, component by component; + and - raise
# ValueError("lengths differ: 2 and 3") for different lengths
# v * 2 and 2 * v (int or float only)
# v @ w, the dot product (same length check)
# abs(v), the length: math.hypot(*v)
if __name__ == "__main__":
v, w = Vector(3, 4), Vector(1, 2)
print(v + w, v - w, -w)
print(v * 2, 2 * v, v @ w, abs(v))
try:
v + Vector(1, 2, 3)
except ValueError as err:
print(err)
test_main.py
from main import Vector
def test_add_sub_neg():
"""+, - und das unäre - arbeiten komponentenweise"""
v, w = Vector(3, 4), Vector(1, 2)
got = (v + w, v - w, -w)
assert got == (Vector(4, 6), Vector(2, 2), Vector(-1, -2)), f"(v + w, v - w, -w) ist {got!r}"
def test_scalar_on_both_sides():
"""v * 2 und 2 * v ergeben denselben Vektor"""
v = Vector(1, 2)
got = (v * 2, 2 * v, 0.5 * v)
assert got == (Vector(2, 4), Vector(2, 4), Vector(0.5, 1.0)), f"(v * 2, 2 * v, 0.5 * v) ist {got!r}"
def test_dot_and_abs():
"""@ ist das Skalarprodukt und abs() die Länge"""
dot = Vector(1, 2, 3) @ Vector(4, 5, 6)
assert dot == 32, f"Vector(1, 2, 3) @ Vector(4, 5, 6) ist {dot!r}, erwartet 32"
assert abs(Vector(3, 4)) == 5.0, f"abs(Vector(3, 4)) ist {abs(Vector(3, 4))!r}, erwartet 5.0"
def test_length_mismatch():
"""Vektoren verschiedener Länge lösen ValueError aus"""
for text, op in [("+", lambda a, b: a + b), ("-", lambda a, b: a - b), ("@", lambda a, b: a @ b)]:
try:
op(Vector(1, 2), Vector(1, 2, 3))
except ValueError:
continue
raise AssertionError(f"Vector(1, 2) {text} Vector(1, 2, 3) sollte ValueError auslösen")
def test_unsupported_operands():
"""Andere Operandentypen enden über NotImplemented in TypeError"""
cases = [
("Vector(1, 2) + (1, 2)", lambda: Vector(1, 2) + (1, 2)),
('Vector(1, 2) * "a"', lambda: Vector(1, 2) * "a"),
('"a" * Vector(1, 2)', lambda: "a" * Vector(1, 2)),
("Vector(1, 2) @ 3", lambda: Vector(1, 2) @ 3),
]
for text, op in cases:
try:
op()
except TypeError:
continue
raise AssertionError(f"{text} sollte TypeError auslösen")
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
Schritt 3: eine Matrix auf Basis von Vector
Schritt 3 von 3. Matrix speichert ihre Zeilen als Vectors und hat bereits shape, T (Transponierte), Indizierung mit m[i, j], repr und Gleichheit. Ergänzen Sie m + n (ValueError bei verschiedenen Formen) und m @ other: mit einem Vector ergibt es den Vector der Zeilen-Skalarprodukte, mit einer Matrix das Matrixprodukt. Für alles andere geben Sie NotImplemented zurück.
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.
Hinweise
Hinweis 1
Zeilen sind Vectors, also erledigen row @ other und a + b bereits die Rechnung und die Längenprüfung.
Hinweis 2
Prüfen Sie in __matmul__ isinstance(other, Vector) und isinstance(other, Matrix) getrennt; der Ergebnistyp unterscheidet sich.
Hinweis 3
Eintrag (i, j) von m @ n ist Zeile i von m @ Spalte j von n. Die Spalten von n sind other.T.rows: Matrix([row @ column for column in columns] for row in self.rows).
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import math
from collections.abc import Iterable, Iterator
class Vector:
"""An immutable vector of numbers."""
_names = "xyz"
def __init__(self, *components: float) -> None:
self._components = tuple(components)
def __repr__(self) -> str:
return f"Vector({', '.join(map(repr, self._components))})"
def __len__(self) -> int:
return len(self._components)
def __iter__(self) -> Iterator[float]:
return iter(self._components)
def __getitem__(self, index: int) -> float:
return self._components[index]
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self._components == other._components
def __hash__(self) -> int:
return hash(self._components)
def __getattr__(self, name: str) -> float:
index = self._names.find(name)
if len(name) == 1 and 0 <= index < len(self._components):
return self._components[index]
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
def _same_length(self, other: "Vector") -> None:
if len(self) != len(other):
raise ValueError(f"lengths differ: {len(self)} and {len(other)}")
def __add__(self, other: object) -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
self._same_length(other)
return Vector(*(a + b for a, b in zip(self, other)))
def __sub__(self, other: object) -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
return self + -other
def __neg__(self) -> "Vector":
return Vector(*(-a for a in self))
def __mul__(self, scalar: object) -> "Vector":
if not isinstance(scalar, (int, float)):
return NotImplemented
return Vector(*(a * scalar for a in self))
__rmul__ = __mul__
def __matmul__(self, other: object) -> float:
if not isinstance(other, Vector):
return NotImplemented
self._same_length(other)
return sum(a * b for a, b in zip(self, other))
def __abs__(self) -> float:
return math.hypot(*self)
class Matrix:
"""An immutable matrix, stored as a tuple of row vectors."""
def __init__(self, rows: Iterable[Iterable[float]]) -> None:
self.rows = tuple(Vector(*row) for row in rows)
if len({len(row) for row in self.rows}) > 1:
raise ValueError("all rows must have the same length")
@property
def shape(self) -> tuple[int, int]:
return len(self.rows), len(self.rows[0]) if self.rows else 0
@property
def T(self) -> "Matrix":
return Matrix(zip(*self.rows))
def __repr__(self) -> str:
return f"Matrix({[list(row) for row in self.rows]})"
def __getitem__(self, index: tuple[int, int]) -> float:
i, j = index
return self.rows[i][j]
def __eq__(self, other: object) -> bool:
if not isinstance(other, Matrix):
return NotImplemented
return self.rows == other.rows
def __hash__(self) -> int:
return hash(self.rows)
def __add__(self, other: object) -> "Matrix":
if not isinstance(other, Matrix):
return NotImplemented
if self.shape != other.shape:
raise ValueError(f"shapes differ: {self.shape} and {other.shape}")
return Matrix(a + b for a, b in zip(self.rows, other.rows))
def __matmul__(self, other: object) -> "Matrix | Vector":
if isinstance(other, Vector):
return Vector(*(row @ other for row in self.rows))
if isinstance(other, Matrix):
columns = other.T.rows
return Matrix([row @ column for column in columns] for row in self.rows)
return NotImplemented
if __name__ == "__main__":
m = Matrix([[1, 2], [3, 4]])
identity = Matrix([[1, 0], [0, 1]])
print(m.shape, m[1, 0], m.T)
print(m + identity)
print(m @ Vector(1, 1))
print(m @ identity == m, m @ m)
Auf dem eigenen Computer ausführen
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 math
from collections.abc import Iterable, Iterator
class Vector:
"""An immutable vector of numbers."""
_names = "xyz"
def __init__(self, *components: float) -> None:
self._components = tuple(components)
def __repr__(self) -> str:
return f"Vector({', '.join(map(repr, self._components))})"
def __len__(self) -> int:
return len(self._components)
def __iter__(self) -> Iterator[float]:
return iter(self._components)
def __getitem__(self, index: int) -> float:
return self._components[index]
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self._components == other._components
def __hash__(self) -> int:
return hash(self._components)
def __getattr__(self, name: str) -> float:
index = self._names.find(name)
if len(name) == 1 and 0 <= index < len(self._components):
return self._components[index]
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
def _same_length(self, other: "Vector") -> None:
if len(self) != len(other):
raise ValueError(f"lengths differ: {len(self)} and {len(other)}")
def __add__(self, other: object) -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
self._same_length(other)
return Vector(*(a + b for a, b in zip(self, other)))
def __sub__(self, other: object) -> "Vector":
if not isinstance(other, Vector):
return NotImplemented
return self + -other
def __neg__(self) -> "Vector":
return Vector(*(-a for a in self))
def __mul__(self, scalar: object) -> "Vector":
if not isinstance(scalar, (int, float)):
return NotImplemented
return Vector(*(a * scalar for a in self))
__rmul__ = __mul__
def __matmul__(self, other: object) -> float:
if not isinstance(other, Vector):
return NotImplemented
self._same_length(other)
return sum(a * b for a, b in zip(self, other))
def __abs__(self) -> float:
return math.hypot(*self)
class Matrix:
"""An immutable matrix, stored as a tuple of row vectors."""
def __init__(self, rows: Iterable[Iterable[float]]) -> None:
self.rows = tuple(Vector(*row) for row in rows)
if len({len(row) for row in self.rows}) > 1:
raise ValueError("all rows must have the same length")
@property
def shape(self) -> tuple[int, int]:
return len(self.rows), len(self.rows[0]) if self.rows else 0
@property
def T(self) -> "Matrix":
return Matrix(zip(*self.rows))
def __repr__(self) -> str:
return f"Matrix({[list(row) for row in self.rows]})"
def __getitem__(self, index: tuple[int, int]) -> float:
i, j = index
return self.rows[i][j]
def __eq__(self, other: object) -> bool:
if not isinstance(other, Matrix):
return NotImplemented
return self.rows == other.rows
def __hash__(self) -> int:
return hash(self.rows)
# Step 3 of the build. Add, returning NotImplemented for other types:
# m + n, row by row; ValueError("shapes differ: (2, 2) and (2, 3)")
# when the shapes differ
# m @ v with a Vector: a Vector of the dot products row @ v
# m @ n with a Matrix: a Matrix whose entry (i, j) is row i of m
# @ column j of n (the columns of n are the rows of n.T)
if __name__ == "__main__":
m = Matrix([[1, 2], [3, 4]])
identity = Matrix([[1, 0], [0, 1]])
print(m.shape, m[1, 0], m.T)
print(m + identity)
print(m @ Vector(1, 1))
print(m @ identity == m, m @ m)
test_main.py
from main import Matrix, Vector
def test_add():
"""m + n addiert Eintrag für Eintrag"""
got = Matrix([[1, 2], [3, 4]]) + Matrix([[1, 0], [0, 1]])
assert got == Matrix([[2, 2], [3, 5]]), f"die Summe ist {got!r}"
def test_add_shape_mismatch():
"""Die Addition von Matrizen verschiedener Form löst ValueError aus"""
try:
Matrix([[1, 2]]) + Matrix([[1], [2]])
except ValueError:
return
raise AssertionError("eine (1, 2)- plus eine (2, 1)-Matrix sollte ValueError auslösen")
def test_matrix_times_vector():
"""m @ v ergibt einen Vector"""
got = Matrix([[1, 2], [3, 4]]) @ Vector(1, 1)
assert isinstance(got, Vector) and got == Vector(3, 7), f"m @ Vector(1, 1) ist {got!r}, erwartet Vector(3, 7)"
def test_matrix_times_matrix():
"""m @ n ist das Matrixprodukt, auch für nicht quadratische Formen"""
a = Matrix([[1, 2, 3], [4, 5, 6]])
b = Matrix([[7, 8], [9, 10], [11, 12]])
got = a @ b
assert got == Matrix([[58, 64], [139, 154]]), f"a @ b ist {got!r}"
def test_unsupported_operands():
"""Zahlen werden nicht unterstützt und enden in TypeError"""
m = Matrix([[1, 2], [3, 4]])
for text, op in [("m @ 2", lambda: m @ 2), ("m + 1", lambda: m + 1)]:
try:
op()
except TypeError:
continue
raise AssertionError(f"{text} sollte TypeError auslösen")
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 herunterladenHäufige Fehler
self.attr in __getattribute__ lesen
class Logged:
def __init__(self):
self.count = 0
def __getattribute__(self, name):
self.count += 1
return object.__getattribute__(self, name)
print(Logged().count)
Was Python ausgibt
RecursionError: maximum recursion depth exceededWarum, und die Lösung
self.count in __getattribute__ ist selbst ein Lesezugriff, also ruft sich die Methode endlos selbst auf. Lesen und schreiben Sie eigene Attribute über object.__getattribute__(self, "count") und object.__setattr__, oder nehmen Sie __getattr__, wenn Sie nur die gescheiterten Zugriffe brauchen.
Ein Klassen-Schlüsselwort, das __init_subclass__ nicht annimmt
class Plugin:
registry = {}
def __init_subclass__(cls):
super().__init_subclass__()
Plugin.registry[cls.__name__] = cls
class Csv(Plugin, name="csv"):
pass
Was Python ausgibt
TypeError: Plugin.__init_subclass__() got an unexpected keyword argument 'name'Warum, und die Lösung
Schlüsselwörter in der Klassenzeile gehen an das __init_subclass__ der Elternklasse. Deklarieren Sie die genutzten und reichen Sie den Rest weiter: def __init_subclass__(cls, /, name, **kwargs): super().__init_subclass__(**kwargs).
Zwei fremde Metaklassen mischen
class MetaA(type):
pass
class MetaB(type):
pass
class A(metaclass=MetaA):
pass
class B(metaclass=MetaB):
pass
class C(A, B):
pass
Was Python ausgibt
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its basesWarum, und die Lösung
C braucht eine Metaklasse, die Unterklasse von MetaA und MetaB ist, und die gibt es nicht. Definieren Sie class MetaAB(MetaA, MetaB) und nutzen Sie diese, oder, besser, ersetzen Sie die Metaklassen durch __init_subclass__, das sich über normale Vererbung kombiniert.
Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.