\n",
"\n",
"1. **Überblick**\n",
"1. Multiple inheritance\n",
"1. Method Resolution Order\n",
"1. Linearisierungsalgorithmus: C3\n",
"1. MRO, dependency injection und super\n",
"1. Zusammenfassung"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"source": ["# Setup\n",
"\n",
"Main point here is to set up warnings properly for presentation and to\n",
"load tutormagic, so that we can later on use pythontutor for code\n",
"animations. "
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "skip"
}
},
"outputs": [],
"execution_count": null,
"source": ["%load_ext tutormagic\n",
"import warnings\n",
"warnings.filterwarnings('ignore', category=DeprecationWarning, module='.*/IPython/.*')\n",
"\n",
"import requests\n",
"import webbrowser\n",
"from IPython.core.magic import register_line_magic\n",
"\n",
"\n",
"\n"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Was bisher geschah\n",
"\n",
"- Wir haben Grundlagen von Klassen und Objekten besprochen\n",
"- Insbes. Objektorientierte Programmierung\n",
" - Kernkonzept: Vererbung"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Dieses Kapitel\n",
"\n",
"- Wir erweitern Vererbung auf die Möglichkeit, mehrere Oberklassen zur\n",
" Vererbung zu nutzen\n",
"- Dazu müssen wir festlegen, in welcher Reihenfolge Methoden der\n",
" Oberklassen benutzt werden\n",
"- Der Fall einer rautenförmigen Klassenhierarchie wird besonders\n",
" interessant"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["
Overview
\n",
"\n",
"1. Überblick\n",
"1. **Multiple inheritance**\n",
"1. Method Resolution Order\n",
"1. Linearisierungsalgorithmus: C3\n",
"1. MRO, dependency injection und super\n",
"1. Zusammenfassung"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Grundidee\n",
"\n",
"- Vererbung diente der Code-Wiederverwendung und Spezialisierung\n",
" - Oberklasse stellt Code bereit; Unterklasse erweitert, überschreibt ggf.\n",
"- Was, wenn Code aus unterschiedlichen Klassen benutzt werden soll?\n",
"- Siehe auch [Python-Dokumentation](https://docs.python.org/3.5/tutorial/classes.html#inheritance)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Beispiel Uhr, Kalender\n",
"\n",
"- Uhr: Klasse, die Uhrzeit bereit hält und auf `tick` eine Sekunde weiterzählt\n",
" - Um Mitternach auf 0:00:00 zurückspringt\n",
"- Kalender: Klasse, die Datum bereit hält und auf `next_day` einen Tag weiterzählt\n",
"- Siehe [Details](http://www.python-course.eu/python3_multiple_inheritance.php)"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class Clock:\n",
" def __init__(self, hour, minute, second):\n",
" # ...\n",
" def tick(self):\n",
" self.second += 1\n",
" if self.second > 60:\n",
" ...\n",
"\n",
"class Calendar:\n",
" def __init__(self, day, month, year):\n",
" #...\n",
"\n",
" def next_day(self):\n",
" self.day += 1\n",
" if self.day > 28:\n",
" # check month ..."
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Beispiel: Uhr und Kalender?\n",
"\n",
"- Wie baut man eine *Uhr mit Kalender*?\n",
" - Von `Clock` ableiten, und die Kalender-Funktionen nachimplementieren?\n",
" - Umgekehrt?\n",
"- Nicht schön!"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Mehrfache Oberklassen!\n",
"\n",
"- Natürliche Idee: Wir erben von zwei Klassen statt nur einer\n",
" - Können auf die Methoden dieser Klassen zugreifen, überschreiben, …\n",
"\n",
"![img](./uml/mi-clock.png \"Mehrere Oberklassen: Clock und Calendar\")"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Syntax?\n",
"\n",
"- Naheliegend: Einfache Vererbung mit Oberklasse in Klammer\n",
"- Mehrere Oberklassen: Komma-separierte Liste der Oberklassen"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class C(Oberklasse1, Oberklasse2, ...):\n",
" ..."
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Semantik?\n",
"\n",
"In diesem einfachen Beispiel ist die Semantik klar: \n",
"\n",
"- Methodenaufruf: eindeutig zuzuordnen \n",
" - Methodennamen der Oberklassen unterscheiden sich!\n",
"- Datenzugriff: Passiert sowieso im Namensraum des Objekts"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Einfaches Beispiel\n",
"\n",
"Schauen wir uns das mit einem einfachen Beispiel an"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class A():\n",
" def m_a(self):\n",
" print(\"a\")\n",
"\n",
"class B():\n",
" def m_b(self):\n",
" print(\"b\")\n",
"\n",
"class C(A, B):\n",
" pass\n",
"\n",
"c = C()\n",
"c.m_a()\n",
"c.m_b()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Gleiche Methodennamen in Oberklassen?\n",
"\n",
"Was passiert, wenn der gleiche Methodenname in mehreren Oberklassen auftritt? "
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class A():\n",
" def m(self):\n",
" print(\"a\")\n",
"\n",
"class B():\n",
" def m(self):\n",
" print(\"b\")\n",
"\n",
"# erst A, dann B: \n",
"class C(A, B): pass\n",
"C().m()\n",
"\n",
"# erst B, dann A:\n",
"class D(B, A): pass\n",
"D().m()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Gleiche Methodennamen in Oberklassen – Reihenfolge!\n",
"\n",
"Offenbar ist die Reihenfolge der Oberklassen wichtig \n",
"\n",
"- Vermutung: Methoden werden in den Oberklassen gesucht\n",
" - Liste von links nach rechts gelesen\n",
"- Regel: Die Methode der zuerst angegebenen Klasse überdeckt die\n",
" Methode der später angegebenen Klasse \n",
" - (Da fehlen noch Details!)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Insbesondere: `__init__` ?\n",
"\n",
"Beispiel: `__init__` taucht in den meisten Klassen auf! \n",
"\n",
"- Vereinbaren von Daten?"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["%%tutor -t\n",
"class A():\n",
" def __init__(self):\n",
" self.a = 17\n",
"\n",
"class B():\n",
" def __init__(self):\n",
" self.b = 42\n",
"\n",
"# erst A, dann B: \n",
"class C(A, B): pass\n",
"\n",
"c = C()\n",
"print(c.a)\n",
"print(c.b)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# `__init__` typischerweise mit `super`?\n",
"\n",
"Ist das Problem, dass wir in Klasse `C` keinen Konstruktor haben, der\n",
"den Konstruktor der Oberklasse aufruft? "
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["%%tutor -t \n",
"class A():\n",
" def __init__(self):\n",
" self.a = 17\n",
"\n",
"class B():\n",
" def __init__(self):\n",
" self.b = 42\n",
"\n",
"# erst A, dann B: \n",
"class C(A, B):\n",
" def __init__(self):\n",
" super().__init__() \n",
"\n",
"c = C()\n",
"print(c.a)\n",
"print(c.b)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# `__init__` ruft Konstruktoren explizit auf?\n",
"\n",
"Zur Erinnerung: Man kann Methoden der Klassen auch explizit aufrufen "
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class A():\n",
" def __init__(self):\n",
" self.a = 17\n",
"\n",
"class B():\n",
" def __init__(self):\n",
" self.b = 42\n",
"\n",
"# erst A, dann B: \n",
"class C(A, B):\n",
" def __init__(self):\n",
" A.__init__(self)\n",
" B.__init__(self)\n",
"\n",
"c = C()\n",
"print(c.a)\n",
"print(c.b)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Beobachtung\n",
"\n",
"- Das funktioniert!\n",
"- Aber ist das schön? Verletzt DRY!\n",
" - Was, wenn man die Klassenhierarchie ändert und `C` von anderen\n",
" Klassen ableitet?\n",
" - Und das nicht nur im Konstruktor benutzt wird?"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# `super` im `__init__` der Oberklasse?\n",
"\n",
"Was würde eigentlich passieren, wenn die Oberklasse im Konstruktur ebenfalls\n",
"`super` benutzt? \n",
"\n",
"- Sie könnte ja ihrerseits abgeleitet sein\n",
"- (Und das ist sie auch – alle Klassen sind Unterklassen von\n",
" `object`!)\n",
"- In gewissem Sinne kanonischer Code:"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class A():\n",
" def __init__(self):\n",
" print(\"A init\") \n",
" super().__init__() \n",
" self.a = 17\n",
"\n",
"class B():\n",
" def __init__(self):\n",
" print(\"B init\")\n",
" super().__init__() \n",
" self.b = 42\n",
"\n",
"# erst A, dann B: \n",
"class C(A, B):\n",
" def __init__(self):\n",
" print(\"C init\")\n",
" super().__init__() \n",
"\n",
"c = C()\n",
"print(c.a)\n",
"print(c.b)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Beobachtung\n",
"\n",
"- Entscheidend scheint der Aufruf von `super().__init__()` in Klasse A\n",
" zu sein\n",
"- Das hängt offenbar mit der Reihenfolge von `C(A,B)` vs. `C(B,A)`\n",
" zusammen\n",
"- Das müssen wir genauer anschauen! (Abschnitt Method Resolution\n",
" Order; siehe unten)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Gleiche Daten?\n",
"\n",
"Kleine Variation zu oben: Beide Klassen benutzten gleiches\n",
"Datenattribut? \n",
"\n",
"- Offenbar nur ein `x`\n",
"- Das ist plausibel, wird zur Laufzeit angelegt!"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["%%tutor -t \n",
"class A():\n",
" def __init__(self):\n",
" print(\"A init\") \n",
" super().__init__() \n",
" self.x = 17\n",
"\n",
"class B():\n",
" def __init__(self):\n",
" print(\"B init\") \n",
" super().__init__() \n",
" self.x = 42\n",
"\n",
"# erst A, dann B: \n",
"class C(A, B):\n",
" def __init__(self):\n",
" print(\"C init\") \n",
" super().__init__() \n",
"\n",
"c = C()\n",
"print(c.x)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["
Overview
\n",
"\n",
"1. Überblick\n",
"1. Multiple inheritance\n",
"1. **Method Resolution Order**\n",
"1. Linearisierungsalgorithmus: C3\n",
"1. MRO, dependency injection und super\n",
"1. Zusammenfassung\n",
""
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Reihenfolge der Methodenaufrufe\n",
"\n",
"Kommen wir auf dieses Beispiel zurück: "
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class A():\n",
" def __init__(self):\n",
" print(\"A init\") \n",
" super().__init__() \n",
"\n",
"class B():\n",
" def __init__(self):\n",
" print(\"B init\")\n",
" super().__init__() \n",
"\n",
"# erst A, dann B: \n",
"class C(A, B):\n",
" def __init__(self):\n",
" print(\"C init\")\n",
" super().__init__() \n",
"\n",
"c = C()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Fragen: Reihenfolge\n",
"\n",
"- In welcher Reihenfolge werden die Methoden hier aufgerufen?\n",
"- Was wäre wünschenswert?\n",
"- Wie kann man das beeinflussen?\n",
"- Gibt es relevante Sonderfälle?\n",
"\n",
"- Siehe [hier für Details](https://www.python.org/download/releases/2.3/mro/) (für Python 2.3 beschrieben; auch für Python\n",
" 3 gültig)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Suche nach auszuführender Methode, erste Ideen\n",
"\n",
"- Finde Attribute spezialisierterer Klassen vor denen allgemeinerer Klassen\n",
"- Zwischen gleichrangigen Klassen, lass den Programmierer der\n",
" Unterklasse bestimmen\n",
" - Durch Angabe der Reihenfolge der Oberklasse für direkt Vorgänger\n",
" schon erfüllt"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Suche nach auszuführender Methode, Sonderfall Einzelvererbung\n",
"\n",
"Sonderfall (wie bisher): eine Klasse nur jeweils genau eine Oberklasse bis zu `object`\n",
"\n",
"- Also keine Mehrfachvererbung\n",
"- Man sagt: *die Klassenhierarchie ist nicht **nach oben** verzweigt*\n",
"\n",
"Dann ist die auszuführende Methode einfach zu finden: \n",
"\n",
"- Suche in der Klasse selbst\n",
"- Wenn nicht erfolgreich, suche in (eindeutig bestimmter) Oberklasse\n",
"- Und rekursiv fortsetzen\n",
"\n",
"Verallgemeinerung? "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Linearisierung nötig für Suche\n",
"\n",
"Wir brauchen: eine *Linearisierung* der Klassenhierachie \n",
"\n",
"- Aus dem Graph der Oberklassen wird eine Liste der Klassennamen\n",
"- Die Linearisierung bestimmt die Reihenfolge, in der nach Methoden\n",
" gesucht wird\n",
" - Die *Method Resolution Order* (MRO)\n",
"- Beispiel: Aus `class C(A, B)` wird die Linearisierung `C, A, B`"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Regel?\n",
"\n",
"Was ist eine **sinnvolle** Regel, um Linearisierung zu bestimmen? "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Zugriff auf MRO\n",
"\n",
"Python erlaubt unmittelbar Zugriff auf die MRO einer Klasse: Attribut\n",
"`__mro__` "
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class A(): pass\n",
"class B(A): pass\n",
"class C(B): pass \n",
"print(C.__mro__)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Anforderungen an sinnvolle Linearisierung: Monotonie\n",
"\n",
"Angenommen: \n",
"\n",
"- Wir betrachten die Linearisierung einer Klasse `C`\n",
"- Darin kommt `C1` vor `C2`\n",
"- Wir leiten von `C` eine neue Klasse `Cneu` ab\n",
"- Was sollte dann in der Linearisierung von `Cneu` für die Reihenfolge\n",
" von `C1` und `C2` gelten?"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["
Definition: Monotonie
\n",
"\n",
"Kommt in der Linearisierung einer Klasse `C` die Klasse `C1` vor der\n",
"Klasse `C2`, so kommt `C1` auch vor `C2` in allen Linearisierungen von\n",
"Unterklassen von `C`. \n",
"\n",
""
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Warum?\n",
"\n",
"Andernfalls: durch Ableiten einer Klasse von `C` könnte man die\n",
"Aufrufreihenfolge von Methoden verändern. Mit sehr subtilen Bugs. "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Anforderungen an sinnvolle Linearisierung: Local precedence ordering"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"source": ["
Definition: Local precedence ordering
\n",
"\n",
"Die Reihenfolge der direkten Oberklassen einer Klasse `C` soll in der\n",
"Linearisierung der Klasse `C` respektiert werden. \n",
"\n",
""
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Warum?\n",
"\n",
"Abweichendes Verhalten würde sicherlich jeden Programmierer\n",
"verwundern. "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# MRO: Erste Idee\n",
"\n",
"- Wir schauen von der Klasse nach oben\n",
"- Wir suchen von links nach rechts in der Oberklassendeklaration\n",
"- Wir durchsuchen die Oberklassen\n",
" - Option 1: Erst alle Eltern, dann die Großeltern, usw. (eine *Breitensuche*)\n",
" - Option 2: Erst die erste Oberklasse (der erste Elter), dann dessen\n",
" Eltern, usw; dann die zweite Oberklasse (der zweite Elter),\n",
" … (eine *Tiefensuche*)\n",
"- Besuchen wir eine Klasse zweimal? Nein\n",
"\n",
"Beispiele? "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Darstellung: einfachere Klassendiagramme\n",
"\n",
"- Zunächst: Wir vereinfachen die Klassendiagramme (UML braucht zu viel\n",
" Platz)\n",
"- Zwei äquivalente Darstellungen von `class C(A,B)`\n",
"\n",
"![img](./uml/simpler-graph-0.png \"Zwei äquivalente Darstellungen von `class C(A,B)`: UML\")\n",
"\n",
"![img](./uml/simpler-graph-1.png \"Zwei äquivalente Darstellungen von `class C(A,B)`: Vereinfachtes Diagramm; Ziffern geben Reihenfolge der Oberklassen an\")"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Beispiel: Ein Klassendiamand\n",
"\n",
"Anmerkung: \n",
"\n",
"- Auf die Reihenfolge bei `A` und `B` achten!\n",
"- `X(object)` ist nicht nötig; hier nur zur Verdeutlichung\n",
"\n",
"![img](./uml/diamond.png \"Vereinfachtes Klassendiagramm für Diamanten\")"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class X(object): pass \n",
"class Y(object): pass \n",
"class A(X, Y): pass \n",
"class B(Y, X): pass\n",
"print(A.__mro__) \n",
"print(B.__mro__)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Problem: Nicht alle Fälle linearisierbar\n",
"\n",
"Es gibt Klassenhierarchien zu denen keine sinnvolle Linearsierung\n",
" bestimmt werden kann\n",
"\n",
"- Beispiel: *Diamond of death*"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class X(object): pass\n",
"class Y(object): pass\n",
"class A(X,Y): pass\n",
"class B(Y,X): pass\n",
"class C(A, B): pass"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"source": ["## Diagramm\n",
"\n",
"![img](./uml/not-linearizable.png \"Nicht linearisierbare Klassenhierarchie\")"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Problem: Nicht alle Fälle linearisierbar (2)\n",
"\n",
"Kern des Problems: Unterschiedliche Reihenfolgen von `X`, `Y` bei `A`,\n",
"`B`"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class X(object):\n",
" def m(self): pass\n",
"class Y(object): \n",
" def m(self): pass\n",
"\n",
"class A(X,Y):\n",
" def a(self):\n",
" m()\n",
"class B(Y,X):\n",
" def b(self):\n",
" m()\n",
"\n",
"class C(A, B):\n",
" def m(self):\n",
" self.a()\n",
" self.b()\n",
" super().m()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Uneinheitliche Reihenfolge\n",
"\n",
"Was passiert bei: \n",
"\n",
"- C.a(): Methode A.a() aufgerufen, die wird wohl X.m() aufrufen\n",
"- C.b(): Methode B.b() aufgerufen, die wird wohl Y.m() aufrufen\n",
" - Also inkonsistentes Verhalten der Klasse `C` bzgl. Aufruf von `m`!\n",
"- `super` in `C`: Welches `super.m` nehmen? `X.m`? `Y.m`?\n",
" - Unklar; beides ist plausibel wegen Verhalten bei `C.a`, `C.b`"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"source": ["## Optionen?\n",
"\n",
"- Option 1: Irgendeine Reihenfolge festlegen; Inkonsistenz akzeptieren\n",
"- Option 2: Solche Klassenhierarchien nicht erlauben\n",
" - Python 3: Wirft Ecxeption beim Versuch, `class C` wie oben zu\n",
" definieren"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Gesucht: Vorgehen für Linearisierung\n",
"\n",
"Wir brauchen also: \n",
"\n",
"- Einen Algorithmus, der eine sinnvolle Linearisierung bestimmt wenn\n",
" möglich\n",
" - Also: Im Einklang mit Monotonie und local precedence\n",
"- Oder einen Fehler mitteilt falls nicht möglich"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["
Overview
\n",
"\n",
"1. Überblick\n",
"1. Multiple inheritance\n",
"1. Method Resolution Order\n",
"1. **Linearisierungsalgorithmus: C3**\n",
"1. MRO, dependency injection und super\n",
"1. Zusammenfassung"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Linearisierungsalgorithmus: C3\n",
"\n",
"- Python benutzt den C3 Algorithmus\n",
" - Aus Sprache Dylan; auch in anderen OO-Sprachen benutzt\n",
" - [Quelle](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.19.3910&rep=rep1&type=pdf)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Terminologie\n",
"\n",
"- Linearisierung einer Klasse (die MRO): eine geordnete Liste aller\n",
" (direkten oder indirekten) Oberklassen der Klasse\n",
" - Notation: C1 C2 … CN\n",
"- *Head* einer Liste `L`: das erste Element, `L[0]`\n",
"- *Tail* einer Liste `L`: der Rest der Liste ab zweitem Element,\n",
" `L[1:]`, ggf `None`\n",
"- *Verkettung*: als + geschrieben"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Grundidee\n",
"\n",
"Die Linearisierung `Lin` einer Klasse `C` ist \n",
"\n",
"- die Klasse `C` selbst,\n",
"- verkettet mit der Kombination (*merge*)\n",
" - der Linearisierung seiner\n",
" direkten Oberklassen und\n",
" - der Liste der direkten Oberklassen (in Reihenfolge wie angegeben)\n",
" - Um local precedence sicherzustellen\n",
"- Rekursiver Algorithmus!\n",
" - Basisfall: `Lin(object) = object`\n",
"\n",
"Oder kurz: \n",
"\n",
"`Lin(C(B1, B2, ..., Bn)) = C + merge(Lin(B1), Lin(B2), ..., Lin(Bn),\n",
"[B1, B2, ... Bn]))`"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Merge\n",
"\n",
"Was bedeutet *merge*?\n",
"\n",
"- Funktion merge durchsucht Oberklassen, sucht nach Konflikten\n",
"- merge arbeitet auf mehreren Linearisierungen, produziert daraus eine\n",
" (oder Fehler)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Gewünschtes Verhalten: Klasse von `object` abgeleitet\n",
"\n",
"`Lin(C(object)) = C + merge(Lin(object), [object])`\n",
"\n",
"- `= C + merge([object], [object])`\n",
"- `= C + object`"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Merge – *good heads*\n",
"\n",
"Wir brauchen das Konzept eines *good heads* um Merge zu formulieren \n",
"\n",
"- Gegeben:\n",
" - Eine Liste von Linearisierung: [Lin1, Lin2, … Lin n]\n",
" - Jede Linearisierung ist eine Liste von Klassen\n",
" - Leere Linearisierungen werden ignoriert\n",
"- Gesucht: Ist der Head einer Liste *good* ?\n",
" - Ein Head ist good wenn er in keinem Tail irgendeiner Liste vorkommt\n",
" - Ein good head darf head mehrerer Listen sein"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Beispiel\n",
"\n",
"- [ABC, BCD, BCA] – weder A noch B sind good heads\n",
"- [ABC, BCD, BCF] – A ist good, B nicht"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Merge – Definition\n",
"\n",
"- `merge (Lin1, ... Lin n) = X + merge(Lin1 - X, ... Lin n - X)`\n",
" falls X der **erste** good head in Lin1, …, Lin n ist\n",
" - `merge( [], ... , []) = []`\n",
" - Notation `l - X`: die Liste `l` aus der der Wert `X` entfernt wurde\n",
"- Nicht definiert (Fehler), falls es keinen good head gibt"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Beispiele\n",
"\n",
"- `merge([ABC, BCD, BCA]) = Fehler`\n",
" - Fehler: Weder A noch B sind good heads\n",
"- `merge([ABC, BCD, BCF]) = A + merge([BC, BCD, BCF]) = A + B + merge([C, CD, CF]) = A + B + C + merge([ [] , D, F]) = A + B + C + D + F`\n",
" - Leere Linearisierungen werden ignoriert\n",
"- `merge([ABC, BCD, FCB]) = A + merge([BC, BCD, FCB]) = A + F + merge([BC, BCD, CB]) = A + F + Fehler = Fehler`\n",
" - Fehler tritt erst später auf; insgesamt aber doch Fehler\n",
"- `merge([AB, CB, DB]) = A + merge([B, CB, DB]) = A + C + merge([ B, B, DB]) = A + C + D + merge(B, B, B) = A + C + D + B`\n",
" - `B` ist erst im letzten Schritt ein good head\n",
"- `merge(ABEF, CD, BEF) = A + B + E + F + C + D`\n",
" - Wir fangen jeweils wieder vorne an! Tiefensuche!"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Klassendiagramm-Beispiel 0: Keine überlappenden Oberklassen\n",
"\n",
"![img](./uml/mro0-0.png \"Einfachstes Beispiel für C3-Linearisierung: Keine überlappenden Oberklassen\")"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class D(object): pass \n",
"class E(object): pass \n",
"class F(object): pass \n",
"class G(object): pass \n",
"class B(D,E): pass \n",
"class C(F,G): pass\n",
"class A(B,C): pass\n",
"print(B.__mro__) \n",
"print(C.__mro__) \n",
"print(A.__mro__)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"source": ["## Beobachtung\n",
"\n",
"- Keine überlappenden Oberklassen (außer `object`)\n",
"- Beobachtung: Tiefensuche!"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Klassendiagramm-Beispiel 0 (2)\n",
"\n",
"Linearisierung (O kurz für object): \n",
"\n",
"- Trivial: Lin(D)=DO, Lin(E)=EO, Lin(F)=FO, Lin(G)=GO\n",
"- Lin(B) = B + merge(DO, EO, DO) = B + D + merge(O, EO, O) = B + D +\n",
" E + merge(O, O, O) = BDEO\n",
"- Lin(C) = CFGO entsprechend\n",
"- Lin(A) = A + merge(BDEO, CFGO, BC)\n",
" - = A + B + merge(DEO, CFGO, C)\n",
" - = A + B + D + merge(EO, CFGO, C)\n",
" - Wir fangen beim nächsten merge-Schritt wieder mit dem ersten\n",
" Argument an, falls möglich\n",
" - Das führt zu Tiefensuche\n",
" - = A + B + D + E + merge(O, CFGO, C)\n",
" - O wird aufgeschoben (nicht good), da es Oberklasse von C ist; Linearisierung \n",
" von C muss erst eingebaut werden\n",
" - = A + BDE + CFG + merge(O, O, []) = ABDECFGO"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Klassendiagramm-Beispiel 0 (3)\n",
"\n",
"Pfeil: Reihenfolge der Methodensuche ab `A` \n",
"\n",
"![img](./uml/mro0-1.png \"Triviales Beispiel für C3-Linearisierung mit MRO\")"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Beobachtung: Teilsequenz\n",
"\n",
"- Die Linearisierungen von `B` und `C` sind als Teilsequenzen in der\n",
" Linearisierung von `A` enthalten\n",
"- Ist das immer so?\n",
"\n",
"Nein – siehe folgende Beispiele! "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Klassendiagramm-Beispiel 1\n",
"\n",
"Ziffern: Reihenfolge der Oberklassen\n",
"\n",
"![img](./uml/mro1-0.png \"Erstes Beispiel für C3-Linearisierung\")"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class D(object): pass \n",
"class E(object): pass \n",
"class F(object): pass\n",
"class B(D,E): pass \n",
"class C(D,F): pass\n",
"class A(B,C): pass\n",
"print(B.__mro__) \n",
"print(C.__mro__) \n",
"print(A.__mro__)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Klassendiagramm-Beispiel 1 (2)\n",
"\n",
"Linearisierung (O kurz für object): \n",
"\n",
"- Trivial: Lin(D) = DO, Lin(E) = EO, Lin(F) = FO\n",
"- Lin(B) = B + merge(DO, EO, DE) = B + D + merge(O, EO, E) = B + D +\n",
" E + merge(O, O, O) = BDEO\n",
"- Lin(C) = CDFO (analog)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Interessant: Lin(A)\n",
"\n",
"- Lin(A) = A + merge(Lin(B), Lin(C), BC)\n",
" - = A + merge(BDEO, CDFO, BC)\n",
" - = A + B + merge(DEO, CDFO, C)\n",
" - = A + B + C + merge(DEO, DFO, [])\n",
" - = A + B + C + D + merge(EO, FO, [])\n",
" - = ABCDEFO"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Klassendiagramm-Beispiel 1 (3)\n",
"\n",
"Grüner Pfeil: Reihenfolge der Methodensuche ab `A` \n",
"\n",
"![img](./uml/mro1-1.png \"Erstes Beispiel für C3-Linearisierung mit MRO\")"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Klassendiagramm-Beispiel 2\n",
"\n",
"Kleine Änderung: Reihenfolge bei Klasse `B` vertauscht \n",
"\n",
"![img](./uml/mro2-0.png \"Zweites Beispiel für C3-Linearisierung\")"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class F(object): pass\n",
"class E(object): pass\n",
"class D(object): pass\n",
"class C(D,F): pass\n",
"class B(E,D): pass\n",
"class A(B,C): pass\n",
"print(B.__mro__) \n",
"print(C.__mro__) \n",
"print(A.__mro__)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Klassendiagramm-Beispiel 2\n",
"\n",
"![img](./uml/mro2-1.png \"Zweites Beispiel für C3-Linearisierung mit MRO für A, B, C\")"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Praktische Konsequenz?"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"source": ["
Achtung! Reihenfolge der Oberklasse
\n",
"\n",
"Achten Sie auf Reihenfolge der Oberklassen!!\n",
"\n",
"Ein unachtsames Umdrehen führt zum Ausführen anderer Methoden – in\n",
"diesen Beispielen würde statt einer Methode `D.m` eine Methode\n",
"gleichen Namens `E.m` ausgeführt! \n",
"\n",
" "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# C3: Nicht-linearisierbare Hierarchie\n",
"\n",
"Erinnerung: Dieses Beispiel nicht linearisierbar"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class X(object): pass\n",
"class Y(object): pass\n",
"class A(X,Y): pass\n",
"class B(Y,X): pass\n",
"class C(A, B): pass"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"source": ["## Verhalten von C3?\n",
"\n",
"Lin(C) = C + merge(Lin(A), Lin(B), AB))\n",
"\n",
"- = C + merge(AXYO, BYXO, AB)\n",
"- = C + A + merge(XYO, BYXO, B)\n",
"- = C + A + B + merge(XYO, YXO, [])\n",
"\n",
"Abbruch, da weder X noch Y ein *good head* sind – zirkuläre Struktur "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["
Overview
\n",
"\n",
"1. Überblick\n",
"1. Multiple inheritance\n",
"1. Method Resolution Order\n",
"1. Linearisierungsalgorithmus: C3\n",
"1. **MRO, dependency injection und super**\n",
"1. Zusammenfassung"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# MRO und Methodenaufrufe in Oberklassen\n",
"\n",
"Schauen wir uns dieses Beispiel genauer an (vereinfachte Version von\n",
"Beispiel 2 oben): "
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class D: pass\n",
"class E:\n",
" def m(self):\n",
" print(\"m in E\")\n",
"class B(D, E):\n",
" def f(self):\n",
" print(\"f in B, calling m\")\n",
" self.m()\n",
"\n",
"class C(E):\n",
" def m(self):\n",
" print(\"m in C\")\n",
"\n",
"class A(B, C): pass\n",
"\n",
"# Ausgabe: m in D (klar) \n",
"print(B.__mro__)\n",
"b = B()\n",
"b.f()\n",
"\n",
"# Ausgabe hier? \n",
"print(A.__mro__)\n",
"a = A()\n",
"a.f()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# MRO und Methodenaufrufe in Oberklassen – Veranschaulichung\n",
"\n",
"![img](./uml/depend-inject.png \"Methodenaufrufe aus Oberklassen: MRO\")"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# MRO und Methodenaufrufe in Oberklassen – Analyse `b.f()`\n",
"\n",
"Was passiert bei den beiden Aufrufen von `f`? \n",
"\n",
"- Bei Aufruf `b.f()`:\n",
" - `b` (referenziert unter dem Namen `self` in den Methoden) ist ein Objekt der Klasse `B`\n",
" - Also wird die MRO dieser Klasse benutzt, um das richtige `f` zu\n",
" finden\n",
" - MRO: BDEO\n",
" - Unter diesen Klassen gibt es ein `f` nur in Klasse `B`; dieses `f` wird ausgeführt\n",
" - `f` ruft dann `m` auf\n",
" - Wieder wird MRO: BDEO benutzt\n",
" - Unter diesen Klassen gibt es ein `m` nur in Klasse `E`; dieses `m` wird ausgeführt"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# MRO und Methodenaufrufe in Oberklassen – Analyse `a.f()`\n",
"\n",
"- Bei Aufruf `a.f()`:\n",
" - `a` (referenziert unter dem Namen `self` in den Methoden) ist ein Objekt der Klasse `A`\n",
" - Also wird die MRO dieser Klasse benutzt, um das richtige `f` zu\n",
" finden\n",
" - MRO: ABDCEO\n",
" - Unter diesen Klassen gibt es ein `f` nur in Klasse `B`; dieses `f` wird ausgeführt\n",
" - `f` ruft dann `m` auf\n",
" - Welches `m` ist jetzt gemeint?\n",
" - Es gibt `m` in `E` und `C`\n",
" - **Entscheidender Punkt**: Bei der Suche nach `m` wird das MRO von\n",
" Klasse `A` benutzt!\n",
" - Obwohl `f` eine Methode der Klasse `B` ist\n",
" - Aber das ist egal – es geht um die MRO der Klasse des\n",
" *Objektes*, nicht die MRO der Klasse der gerade ausgeführten\n",
" Methode\n",
" - Also nach MRO: ABDCEO wird eine Methode `m` zuerst in `C` gefunden\n",
" - Also wird `C.m` ausgeführt!"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# MRO: Klasse des Objektes entscheidend"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"source": ["
Achtung! Klasse des Objektes entscheidet über MRO
\n",
"\n",
"Bei einem Methodenaufruf entscheidet die Klasse des Objektes, welche\n",
"MRO benutzt wird. Die Klasse der dabei gerade aufgeführten Methode ist\n",
"egal. \n",
"\n",
" "
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# MRO: Nutzung von `super`?\n",
"\n",
"Was passiert, wenn in einer Methode die Funktion `super` aufgerufen\n",
"wird? \n",
"\n",
"- Beispiel: `super().m()`\n",
"- Bisherige Intention: `super` liefert die Oberklasse, ruft dort die\n",
" Methode `m` auf"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "fragment"
}
},
"source": ["## Intuition passt nicht!\n",
"\n",
"- Diese Intuition war korrekt im Fall der Einfachvererbung\n",
"- Bei Mehrfachvererbung: *die* Oberklasse ist nicht mehr eindeutig\n",
" bestimmt\n",
"- Wir brauchen präzisere Definition für `super()`!"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# `super` bei Mehrfachvererbung: MRO\n",
"\n",
"- `super` sucht nach einer auszuführenden Methode\n",
"- Für diese Suche haben wir ja die MRO definiert"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"source": ["
Definition: `super` und MRO
\n",
"\n",
"Sei `c` ein Objekt einer Klasse `C`, die Klasse `B` eine (direkte oder\n",
"indirekte) Oberklasse von `C` (oder auch `C` selbst). \n",
"\n",
"Wird die Funktion `super` bei der Ausführung einer Methode `m1` der\n",
"Klasse `B` aufgerufen um eine Methode `m2` aufzurufen, so wird `m2` in der\n",
"MRO **von `C`** gesucht, **ab Klasse `B`**! \n",
"\n",
""
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# `super` und MRO: Beispiel 1\n",
"\n",
"Achten Sie auf den Unterschied der Ausgabe in den beiden Aufrufen von\n",
"`m` in `D.f`"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class B:\n",
" def m(self):\n",
" print(\"m in B\")\n",
"class C:\n",
" def m(self):\n",
" print(\"m in C\")\n",
"\n",
"class D(C):\n",
" def f(self):\n",
" # Auf diesen Unterschied achten: \n",
" print(\"f in D, calling m via super: \") \n",
" super().m()\n",
" print(\"f in D, calling m via self: \") \n",
" self.m()\n",
"\n",
"class A(B, D): \n",
" def f(self):\n",
" print(\"f in A, calling super\")\n",
" super().f()\n",
"\n",
"# Ausgabe hier? \n",
"print(A.__mro__)\n",
"a = A()\n",
"a.f()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# `super` und MRO: Beispiel 2\n",
"\n",
"- MRO von `A`: ABDCO\n",
"- Aufruf von `super().m()` in `B.m` führt zum Aufruf einer *tieferen*\n",
" Klasse als `B` selbst!\n",
" - Keineswegs eine Oberklasse!"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class O:\n",
" def m(self):\n",
" print(\"I am the ultimate m\")\n",
"class B(O):\n",
" def m(self):\n",
" print(\"m in B\")\n",
" super().m() \n",
"class C(O):\n",
" def m(self):\n",
" print(\"m in C\")\n",
" super().m() \n",
"\n",
"class D(C):\n",
" def m(self):\n",
" # Auf diesen Unterschied achten: \n",
" print(\"f in D, calling super\") \n",
" super().m()\n",
"\n",
"class A(B, D): \n",
" def m(self):\n",
" print(\"m in A, calling super\")\n",
" super().m()\n",
"\n",
"# Ausgabe hier? \n",
"print(A.__mro__)\n",
"a = A()\n",
"a.m()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# `super` und MRO: Beispiel `__init__`\n",
"\n",
"Wie oft werden die Konstruktoren hier ausgeführt?\n",
"\n",
"- Warum?\n",
"- Ist das so gewollt?"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class O: \n",
" def __init__(self):\n",
" print(\"O\")\n",
" super().__init__()\n",
"\n",
"class B(O):\n",
" def __init__(self):\n",
" print(\"B\")\n",
" super().__init__()\n",
"\n",
"class C(O):\n",
" def __init__(self):\n",
" print(\"C\")\n",
" super().__init__()\n",
"\n",
"class A(B,C):\n",
" def __init__(self):\n",
" print(\"C\")\n",
" super().__init__()\n",
"\n",
"a = A()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# `super`: Terminologie\n",
"\n",
"- Angesichts dieser Beispiele (und Definition) ist der Name `super` unglücklich\n",
" - Es wird eben nicht zwangsläufig eine Oberklasse aufgerufen; es\n",
" kann zur Ausführung von Geschwisterklassen kommen\n",
"- Besser wäre: `next_method()` oder ähnlich\n",
" - Auch, um Unterschied zu anderen OO-Sprachen deutlich zu machen,\n",
" die in der Tat nur ein *primitives* super besitzen\n",
" - Und deswegen den Diamond of Death nicht vernünftig behandeln\n",
" können!\n",
"- Siehe auch [super considered super](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Cooperative multiple inheritance\n",
"\n",
"MRO und die konsistente Nutzung von `super` in einer Klassenhierarchie\n",
"führen zu Python-typischem Programmiermodell: \n",
"\n",
"- Explizite Referenzen auf Namen von Oberklassen sind nicht sinnvoll;\n",
" `super` nutzen\n",
"- Abgeleitete Klassen rufen (in aller Regel) in überschriebenen\n",
" Methoden die überschriebene Methode mit `super()` auf\n",
" - Bis auf eine weit oben stehende Basisklasse, in der die\n",
" `super`-Folge terminiert\n",
"- Argumente müssen übereinstimmen\n",
" - Typisches Beispiel: Konstruktoren\n",
" - Nutze `*args` und `**kwargs`!\n",
"\n",
"Insgesamt: Klassen sind auf den Aufruf durch `super` vorbereitet und\n",
"geben ihn entsprechend weiter \n",
"\n",
"- Sie kooperieren miteinander: *cooperative multiple inheritance*\n",
"- (We are all adults…)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Dependency injection\n",
"\n",
"- Cooperative multiple inheritance erlaubt *cool stuff*\n",
"- Beispiel: dependency injection\n",
" - Wir ändern das Aufrufverhalten durch Ableiten!"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Dependency injection: Pizzeria-Beispiel\n",
"\n",
"- Siehe: [Talk @ PyCon 2015, Youtube](https://www.youtube.com/watch?v=EiOglTERPEo)"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class Teigfabrik:\n",
" def get_teig(self):\n",
" print(\"Teig aus genmanipuliertem Weizen\") \n",
"\n",
"class Pizzeria(Teigfabrik):\n",
" def order_pizza(self):\n",
" teig = super().get_teig()\n",
"\n",
"Pizzeria().order_pizza()"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Dependency injection: Pizzeria-Beispiel (2)"
]
},{
"cell_type": "code",
"metadata": {
"slideshow": {
"slide_type": "-"
}
},
"outputs": [],
"execution_count": null,
"source": ["class Teigfabrik:\n",
" def get_teig(self):\n",
" print(\"Teig aus genmanipuliertem Weizen\") \n",
"\n",
"class Pizzeria(Teigfabrik):\n",
" def order_pizza(self):\n",
" teig = super().get_teig()\n",
"\n",
"class BioTeigfabrik(Teigfabrik):\n",
" def get_teig(self):\n",
" print(\"Biologischer Weizen führt zu Teig\")\n",
"\n",
"class BioPizzeria(Pizzeria, BioTeigfabrik): pass\n",
"\n",
"BioPizzeria().order_pizza()\n",
"print(BioPizzeria.__mro__)"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Dependency injection: Muster\n",
"\n",
"- Pizzeria-Beispiel zeigt typisches Muster:\n",
" - Durch geschicktes Ableiten von zwei Klassen wurde Funktionalität\n",
" vereinigt\n",
" - Und Funktionalität einer Oberklasse ersetzt!\n",
" - Leere Unterklasse: typisch!\n",
"- Kommt häufig in größeren Python-Programmen vor\n",
"- Mächtig, flexibel, deterministisch\n",
" - MRO wurde für solche Zwecke gebaut!\n",
" - Aber auch mit Vorsicht zu benutzen!"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Einfachere Variante: Mixins\n",
"\n",
"- Gleiche Grundidee, aber etwas einfacher: Mixin\n",
"- Mixin: Prima für Reuse fertigen Codes, nicht zur Spezialisierung\n",
"- Vorgehen:\n",
" - Mixin-Klasse stellt Methoden zur Verfügung\n",
" - Klasse erbt (u.a.) von einer Mixin-Klasse\n",
" - … und nutzt die Methoden des Mixins,\n",
" - verändert diese aber typischerweise nicht"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Mixin: Muster\n",
"\n",
"- Typische Muster für Mixins:\n",
" - Basisklasse und Mixin-Klasse(n) zusammen entworfen\n",
" - Basisklasse implementiert Methoden der Mixins als leere Methoden\n",
" - Eigentlich Klasse: erbe von Basisklasse und gewünschtem Mixin\n",
"- Beispiel\n",
" - Webframeworks ([Django](https://docs.djangoproject.com/en/1.10/topics/class-based-views/mixins/)):\n",
" - Code zum Anzeigen eines einzelnen Objekts oder\n",
" Liste von Objekten als Mixin\n",
" - Eigene View-Klasse erbt u.a. das gewünschte Mixin"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["
Overview
\n",
"\n",
"1. Überblick\n",
"1. Multiple inheritance\n",
"1. Method Resolution Order\n",
"1. Linearisierungsalgorithmus: C3\n",
"1. MRO, dependency injection und super\n",
"1. **Zusammenfassung**"
]
},{
"cell_type": "markdown",
"metadata": {
"slideshow": {
"slide_type": "slide"
}
},
"source": ["# Zusammenfassung\n",
"\n",
"- Mehrfachvererbung (multiple inheritance) ist ein mächtiger, nahezu\n",
" unverzichtbarer Bestandteil von OO\n",
"- Es gibt leicht zu beherrschende Fälle: Oberklassen sind disjunkt\n",
"- Es gibt Fälle, die Überlegung brauchen: Oberklassen definieren die\n",
" gleichen Methoden\n",
" - *Diamond of Death*\n",
" - Mit geeignetem Linearisierungsalgorithmus für MRO aber problemlos\n",
" zu lösen!\n",
"- Python-spezifische Aspekte (Funktion von `super`) erlauben\n",
" zusätzliche mächtige Ansätze wie dependency injection\n",
"\n",
" "
]
}
],
"metadata": {
"celltoolbar": "Slideshow",
"livereveal": {
"theme": "simple",
"transition": "none"
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.5.1"
}
},
"nbformat": 4,
"nbformat_minor": 0
}