Wie Python Daten und Verhalten organisiert
Python unterstützt mehrere Arten, ein Programm aufzubauen. Prozeduraler Code folgt einer Reihe von Schritten. Funktionaler Code verarbeitet Daten mit Funktionen. Objektorientierter Code bündelt zusammengehörige Daten und Verhalten. In Python lassen sich diese drei Stile kombinieren.
Während ein Programm läuft, ist jeder Python-Wert ein Objekt. Zahlen, Funktionen, Klassen und Module haben jeweils einen Typ, eine eindeutige Identität und einen gespeicherten Wert.
Der Typ eines Objekts bestimmt seine Attribute und Methoden. Da diese Regel für alle Objekte gilt, kann eine Funktion in einer Liste stehen, eine Klasse an eine andere Funktion übergeben werden und eine Zahl Methoden haben. Pythons Objektmodell funktioniert mit prozeduralen, funktionalen und objektorientierten Programmstrukturen.
Arten des Programmaufbaus im Vergleich
Das gleiche Objektmodell trägt mehrere Arten, ein Programm aufzubauen. Ein prozeduraler Stil ordnet Schritte und einen ausdrücklichen Datenfluss, ein funktionaler Stil setzt eine Berechnung aus geschachtelten Funktionsaufrufen zusammen, und ein objektorientierter Stil legt Daten zusammen mit den Operationen ab, die sie verarbeiten. Wer diese drei Stile unterscheiden kann, erkennt im fremden Code schneller, wohin der Ablauf führt.
Prozeduraler Stil
In einem prozeduralen Stil wird ein Programm um Operationen und einen expliziten Datenfluss organisiert. Datenstrukturen und die Funktionen, die sie erhalten, können getrennt bleiben, auch wenn die Sprache zusätzlich Klassen unterstützt.
#include <stdio.h>
// Beispiel für einen prozeduralen Ansatz in C
struct ParkSystem {
int max;
int occ;
};
int occupy(struct ParkSystem* ps) {
if (ps->max <= ps->occ) {
return -1;
}
ps->occ++;
return 0;
}
void leave(struct ParkSystem* ps) {
if (ps->occ > 0) {
ps->occ--;
}
}
int main() {
struct ParkSystem ps = {100, 0};
occupy(&ps);
printf("%d %d", ps.max, ps.occ);
return 0;
}Hier speichert das C-struct den Parkzustand und jede Funktion erhält einen Zeiger darauf. Eine prozedurale Funktion kann eine Eingabe lesen und unverändert lassen.
Objektorientierte Sprachen
In einem objektorientierten Stil kann eine Klasse zusammengehörigen Zustand und Verhalten hinter einer Schnittstelle bündeln. Attribute beschreiben den Objektzustand, Methoden die Operationen, die ihn verwenden oder verändern.
# Beispiel für einen objektorientierten Ansatz in Python
class ParkSystem:
def __init__(self, max_capacity):
self.max = max_capacity
self.occ = 0
def occupy(self):
if self.max <= self.occ:
return -1
self.occ += 1
return 0
def leave(self):
if self.occ > 0:
self.occ -= 1
# Verwendung
ps = ParkSystem(100)
ps.occupy()
print(f"Max: {ps.max}, Occupied: {ps.occ}")Die Python-Version speichert die Regeln und Operationen des Parksystems in einer Klasse. Die Methode occupy prüft beim Aufruf die Kapazität. Externer Code kann die öffentlichen Attribute max und occ jedoch direkt ändern. Dieses kurze Beispiel schützt den Zustand des Objekts daher noch nicht vollständig.
Funktionaler Stil
Eine reine Funktion berechnet ein Ergebnis aus Eingaben ohne Zustandsänderung. Hier erhält die Funktion Kapazität und Fahrzeugzahl als Argumente und liefert die freien Plätze zurück.
def available_spaces(capacity, occupied):
return capacity - occupied
print(available_spaces(100, 1))
# Ausgabe: 99Parksystem am Beispiel
In diesem Beispiel ist das Parksystem ein Objekt mit Zustand und Verhalten. Sein Zustand speichert die maximale Kapazität und die Zahl der belegten Stellplätze. Seine Methoden erfassen einfahrende und ausfahrende Autos.
class ParkingSystem:
def __init__(self, capacity):
"""Parksystem mit bestimmter Kapazität initialisieren"""
if type(capacity) is not int or capacity <= 0:
raise ValueError("Die Kapazität muss eine positive ganze Zahl sein")
self.capacity = capacity
self.vehicles = [] # Liste der geparkten Fahrzeuge
def park_vehicle(self, vehicle_plate):
"""Methode zum Parken eines Fahrzeugs"""
if vehicle_plate in self.vehicles:
return f"Fahrzeug {vehicle_plate} ist bereits geparkt"
if len(self.vehicles) >= self.capacity:
return f"Parkplatz voll! {vehicle_plate} kann nicht geparkt werden"
self.vehicles.append(vehicle_plate)
return f"Fahrzeug {vehicle_plate} erfolgreich geparkt"
def exit_vehicle(self, vehicle_plate):
"""Methode zum Verlassen des Parkplatzes"""
if vehicle_plate in self.vehicles:
self.vehicles.remove(vehicle_plate)
return f"Fahrzeug {vehicle_plate} hat den Parkplatz verlassen"
else:
return f"Fahrzeug {vehicle_plate} nicht gefunden"
def get_status(self):
"""Methode zum Anzeigen des Parkstatus"""
occupied = len(self.vehicles)
available = self.capacity - occupied
return {
'capacity': self.capacity,
'occupied': occupied,
'available': available,
'vehicles': tuple(self.vehicles)
}
def is_full(self):
"""Methode zur Überprüfung, ob der Parkplatz voll ist"""
return len(self.vehicles) >= self.capacity
# Beispielverwendung
parking = ParkingSystem(5)
print(parking.park_vehicle("B 1234 CD"))
print(parking.park_vehicle("B 5678 EF"))
print(parking.get_status())
print(parking.is_full())Objektkonzepte in Datentypen
„Alles ist ein Objekt“ bedeutet, dass Python Laufzeitwerte über ein gemeinsames Objektmodell behandelt. Welche Attribute und Methoden verfügbar sind, hängt weiterhin vom jeweiligen Typ ab.
Zahlen sind Objekte
Eine ganze Zahl ist ein Objekt mit Methoden. Das Beispiel ruft die Methode bit_length() der Zahl 42 auf und gibt das Ergebnis aus.
# Zahlen in Python sind Objekte
number = 42
# Zahlen haben Methoden
print(number.bit_length()) # Methode zur Berechnung der Bitlänge
print(number.__add__(8)) # Additionsmethode (wie Zahl + 8)
# Float-Zahlen sind ebenfalls Objekte
price = 15.75
print(price.is_integer()) # Methode zur Überprüfung, ob es sich um eine ganze Zahl handelt
print(price.as_integer_ratio()) # Methode zur Umrechnung in ein Verhältnis
# Sogar die Ergebnisse mathematischer Operationen sind Objekte
result = 10 + 5
print(type(result)) # <class 'int'>
print(dir(result)) # Attributnamen erkundenStrings sind Objekte
Ein String ist ein Objekt mit Methoden, die neue Werte erzeugen. Das Beispiel ruft mehrere String-Methoden auf einem Text auf.
# Zeichenfolgen in Python sind sehr leistungsstarke Objekte
message = "Python zu lernen macht Spaß"
# Strings haben viele nützliche Methoden
print(message.upper()) # Ausgabe: PYTHON ZU LERNEN MACHT SPASS
print(message.lower()) # Ausgabe: python zu lernen macht spaß
print(message.title()) # Ausgabe: Python Zu Lernen Macht Spaß
print(message.count('n')) # Zähle den Buchstaben „n“
print(message.replace('Python', 'Programming')) # Wort ersetzen
# Methoden zur Überprüfung
email = "user@example.com"
print(email.endswith('.com')) # True
print(email.startswith('user')) # True
print(email.find('@')) # Position des „@“-Zeichens
# Methoden zur Formatierung
name = "alice"
age = 25
formatted = "Name: {}, Alter: {}".format(name.title(), age)
print(formatted)
# String teilen und verbinden
words = message.split() # In Wortliste aufteilen
print(words)
rejoined = " ".join(words) # Kommt wieder zusammen
print(rejoined)Listen sind Objekte
Eine Liste bietet Methoden zur Änderung ihres Inhalts wie append() sowie lesende Methoden wie count(). Das Beispiel beginnt mit einer Obstliste und ruft die Listenmethoden auf.
# Listen in Python sind dynamische Objekte
fruits = ["Apfel", "Orange", "Mango"]
# Listen verfügen über Methoden zur Datenmanipulation
fruits.append("Banane") # Am Ende hinzufügen
print(fruits)
fruits.insert(1, "Erdbeere") # An einer bestimmten Position hinzufügen
print(fruits)
fruits.remove("Orange") # Bestimmtes Element entfernen
print(fruits)
# Methoden zum Suchen und Sortieren
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
print(numbers.count(1)) # Vorkommen zählen
print(numbers.index(4)) # Position finden
numbers.sort() # Sortieren
print(numbers)
numbers.reverse() # Umgekehrte Reihenfolge
print(numbers)
# Listenkomprehension - die idiomatische Python-Variante
squares = [x**2 for x in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
# Methoden für fortgeschrittene Operationen
original = [1, 2, 3]
copy_list = original.copy() # Kopie erstellen
original.extend([4, 5]) # Mehrere Elemente hinzufügen
print(f"Original: {original}")
print(f"Copy: {copy_list}")Wörterbücher sind Objekte
Ein Wörterbuch ist ein Objekt mit Methoden zum Lesen und Ändern seiner Paare. Das Beispiel verwendet einen Studierendendatensatz für diese Methoden.
# Wörterbücher in Python sind flexible Objekte
student = {
"name": "Alice",
"age": 22,
"major": "Computer Science",
"gpa": 3.8
}
# Wörterbücher verfügen über Manipulationsmethoden
print(student.get("name")) # Wert sicher abrufen
print(student.get("height", "Unbekannt")) # Standardwert, wenn der Schlüssel nicht vorhanden ist
# Methoden zum Anzeigen von Wörterbuchinhalten
print(student.keys()) # Alle Schlüssel
print(student.values()) # Alle Werte
print(student.items()) # Schlüssel-Wert-Paare
# Methoden zur Aktualisierung und Manipulation
student.update({"semester": 6, "age": 23}) # Update multiple values
print(student)
# Pop-Methode zum Abrufen und Entfernen
gpa = student.pop("gpa")
print(f"GPA entfernt: {gpa}")
print(student)
# Dictionary comprehension
grades = {"Math": 85, "Physics": 92, "Chemistry": 78}
passed = {subject: grade for subject, grade in grades.items() if grade >= 80}
print(passed)
# Nested dictionary
university = {
"students": {
"CS": ["Alice", "Bob"],
"Math": ["Charlie", "Diana"]
},
"location": "Jakarta"
}
print(university["students"]["CS"])Funktionen sind auch Objekte
Du kannst Funktionen wie andere Daten weitergeben, speichern und verwenden.
# Funktionen in Python sind erstklassige Objekte
def greet(name):
return f"Hello, {name}!"
def farewell(name):
return f"Goodbye, {name}!"
# Funktionen haben Attribute
print(greet.__name__) # Funktionsname
print(type(greet)) # <class 'function'>
# Funktionen können in Variablen gespeichert werden
my_function = greet
print(my_function("Alice")) # Hello, Alice!
# Funktionen können in Listen gespeichert werden
functions = [greet, farewell]
for func in functions:
print(func("Bob"))
# Als Parameter können Funktionen verwendet werden
def call_function(func, name):
return func(name)
result = call_function(greet, "Charlie")
print(result)
# Funktionen können von anderen Funktionen zurückgegeben werden
def get_greeting_function(language):
def english_greet(name):
return f"Hello, {name}!"
def indonesian_greet(name):
return f"Halo, {name}!"
if language == "english":
return english_greet
if language == "indonesian":
return indonesian_greet
raise ValueError(f"Nicht unterstützte Sprache: {language}")
# Verwendung der zurückgegebenen Funktion
greet_func = get_greeting_function("indonesian")
print(greet_func("Diana")) # Halo, Diana!
# Lambda-Funktionen sind ebenfalls Objekte
square = lambda x: x ** 2
print(type(square)) # <class 'function'>
print(square(5)) # 25Klassen und Instanzen als Objekte
Eine Klasse definiert, wie ihre Instanzen entstehen und wie gemeinsames Verhalten aufgelöst wird. Die Klasse selbst ist ebenfalls ein Objekt, während jede Instanz ihren eigenen Instanzzustand trägt.
# Klasse als Blaupause erstellen
class Student:
# Class attribute (shared by all instances)
university = "Universitas Indonesia"
def __init__(self, name, major, semester):
# Instanzattribute (für jede Instanz individuell)
self.name = name
self.major = major
self.semester = semester
self.courses = []
def add_course(self, course):
"""Methode zum Hinzufügen eines Kurses"""
self.courses.append(course)
return f"{self.name} nimmt Kurs {course}"
def get_info(self):
"""Methode, um Studenteninformationen zu erhalten"""
return {
'name': self.name,
'major': self.major,
'semester': self.semester,
'courses': tuple(self.courses),
'university': self.university
}
def __str__(self):
"""Spezielle Methode zur String-Darstellung"""
return f"Student({self.name}, {self.major})"
def __len__(self):
"""Spezielle Methode, um die Anzahl der Kurse zu ermitteln"""
return len(self.courses)
# Instanzen (Objekte) aus einer Klasse erstellen
student1 = Student("Alice", "Computer Science", 4)
student2 = Student("Bob", "Mathematics", 6)
# Jede Instanz ist ein Objekt mit Attributen und Methoden
print(student1.add_course("Python Programming"))
print(student1.add_course("Data Structures"))
print(student2.add_course("Calculus"))
print(student2.add_course("Linear Algebra"))
# Methoden nutzen
print(student1.get_info())
print(student2.get_info())
# Spezielle Methoden (__str__ und __len__)
print(student1) # Student(Alice, Computer Science)
print(len(student1)) # 2 (Anzahl Kurse)
# Instanzen verfügen über zugängliche Attribute
print(f"Name: {student1.name}")
print(f"Major: {student1.major}")
print(f"Universität: {student1.university}")
# Klasse ist auch ein Objekt
print(type(Student)) # <class 'type'>
print(Student.__name__) # Student
print(Student.university) # Universitas IndonesiaSpezielle Methoden
Spezielle Methoden beginnen und enden mit doppelten Unterstrichen. Python ruft sie über Syntax und Built-ins wie +, abs(), repr() und Wahrheitstests auf. Implementiere eine solche Methode nur, wenn ihre Bedeutung für die Domäne natürlich ist.
from math import hypot
class Vector2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
"""Gut lesbare Darstellung für Menschen."""
return f"({self.x}, {self.y})"
def __repr__(self):
"""Eindeutige Darstellung zum Debuggen."""
return f"Vector2D(x={self.x!r}, y={self.y!r})"
def __abs__(self):
"""Den Betrag des Vektors zurückgeben."""
return hypot(self.x, self.y)
def __eq__(self, other):
"""Koordinaten mit einem anderen Vector2D vergleichen."""
if not isinstance(other, Vector2D):
return NotImplemented
return self.x == other.x and self.y == other.y
def __add__(self, other):
"""Einen neuen Vektor aus der koordinatenweisen Summe bilden."""
if not isinstance(other, Vector2D):
return NotImplemented
return Vector2D(self.x + other.x, self.y + other.y)
def __bool__(self):
"""Gibt für den Nullvektor False und für jeden anderen Vektor True zurück."""
return bool(self.x or self.y)
first = Vector2D(3, 4)
second = Vector2D(1, -2)
print(first) # (3, 4) über __str__
print(repr(first)) # Vector2D(x=3, y=4)
print(abs(first)) # 5.0 über __abs__
print(first == Vector2D(3, 4)) # True über __eq__
combined = first + second # __add__ liefert einen neuen Vektor
print(combined) # (4, 2)
print(first) # (3, 4), unverändert
zero = Vector2D(0, 0)
print(bool(first)) # True
print(bool(zero)) # FalseObjektorientierte Programmierpraxis
Das folgende Beispiel verbindet mehrere Klassen, um Bücher, Mitglieder und Ausleihen in einer Bibliothek abzubilden.
| Schritt | Erstes Buch | Alices Ausleihen |
|---|---|---|
| Vor der Ausleihe | Verfügbar, ohne Entleiher | Leer |
Nach borrow_book() | Von Alice ausgeliehen | Enthält das erste Buch |
Nach return_book() | Wieder verfügbar | Leer |
Buchtitel, Autoren und Kennungen sind Übungsdaten. Buch- und Mitgliederlisten bleiben öffentliche Attribute. Das Beispiel erklärt Objektbeziehungen, keine Mehrbenutzerspeicherung.
class Book:
def __init__(self, title, author, isbn):
self.title = title
self.author = author
self.isbn = isbn
self.is_borrowed = False
self.borrower = None
def __str__(self):
status = "Ausgeliehen" if self.is_borrowed else "Verfügbar"
return f"{self.title} von {self.author} - {status}"
class Member:
def __init__(self, name, member_id):
self.name = name
self.member_id = member_id
self.borrowed_books = []
def __str__(self):
return f"Mitglied: {self.name} (ID: {self.member_id})"
class Library:
def __init__(self, name):
self.name = name
self.books = []
self.members = []
def add_book(self, book):
"""Buch zur Bibliothek hinzufügen"""
self.books.append(book)
return f"Buch „{book.title}“ erfolgreich hinzugefügt"
def register_member(self, member):
"""Neues Mitglied registrieren"""
self.members.append(member)
return f"Mitglied {member.name} erfolgreich registriert"
def borrow_book(self, member_id, isbn):
"""Buch ausleihen"""
# Mitglied finden
member = None
for m in self.members:
if m.member_id == member_id:
member = m
break
if member is None:
return "Mitglied nicht gefunden"
# Buch finden
book = None
for b in self.books:
if b.isbn == isbn and not b.is_borrowed:
book = b
break
if book is None:
return "Buch nicht verfügbar"
# Ausleihe verarbeiten
book.is_borrowed = True
book.borrower = member
member.borrowed_books.append(book)
return f"{member.name} hat '{book.title}' erfolgreich ausgeliehen"
def return_book(self, member_id, isbn):
"""Buch zurückgeben"""
member = None
for m in self.members:
if m.member_id == member_id:
member = m
break
if member is None:
return "Mitglied nicht gefunden"
book = None
for b in member.borrowed_books:
if b.isbn == isbn:
book = b
break
if book is None:
return "Das Buch wurde nicht in der Ausleihliste gefunden"
# Rückgabe verarbeiten
book.is_borrowed = False
book.borrower = None
member.borrowed_books.remove(book)
return f"{member.name} hat erfolgreich „{book.title}“ zurückgegeben."
def get_available_books(self):
"""Liste der verfügbaren Bücher zurückgeben"""
available = [book for book in self.books if not book.is_borrowed]
return available
def get_member_books(self, member_id):
"""Von einem Mitglied ausgeliehene Bücher zurückgeben"""
for member in self.members:
if member.member_id == member_id:
return list(member.borrowed_books)
return []
# Beispielhafte Nutzung eines Bibliothekssystems
library = Library("Zentralbibliothek")
# Bücher hinzufügen
book1 = Book("Python Programming", "John Smith", "978-1234567890")
book2 = Book("Data Science Basics", "Jane Doe", "978-0987654321")
book3 = Book("Machine Learning", "Bob Johnson", "978-1122334455")
print(library.add_book(book1))
print(library.add_book(book2))
print(library.add_book(book3))
# Mitglieder registrieren
member1 = Member("Alice Cooper", "M001")
member2 = Member("Bob Wilson", "M002")
print(library.register_member(member1))
print(library.register_member(member2))
# Bücher ausleihen
print(library.borrow_book("M001", "978-1234567890"))
print(library.borrow_book("M002", "978-0987654321"))
# Buchstatus anzeigen
print("\nListe aller Bücher:")
for book in library.books:
print(book)
print("\nVerfügbare Bücher:")
for book in library.get_available_books():
print(book)
print(f"\nVon Alice ausgeliehene Bücher: {len(library.get_member_books('M001'))} Bücher")
# Rückgabe von Büchern
print(library.return_book("M001", "978-1234567890"))Pythons Objektmodell erklärt, warum Werte, Funktionen und Klassen dieselben Sprachmechanismen durchlaufen können. Nutze objektorientierte Komposition, wenn zusammengehöriger Zustand und Verhalten gemeinsam verwaltet werden müssen. Nutze ein prozedurales oder funktionales Design, wenn sich die Abfolge der Schritte oder der Datenfluss damit direkter erklären lässt.