Wenn du bisher funktional, deklarativ oder imperativ programmiert hast, kann es dir etwas fremd vorkommen, den Fokus auf die objektorientierte Programmierung (OOP) zu verlagern.
Ein OOP-Ansatz verlangt, dass du ein Problem als ein oder mehrere objects modellierst, die miteinander interagieren, einen Zustand speichern und auf Daten einwirken.
Objekte können Entitäten aus der realen Welt darstellen (etwa Autos oder Katzen) oder abstraktere Konzepte (etwa Ganzzahlen, Fahrzeuge oder Säugetiere).
Jedes Objekt wird zu einer eindeutigen Instanz im Arbeitsspeicher des Computers und repräsentiert einen Teil des Gesamtmodells.
Classes sind die Definitionen neuer Objekttypen, aus denen neue instances von Objekten erzeugt werden.
Sie bündeln oft Daten mit Code oder Funktionen, die auf diesen Daten arbeiten.
In diesem Sinne sind Klassen Baupläne oder Sammlungen von Anweisungen, aus denen viele Objekte eines ähnlichen Typs gebaut und verwendet werden können.
Ein komplexes Programm kann viele Klassen haben, von denen jede viele verschiedene Varianten von Objekten erzeugt.
Der Prozess, ein Objekt aus einer Klasse zu bauen, heißt instantiation (oder: eine Instanz der Klasse erzeugen).
Eine Klassendefinition in Python ist unkompliziert:
class MyClass:
# Class body goes here
Klassenfelder (auch bekannt als properties, attributes, data members oder variables) kannst du dem Klassenkörper hinzufügen:
class MyClass:
number = 5
string = "Hello!"
Eine Instanz (ein Objekt) von MyClass kannst du erzeugen und an einen Namen binden, indem du die Klasse aufrufst (genauso, wie du eine Funktion aufrufst):
>>> new_object = MyClass()
# Class is instantiated and resulting object is bound to the "new_object" name.
# Note: the object address 'at 0x15adc55b0' will vary by computer.
>>> new_object
<__main__.MyClass at 0x15adc55b0>
Class attributes werden von allen Objekten (oder Instanzen) geteilt, die aus einer Klasse erzeugt werden. Du kannst über dot notation auf sie zugreifen. Dabei steht ein . nach dem Objektnamen und vor dem Attributnamen:
>>> new_object = MyClass()
# Accessing the class attribute "number" via dot-notation.
>>> new_object.number
5
# Accessing the class attribute "string" via dot-notation.
>>> new_object.string
'Hello!'
# Instantiating an additional object and binding it to the "second_new_object" name.
>>> second_new_object = MyClass()
>>> second_new_object
# Note: the object address "at 0x15ad99970" will vary by computer.
<__main__.MyClass at 0x15ad99970>
# Second_new_object shares the same class attributes as new_object.
>>> new_object.number == second_new_object.number
True
Klassenattribute werden im Klassenkörper selbst definiert, noch vor allen anderen Methoden. Sie gehören der Klasse, wodurch sie von allen Instanzen der Klasse geteilt werden können. Weil diese Attribute geteilt werden, kannst du ihren Wert direkt über die Klasse abrufen und ändern. Wenn du den Wert von Klassenattributen änderst, änderst du den Wert für alle Objekte, die aus der Klasse erzeugt wurden:
>>> obj_one = MyClass()
>>> obj_two = MyClass()
# Accessing a class attribute from an object.
>>> obj_two.number
5
# Accessing the class attribute from the class itself.
>>> MyClass.number
5
# Modifying the value of the "number" class attribute.
>>> MyClass.number = 27
# Modifying the "number" class attribute changes the "number" attribute for all objects.
>>> obj_one.number
27
>>> obj_two.number
27
Eine Menge Objekte zu haben, deren Daten ständig synchron sind, ist nicht besonders nützlich.
Zum Glück kannst du Objekte, die aus einer Klasse erzeugt werden, mit eigenen instance attributes (oder Instanzeigenschaften, -variablen oder -feldern) anpassen.
Wie der Name schon sagt, sind Instanzattribute für jedes Objekt einzigartig und können unabhängig voneinander geändert werden.
__init__() anpassenDie spezielle „Dunder-Methode" (kurz für „double underscore method") __init__() dient dazu, Klasseninstanzen anzupassen, und du kannst damit Instanzattribute oder -eigenschaften für Objekte initialisieren.
Wegen ihrer Rolle beim Initialisieren von Instanzattributen wird __init__() auch als class constructor oder initializer bezeichnet.
__init__() nimmt einen erforderlichen Parameter namens self, der auf das gerade initialisierte oder erzeugte Objekt verweist.
Daten für Instanzattribute oder -eigenschaften kannst du dann als Argumente von __init__() übergeben, und zwar nach dem self-Parameter.
Unten hat MyClass nun Instanzattribute namens location_x und location_y.
Wie du siehst, wurden die beiden Attribute dem ersten und zweiten Index des location-Arguments (eines Tupels) zugewiesen, das an __init__() übergeben wurde.
Die Attribute location_x und location_y einer Klasseninstanz werden nun initialisiert, wenn du die Klasse instanziierst und so ein Objekt erzeugst:
class MyClass:
# These are class attributes, variables, or fields.
number = 5
string = "Hello!"
# This is the class "constructor", with a "location" parameter that is a tuple.
def __init__(self, location):
# This is an instance or object property, attribute, or variable.
# Note that we are unpacking the tuple argument into two separate instance variables.
self.location_x = location[0]
self.location_y = location[1]
# Create a new object "new_object_one", with object property (1, 2).
>>> new_object_one = MyClass((1, 2))
# Create a second new object "new_object_two" with object property (-8, -9).
>>> new_object_two = MyClass((-8, -9))
# Note that new_object_one.location_x and new_object_two.location_x two different values.
>>> new_object_one.location_x
1
>>> new_object_two.location_x
-8
Beachte, dass du beim Initialisieren von MyClass oben nur ein Argument übergeben musst. Python kümmert sich darum, self zu übergeben, wenn die Klasse aufgerufen wird.
Eine method ist eine function, die entweder an die Klasse selbst gebunden ist (bekannt als Klassenmethode, dazu kommen wir in einer späteren Übung) oder an eine Instanz der Klasse (ein Objekt).
Methoden, die auf einem Objekt (einer Instanz) arbeiten, müssen mit self als erstem Parameter definiert werden.
Die übrigen Parameter kannst du dann genauso definieren wie bei einer „normalen" oder nicht gebundenen Funktion:
class MyClass:
number = 5
string = "Hello!"
# Class constructor.
def __init__(self, location):
# Instance properties
self.location_x = location[0]
self.location_y = location[1]
# Instance method. Note "self" as first parameter.
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
Wie beim Zugriff auf Attribute musst du zum Aufrufen einer Methode einfach einen . nach dem Objektnamen und vor dem Methodennamen setzen.
Die aufgerufene Methode braucht keine Kopie des Objekts als ersten Parameter. Python setzt self automatisch ein:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Make a new test_object with location (3,7)
>>> test_object = MyClass((3,7))
>>> (test_object.location_x, test_object.location_y)
(3,7)
# Call change_location to increase location_x and location_y by 7.
>>> test_object.change_location(7)
(10, 14)
Auf Klassenattribute kannst du aus Instanzmethoden genauso zugreifen wie von außerhalb der Klasse:
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances from within an instance.
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
>>> test_object_one = MyClass((0,0))
>>> test_object_one.number
5
>>> test_object_two = MyClass((13, -3))
>>> test_object_two.increment_number()
>>> test_object_one.number
6
pass als Platzhalter oder StubIn früheren Konzeptübungen und in den Stubs von Übungen hast du gesehen, dass das Schlüsselwort pass im Körper von Funktionen anstelle von echtem Code verwendet wird.
Das Schlüsselwort pass ist ein syntaktisch gültiger Platzhalter. Es verhindert, dass Python bei einer leeren Funktions- oder Klassendefinition einen Syntaxfehler auslöst.
Im Grunde ist es eine Art, dem Python-Interpreter zu sagen: „Keine Sorge! Ich werde hier irgendwann Code einfügen, ich habe es nur noch nicht getan."
class MyClass:
number = 5
string = "Hello!"
def __init__(self, location):
self.location_x = location[0]
self.location_y = location[1]
# Alter instance variable location_x and location_y
def change_location(self, amount):
self.location_x += amount
self.location_y += amount
return self.location_x, self.location_y
# Alter class variable number for all instances
def increment_number(self):
# Increment the 'number' class variable by 1.
MyClass.number += 1
# This will compile and run without error, but has no current functionality.
def pending_functionality(self):
# Stubbing or placholding the body of this method.
pass
Ellen entwickelt ein Spiel, in dem der Spieler gegen Aliens kämpfen muss.
Sie hat gerade etwas über objektorientierte Programmierung (OOP) gelernt und möchte unbedingt nutzen, was ihr der Einsatz von classes für ihr Programm bieten könnte.
Zu Ellens Freude hast du angeboten zu helfen, und sie hat dir die Aufgabe übertragen, die Aliens zu programmieren, gegen die der Spieler kämpfen muss.
Definiere die Klasse Alien mit einem Konstruktor, der zwei Parameter <x_coordinate> und <y_coordinate> entgegennimmt und sie in den Instanzvariablen x_coordinate und y_coordinate ablegt.
Jeder Alien startet außerdem mit 3 Gesundheitspunkten, also sollte auch die Variable health initialisiert werden.
>>> alien = Alien(2, 0)
>>> alien.x_coordinate
2
>>> alien.y_coordinate
0
>>> alien.health
3
Jetzt soll jeder Alien seine eigene Position und seine Gesundheit selbst verwalten können.
hit
Ellen möchte, dass die Alien-class eine Methode hit besitzt, die bei Aufruf die Gesundheit eines Alien-Objekts um 1 verringert.
So kann sie einfach <alien>.hit() aufrufen, statt die Gesundheit eines Aliens manuell ändern zu müssen.
Es bleibt dir überlassen, ob hit() die Gesundheitspunkte auf oder unter null bringt.
>>> alien = Alien(0, 0)
# Initialized health value.
>>> alien.health
3
# Decrements health by 1 point.
>>> alien.hit()
>>> alien.health
2
is_alive
Dir fällt auf, dass die Gesundheit irgendwann wahrscheinlich bei 0 landen wird, wenn sie immer weiter sinkt (oder sogar darunter!).
Es wäre eine gute Idee, eine Methode is_alive hinzuzufügen, die Ellen schnell aufrufen kann, um zu prüfen, ob der Alien ... nun ja ... noch lebt. 😉
<alien>.is_alive() sollte einen booleschen Wert zurückgeben.
>>> alien.health
1
>>> alien.is_alive()
True
>>> alien.hit()
>>> alien.health
0
>>> alien.is_alive()
False
teleport
In Ellens Spiel können sich die Aliens teleportieren!
Du musst eine Methode teleport schreiben, die neue Werte für x_coordinate und y_coordinate entgegennimmt und die Koordinaten des Aliens entsprechend ändert.
>>> alien.teleport(5, -4)
>>> alien.x_coordinate
5
>>> alien.y_coordinate
-4
collision_detection
Natürlich müssen die Aliens erkennen können, wenn eine solche Kollision stattgefunden hat, wenn sie von etwas getroffen werden können.
Allerdings können Algorithmen zur Kollisionserkennung knifflig sein, und du weißt noch nicht, wie man einen implementiert.
Ellen hat gesagt, dass sie das später erledigen wird, aber sie möchte trotzdem, dass die Methode collision_detection in der Klasse auftaucht, als Erinnerung daran, die Funktionalität noch auszubauen.
Sie muss irgendeine Variable entgegennehmen (wahrscheinlich ein anderes Objekt), aber mehr weißt du wirklich nicht.
Du musst sicherstellen, dass das Einfügen der Methodendefinition in die Klasse keine Fehler beim Aufruf verursacht:
>>> alien.collision_detection(other_object)
>>>
Ellen kommt mit einer neuen Bitte zu dir zurück. Sie möchte den Überblick behalten, wie viele Aliens im Laufe des Spiels erstellt wurden. Sie sagt, dass es etwas mit dem Punktesystem zu tun hat.
Zum Beispiel:
>>> alien_one = Alien(5, 1)
>>> alien_one.total_aliens_created
1
>>> alien_two = Alien(3, 0)
>>> alien_two.total_aliens_created
2
>>> alien_one.total_aliens_created
2
# Accessing the variable from the class directly
>>> Alien.total_aliens_created
2
Ellen ist begeistert von dem, was du bisher gemacht hast, aber sie hat noch eine Bitte.
Sie hätte gern eine eigenständige Funktion (außerhalb der Alien()-Klasse), die aus einer Liste von Positionen (als tuples) eine list von Alien()-Objekten erstellt.
Zum Beispiel:
>>> alien_start_positions = [(4, 7), (-1, 0)]
>>> aliens = new_aliens_collection(alien_start_positions)
...
>>> for alien in aliens:
... print(alien.x_coordinate, alien.y_coordinate)
(4, 7)
(-1, 0)
Melde dich bei Exercism an, um Python mit 17 Konzepte146 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.