Objekte in Python

Die objektorientierten Programmierung wurde in den 1960er Jahren entwickelt und in den letzten 40 Jahren immer populärer. Sie ist aus der Programmierung nicht mehr wegzudenken. Mit Objekten lässt sich der Quellcode von komplexen Projekten übersichtlich organisieren.
Ein Objekt ist alles, was Daten zusammenfassen kann. Du hast Objekte schon kennengelernt, denn jeder String ist ein Objekt, genauso wie Listen.

1Python Programmierkurs
2Python: Methoden
3Kontrollstrukturen
4Strings in Python
5Container
6Objekte in Python
7Module
8Exceptions in Python
9Typkonvertierung
10Python und Dateien
11Datum und Zeit mit Python verarbeiten
12Multithreading
13Netzwerk in Python
14Logging in Python
15GPIO
16Automatische Tests
17Datenbanken mit Python
18Python: Generatoren und List Comprehension
19Python: Webseiten mit Flask
20Python virtuelle Umgebungen
21Interrupts & Signale
22NumPy
23Matplotlib
24match
25Reguläre Ausdrücke Python Tutorial Teil 25

Objekte als Datenspeicher

Bisher haben wir alle Programme dieses Kurs in eine einzelne Datei geschrieben. Dieses Vorgehen macht aber, gerade bei großen Projekten mit vielen Methoden und Variablen den Quellcode schnell unlesbar. Ein Objekt fasst alle Variablen, die zu ihm gehören zusammen. Sie werden dann Attribute genannt.

Hier mal das klassische Beispiel eines Objekts für eine Person, speichere sie in der Datei person.py

class Person:
# Konstruktor
  def __init__(self,vorname, name):
    self.vorname = vorname
    self.name = name

Um ein Objekt erzeugen zu können, benötigen wir in Python eine Klasse, die das Objekt beschreibt.

Konstruktor

Der Konstruktor ist eine Spezialmethode einer Klasse, der bei der Erzeugung des Objekts aufgerufen wird. Hier wird die Objektinstanz initialisiert. In unserem Beispiel werden. Per Konvention heißt der Konstruktor in Python __init__ Die übergebenen Parameter vorname und name den Attributen zugewiesen. Der Parameter self istein besonderer Parameter von Python, mit dem das Objekt sich auf sich selbst bezieht, du musst ihn bei jeder Methode in der Klasse als erstes angeben, ansonsten erhälst du vom Interpreter diese Fehlermeldung:

TypeError: __init__() takes 2 positional arguments but 3 were given

Dieser Fehler hat mich bei meinen Anfängen mit Python echt geärgert, daher erwähne ich das hier mal.

Die Notation des Konstruktors mit __ (doppelten Unterstrichen) ist eine Konvention von Python, mit der interne Methoden und Variablen gekennzeichnet werden. Dies verhindert den Zugriff darauf nicht, es soll nur jedem Programmierer zeigen, dass er mit solchen Attributen und Methoden vorsichtig sein muss. Diese Notation wird auch Dunder genannt, verkürzt von „Double Underscore“.

Unsere Klasse ist jetzt definiert, nun wollen wir sie auch verwenden. Erstelle die Datei useperson.py im selben Verzeichnis wie person.py mit dem folgen Codeblock

from person import Person

p = Person('Olli','Graf')
print(p.vorname)

die erste Zeile importiert uns die Klasse Person aus der Datei person (das .py fügt Python implizit an).

Danach wird eine Instanz von Person mit den übergebenen Werten für name und vorname erzeugt und der Variable p zugewiesen. Du kannst jetzt so viele Instanzen von Person erzeugen, bis dir der Speicher ausgeht. Mehrere Instanzen solltest du dann in einem Dictionary verwalten.

Klassenmethoden

Klassen können nicht nur Attribute verwalten, sondern auch Methoden zu deren Manipulation mitbringen. Stell die vor, du bräuchtest eine Methode, die dir eine Instanz von Person formatiert für die Ausgabe zur Verfügung stellt. Dann ergänzt du Person wie folgt

class Person:
    # Konstruktor
  def __init__(self,vorname, name):
    self.vorname = vorname
    self.name = name

  def format(self):
      return self.vorname + ' ' + self.name
  def __str__(self):
     return self.format()

Die format() Methode gehört jetzt zur Klasse Person dazu und steht in jeder Instanz zur Verfügung. Daher kannst du jetzt auch useperson.py ändern:

from person import Person

p = Person('Olli','Graf')

print(p.format())

