Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen angezeigt.

Link zu dieser Vergleichsansicht

Beide Seiten der vorigen Revision Vorhergehende Überarbeitung
Nächste Überarbeitung
Vorhergehende Überarbeitung
modul:m323:learningunits:lu02:byreferenceinclasses [2024/08/28 09:02] – kmaurizimodul:m323:learningunits:lu02:byreferenceinclasses [2026/08/25 09:14] (aktuell) – Korrektur mutable Defaults (Code warf ValueError) + Animation eingebunden admin
Zeile 4: Zeile 4:
  
 In Python werden Objekte, einschließlich Dataclasses, immer ''By Reference'' übergeben. Das bedeutet, dass bei der Übergabe eines Objekts an eine Funktion eine Referenz auf das Objekt übergeben wird und keine Kopie davon erstellt wird. Änderungen, die innerhalb der Funktion an den Attributen des Objekts vorgenommen werden, wirken sich direkt auf das ursprüngliche Objekt aus. In Python werden Objekte, einschließlich Dataclasses, immer ''By Reference'' übergeben. Das bedeutet, dass bei der Übergabe eines Objekts an eine Funktion eine Referenz auf das Objekt übergeben wird und keine Kopie davon erstellt wird. Änderungen, die innerhalb der Funktion an den Attributen des Objekts vorgenommen werden, wirken sich direkt auf das ursprüngliche Objekt aus.
 +
 +===== Animation: Objekte, frozen und Standardwerte =====
 +
 +Die Animation zeigt drei Abläufe Schritt für Schritt. Tab 1 und 2 gehören zu den Beispielen weiter unten; Tab 3 zeigt, was bei einem veränderbaren Standardwert passiert – und damit den Grund, weshalb Dataclasses solche Standardwerte gar nicht erst zulassen.
 +
 +{{url>https://templates-python.github.io/m323_html/lu02/objekte-und-standardwerte.html 100%,800px noborder|Animation: Objekte, Dataclasses und Standardwerte}}
  
 ===== Dataclasses ===== ===== Dataclasses =====
Zeile 13: Zeile 19:
 <code python> <code python>
 from dataclasses import dataclass from dataclasses import dataclass
 +
 @dataclass @dataclass
 class Person: class Person:
-name: str +    name: str 
-age: int+    age: int
 </code> </code>
  
-Diese Dataclass Person hat zwei Attribute: name und age.+Diese Dataclass ''Person'' hat zwei Attribute: ''name'' und ''age''.
  
 ===== By Reference bei Dataclasses ===== ===== By Reference bei Dataclasses =====
Zeile 29: Zeile 36:
 <code python> <code python>
 from dataclasses import dataclass from dataclasses import dataclass
 +
 @dataclass @dataclass
 class Person: class Person:
-name: str +    name: str 
-age: int+    age: int
  
 def birthday(person: Person): def birthday(person: Person):
-""" +    """ 
-Increments the age of the person by 1. +    Increments the age of the person by 1. 
-""" +    """ 
-person.age += 1+    person.age += 1
  
-if name == 'main': +if __name__ == '__main__': 
-p = Person(name='Alice', age=30) +    p = Person(name='Alice', age=30) 
-print(f'Vor der Änderung: {p}') # Output: Vor der Änderung: Person(name='Alice', age=30) +    print(f'Vor der Änderung: {p}')  # Output: Vor der Änderung: Person(name='Alice', age=30) 
-birthday(p) +    birthday(p) 
-print(f'Nach der Änderung: {p}') # Output: Nach der Änderung: Person(name='Alice', age=31)+    print(f'Nach der Änderung: {p}')  # Output: Nach der Änderung: Person(name='Alice', age=31)
 </code> </code>
  
-In diesem Beispiel wird die Instanz der Dataclass Person an die Funktion birthday übergeben. Da Objekte ''By Reference'' übergeben werden, wird das Attribut age der Originalinstanz p direkt in der Funktion verändert.+In diesem Beispiel wird die Instanz der Dataclass ''Person'' an die Funktion ''birthday()'' übergeben. Da Objekte ''By Reference'' übergeben werden, wird das Attribut ''age'' der Originalinstanz ''p'' direkt in der Funktion verändert.
  
 ===== Mutable und Immutable Attribute in Dataclasses ===== ===== Mutable und Immutable Attribute in Dataclasses =====
Zeile 53: Zeile 61:
 Python-Dataclasses können sowohl mutable als auch immutable Attribute enthalten. Unabhängig davon, ob die Attribute mutable oder immutable sind, wird die Dataclass selbst immer ''By Reference'' übergeben. Python-Dataclasses können sowohl mutable als auch immutable Attribute enthalten. Unabhängig davon, ob die Attribute mutable oder immutable sind, wird die Dataclass selbst immer ''By Reference'' übergeben.
  
-==== Beispiel: Mutable Attribute in Dataclasses ====+==== Mutable Standardwerte sind nicht erlaubt ==== 
 + 
 +Es liegt nahe, einem Listen-Attribut einfach eine leere Liste als Standardwert zu geben:
  
 <code python> <code python>
-from dataclasses import dataclass, field +from dataclasses import dataclass 
-from typing import List+
 @dataclass @dataclass
 class Student: class Student:
-name: str +    name: str 
-grades: List[int] = field(default_factory=list)+    grades: list = []   # ValueError! 
 +</code>
  
-def add_grade(student: Student, grade: int): +Dieser Code lässt sich **nicht ausführen**. Bereits beim Definieren der Klasse bricht Python ab:
-""" +
-Adds a grade to the student's grade list. +
-""" +
-student.grades.append(grade)+
  
-if name == 'main': +<code> 
-s = Student(name='Bob') +ValueError: mutable default <class 'list'> for field grades is not allowed: use default_factory
-print(f'Vor der Änderung: {s.grades}') # Output: Vor der Änderung: [] +
-add_grade(s, 90) +
-print(f'Nach der Änderung: {s.grades}') # Output: Nach der Änderung: [90]+
 </code> </code>
  
-In diesem Beispiel wird die Liste grades (ein mutable Attribut) innerhalb der Funktion add_grade verändert. Da das Objekt Student by reference übergeben wird, wird die Original-Liste grades modifiziert.+Der Grund: Ein Standardwert wird **einmalig** ausgewertet – nämlich dann, wenn die Klasse bzw. die Funktion definiert wird, und nicht bei jeder Instanzierung. Alle Instanzen würden sich also dieselbe Liste teilen. Weil das fast nie gewollt ist, verbieten Dataclasses solche Standardwerte von vornherein. 
 + 
 +<WRAP center round important 90%> 
 +**Bei normalen Funktionen greift dieser Schutz nicht!** Dort ist derselbe Fehler erlaubt – und still: 
 + 
 +<code python> 
 +def add_grade(grade, grades=[]): 
 +    grades.append(grade) 
 +    return grades 
 + 
 +print(add_grade(90))  # Output: [90] 
 +print(add_grade(80))  # Output: [90, 80]  <- und nicht [80] 
 +</code> 
 + 
 +Die Liste im Standardwert überlebt den Funktionsaufruf, weil sie nur einmal erzeugt wurde. Tab 3 der Animation oben spielt genau das durch. Die Lösung bei Funktionen: ''None'' als Standardwert verwenden und die Liste im Funktionsrumpf erzeugen. 
 +</WRAP> 
 + 
 +==== Lösung: Verwendung von `field(default_factory=list)` ==== 
 + 
 +Für mutable Standardwerte verwendet man in Dataclasses die Funktion ''field'' mit dem Parameter ''default_factory''. Diese Factory wird bei **jeder** Instanzierung neu aufgerufen – dadurch erhält jede Instanz ihre eigene Liste. 
 + 
 +<code python> 
 +from dataclasses import dataclass, field 
 + 
 +@dataclass 
 +class Student: 
 +    name: str 
 +    grades: list = field(default_factory=list) 
 + 
 +if __name__ == '__main__': 
 +    student1 = Student(name='Alice') 
 +    student2 = Student(name='Bob') 
 + 
 +    student1.grades.append(90) 
 +    print(student2.grades)  # Output: [] - Jede Instanz hat ihre eigene, unabhängige Liste 
 +</code>
  
 ===== Immutable Dataclasses ===== ===== Immutable Dataclasses =====
  
-Wenn Sie möchten, dass eine Dataclass unveränderlich ist, können Sie sie durch Setzen des frozen-Parameters in @dataclass unveränderlich machen. In einer gefrorenen (frozen) Dataclass sind alle Attribute unveränderlich, und jede versuchte Änderung führt zu einem Fehler. Dies ähnelt dem Verhalten von ''By Value'', da die ursprüngliche Instanz nicht verändert werden kann.+Wenn Sie möchten, dass eine Dataclass unveränderlich ist, können Sie sie durch Setzen des ''frozen''-Parameters in ''@dataclass'' unveränderlich machen. In einer gefrorenen (frozen) Dataclass sind alle Attribute unveränderlich, und jede versuchte Änderung führt zu einem Fehler. Dies ähnelt dem Verhalten von ''By Value'', da die ursprüngliche Instanz nicht verändert werden kann.
  
 ==== Beispiel: Immutable Dataclass ==== ==== Beispiel: Immutable Dataclass ====
Zeile 86: Zeile 125:
 <code python> <code python>
 from dataclasses import dataclass from dataclasses import dataclass
 +
 @dataclass(frozen=True) @dataclass(frozen=True)
 class ImmutablePerson: class ImmutablePerson:
-name: str +    name: str 
-age: int+    age: int
 </code> </code>
  
-In diesem Fall ist die Dataclass ImmutablePerson unveränderlich, was bedeutet, dass jedes Attribut, sobald es gesetzt ist, nicht mehr geändert werden kann. Jeder Versuch, das Attribut age oder name zu ändern, führt zu einem Fehler.+In diesem Fall ist die Dataclass ''ImmutablePerson'' unveränderlich, was bedeutet, dass jedes Attribut, sobald es gesetzt ist, nicht mehr geändert werden kann. Jeder Versuch, das Attribut ''age'' oder ''name'' zu ändern, führt zu einem ''FrozenInstanceError'': 
 + 
 +<code> 
 +FrozenInstanceError: cannot assign to field 'age' 
 +</code> 
 + 
 +==== Beispiel: Anwendung einer frozen Dataclass ==== 
 + 
 +<code python> 
 +from dataclasses import dataclass 
 + 
 +@dataclass(frozen=True) 
 +class Person: 
 +    name: str 
 +    age: int 
 + 
 +def birthday(person: Person) -> Person: 
 +    """ 
 +    Returns a new Person instance with the age incremented by 1. 
 +    """ 
 +    return Person(name=person.name, age=person.age + 1) 
 + 
 +if __name__ == '__main__': 
 +    p = Person(name='Alice', age=30) 
 +    new_p = birthday(p) 
 +    print(f'Original: {p}')  # Output: Original: Person(name='Alice', age=30) 
 +    print(f'Neue Instanz: {new_p}')  # Output: Neue Instanz: Person(name='Alice', age=31) 
 +</code> 
 + 
 +=== Erläuterung: === 
 + 
 +Die Dataclass ''Person'' ist als ''frozen'' markiert, was sie unveränderlich macht. 
 +Die Funktion ''birthday'' ist eine pure function. Sie nimmt eine Instanz der Dataclass ''Person'' als Eingabe und gibt eine neue Instanz zurück, wobei das ''age''-Attribut um eins erhöht wird. 
 +Das Originalobjekt ''p'' bleibt unverändert. Stattdessen wird eine neue Instanz ''new_p'' mit dem geänderten Alter erstellt. 
 + 
 +=== Vorteile: === 
 +  * **Keine Seiteneffekte:** Da das Originalobjekt nicht verändert wird, gibt es keine ungewollten Änderungen am Zustand. 
 +  * **Vorhersehbarkeit:** Pure functions sind leichter zu verstehen und zu testen, da das Ergebnis nur von den Eingabewerten abhängt. 
 +  * **Immutable Data:** Die Verwendung von immutable Datenstrukturen verhindert unbeabsichtigte Modifikationen und erleichtert die Parallelisierung von Programmen. 
 + 
 +Dieses Vorgehen ist besonders nützlich in Szenarien, in denen Datenintegrität und Vorhersagbarkeit des Programmverhaltens von hoher Bedeutung sind.
  
 ===== Zusammenfassung ===== ===== Zusammenfassung =====
  
-In Python werden Objekte, einschließlich Dataclasses, immer ''By Reference'' übergeben. Änderungen an den Attributen eines Objekts innerhalb einer Funktion wirken sich direkt auf das Originalobjekt aus. Um ungewollte Änderungen zu vermeiden, kann eine Dataclass als ''frozen'' deklariert werden, was sie unveränderlich macht und vor unbeabsichtigten Modifikationen schützt.+In Python werden Objekte, einschließlich Dataclasses, immer ''By Reference'' übergeben. Änderungen an den Attributen eines Objekts innerhalb einer Funktion wirken sich direkt auf das Originalobjekt aus. Um ungewollte Änderungen zu vermeiden, kann eine Dataclass als ''frozen'' deklariert werden, was sie unveränderlich macht und vor unbeabsichtigten Modifikationen schützt. Zusätzlich ist es wichtig, bei mutable Attributen wie Listen ''field(default_factory=list)'' zu verwenden – ein direkter mutable Standardwert wird von Dataclasses gar nicht erst akzeptiert.
  
 <WRAP center round tip 60%> <WRAP center round tip 60%>
-Tipp: Wenn Sie mutable Attribute in einer Dataclass verwenden, sollten Sie sorgfältig überlegen, ob die direkte Veränderung dieser Attribute innerhalb von Funktionen in Ihrem Programmdesign erwünscht ist. Verwenden Sie ''frozen'' Dataclasses, um unbeabsichtigte Änderungen zu verhindern.+Tipp: Wenn Sie mutable Attribute in einer Dataclass verwenden, sollten Sie sorgfältig überlegen, ob die direkte Veränderung dieser Attribute innerhalb von Funktionen in Ihrem Programmdesign erwünscht ist. Verwenden Sie ''frozen'' Dataclasses oder ''field(default_factory=list)'', um unbeabsichtigte Änderungen zu verhindern.
 </WRAP> </WRAP>
 +
 +----
 +{{tag>M323-LU02 M323-A1I M323-A1B}}
 +[[https://creativecommons.org/licenses/by-nc-sa/4.0/ch/|{{https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png}}]] (c) Kevin Maurizi 
 +
  • modul/m323/learningunits/lu02/byreferenceinclasses.1724828577.txt.gz
  • Zuletzt geändert: 2024/08/28 09:02
  • von kmaurizi