Der Aufruf von format() geschieht jetzt direkt am Objekt p. Stell dir vor, du möchtest das Format der Ausgabe ändern, z.B. sollen Vorname und Nachname umgekehrt mit einem „,“ getrennt erscheinen. Dann änderst das einmal in der format() Methode der Klasse, anstatt 1000-mal in 1000 einzelnen Objektinstanzen.

Ein Tipp an Rande. Statt einer format() Methode kannst du auch die interne Methode __str__() verwenden und dann das Objekt mit print(p) ausgeben.

zweite Klasse

Nur Namen und Vornamen zu speichern reicht i.d.R. für eine Person nicht aus, es gehört üblicherweise noch eine Anschrift dazu. Wir könnten jetzt für Straße, Hausnummer, Postleitzahl und Ort weitere Attribute in Person aufnehmen, aber objektorientiert ist die Anschrift ein weiteres Objekt, für das du eine weitere Klasse in einer neuen Datei anschrift.py deklarierst.

class Anschrift:
  def __init__(self, strasse, hausnummer, plz, ort):
    self.strasse = strasse
    self.hausnummer = hausnummer
    self.plz = plz
    self.ort = ort

  def __str__(self):
    return self.strasse + ' '  + self.hausnummer + '\n' + self.plz + ' ' + self.ort

Neu ist hier eigentlich nur, die Trennung von Hausnummer und PLZ in __str__ \n ist ein Sonderzeichen, mit dem ein Zeilenumbruch (Newline) erzeugt wird.

Die Klasse Person modifizierst du jetzt wie folgt, um Anschrift zu benutzen.

from anschrift import Anschrift

class Person:
    # Konstruktor
  def __init__(self,vorname, name,strasse,hausnummer, plz,ort):
    self.vorname = vorname
    self.name = name
    self.anschrift = Anschrift(strasse,hausnummer,plz,ort)


  def format(self):

    return self.vorname + ' ' + self.name
  def __str__(self):
    return self.format() + '\n' + str(self.anschrift)

Person bindet jetzt ein Objekt vom Typ Anschrift ein und legt strasse, hausnummer, plz und ort dort ab. Sowas wird in der objektorientierten Welt Assoziation genannt.

Vererbung

Objekte können ihre Attribute nicht nur durch Assoziation, sondern auch durch Vererbung zur Verfügung stellen. Nehmen wir mal folgendes Beispiel in der Datei vererbung.py

class Fahrzeug:
    def __init__(self, marke, modell, farbe):
        self.marke = marke
        self.modell = modell
        self.farbe = farbe

    def __str__(self):
        return f'{self.marke} {self.modell} {self.farbe})'

#Klasse Auto erbt von Fahrzeug
class Auto(Fahrzeug):
  def __init__(self, marke, modell, farbe, ps):
    super().__init__(marke, modell, farbe)
    self.ps = ps

  def __str__(self):
    return f'{super().__str__()} mit {self.ps} PS'

#Klasse Fahhrad erbt von Fahrzeug
class Fahrrad(Fahrzeug):
  def __init__(self, marke, modell, farbe):
    super().__init__(marke, modell, farbe)

  def __str__(self):
    return super().__str__()


bond = Auto('Aston Martin','DB5','grau','286')
mcfly = Auto('Delorean', 'DMC-12','grau','132')
eliot = Fahrrad('Kuwahara','ET-1','weiß')

Für Vererbung benötigst du immer mind. zwei Klassen. Eine Superklasse und eine davon abgeleitete Klasse. Die Superklasse ist hier Fahrzeug. Auto und Fahrrad leiten davon ab.

Die Klasse Fahrzeug enthält Attribute für marke, modell und farbe. Dies sind Attribute, die universell für alle abgeleiteten Klassen. Die beiden anderen Klassen übernehmen durch die Vererbung diese Attribute und erweitern diese ggf. durch ihren eigenen Attribute z.B. ps bei Auto. Deshalb sagt man auch, Auto ist eine Spezialisierung von Fahrzeug. Um auf Methoden und Attribute der Superklasse

zuzugreifen, wird die super() Funktion benutzt. Dadurch werden die allgemeinen Attribute dem Konstruktor der Superklasse übergeben.

Durch die Vererbung ist die Variable bond nicht nur vom Typ Auto, sondern auch vom Typ Fahrzeug.

Der Post ist jetzt sehr lang geworden. Es gibt noch viele Dinge über Objekte, Klassen und Objektorientierung zu sagen, dass verschiebe ich auf einen weiteren Beitrag zu dem Thema. Im nächsten Teil organisieren wir dann durch Module den Quellcode besser.

Schreibe einen Kommentar

Cookie Consent Banner von Real Cookie Banner