zurück rauf weiter Englisch Index

Kapitel 12

Rohübersetzung -- bitte um Rückmeldungen über Fehler und Unklarheiten an glingl@aon.at

Klassen und Objekte

12.1 Benutzerdefinierte zusammengesetzte Datentypen

Nachdem wir einige von Pythons eingebauten Datentypen benützt haben, sind wir nun bereit einen benutzerdefinierten Datentyp zu erzeugen: den Punkt.

Betrachten wir zunächst den Begriff dess mathematischen Punktes. In zwei Dimensionen ist ein Punkt ein Paar von zwei Zahlen (Koordinaten) die zusammen als ein einziges Objekt behandelt werden. In der mathematischen Notation werden Punkte oft als Zahlenpaare in runden Klammern geschrieben, mit einem Komma zwischen den Koordinaten. Zum Beispiel stellt das Paar (0, 0) den Ursprung dar, und das Paar (x, y) stellt den Punkt mit x Einheiten nach rechts und y Einheiten nach oben, vom Ursprung aus gesehen, dar.

Eine natürliche Art einen Punkt in Python darzustellen ist mit zwei Gleitkommazahlen. Dabei erhebt sich die Frage, wie wir diese beiden zu einem zusammengesetzten Objekt zusammenfassen können. Eine rasche und einfallslose (quick and dirty) Lösung ist es, eine Liste oder ein Tupel zu verwenden, und für manche Anwendungen mag dies die beste Lösung sein.

Eine Alternative dazu ist, einen neuen benutzerdefinierten zusammengesetzten Datentyp zu definieren, eine sogenannte Klasse. Dieser Zugang erfordert etwas mehr Mühe, bringt aber Vorteile, die bald zu sehen sein werden.

Eine Klassendefinition sieht so aus:

class Punkt:
  pass

Klassendefinitionen können in einem Programm an beliebiger Stelle auftreten, sie werden aber üblicherweise an den Anfang gestellt (hinter die import Anweisungen). Die Syntax-Regeln für die Klassendefinition sind die selben wie für andere zusammengesetzte Anweisungen (siehe Abschnitt 4.4).

Diese Definition erzeugt eine neue Klasse, genannt Punkt. Die pass Anweisung hat keine Wirkung; sie ist nur notwendig, weil eine zusammengesetzte Anweisung irgendwas in ihrem Körper stehen haben muss.

Indem wir die Point Klasse erzeugt haben, haben wir auch einen neuen Typ, Point genannt, erzeugt. Die Dinge, die diesen Typ haben, nennt man Instanzen dieses Typs oder Objekte. Das Erzeugen einer neuen Instanz - also eines Objekts dieses Typs - nennt man Instanziierung. Um ein Punkt Objekt zu instanziieren, rufen wir eine Funktion namens (du hast es vielleicht schon erraten) Punkt auf:

blank = Punkt()

Der Variablen blank wird ein Verweis auf ein neues Punkt Objekt zugewiesen. Eine Funktion wie Punkt, die ein neues Objekt erzeugt, nennt man Konstruktor.

12.2 Attribute

Wir können zu einer Instanz neue Daten hinzufügen, indem wir die Punkt-Notation anwenden:

>>> blank.x = 3.0
>>> blank.y = 4.0

Diese Syntax ist ähnlich der, mit der man eine Variable aus einem Modul anspricht, wie z. B. math.pi oder string.uppercase. Im Gegensatz dazu wird hier aber ein Datenelement einer Instanz angesprochen. Solche benannten Datenelemente heißen Attribute.

Das folgende Zustandsdiagramm zeigt das Ergebnis dieser Zuweisungen:

Die Variable blank verweist auf ein Punkt Objekt, das zwei Attribute aufweist. Jedes Attribut verweist auf eine Gleitkommazahl.

Wir könne den Wert eines Attributes auch auslesen, indem wir die gleiche Syntax anwenden:

>>> print blank.y
4.0
>>> x = blank.x
>>> print x
3.0

Der Ausdruck blank.x bedeutet, "Gehe zum Objekt, auf das blank verweist und ermittle den Wert vvon x." In unserem Beispiel weisen wie diesen Wert einer Variablen namens x zu. Es gibt hier keinen Konflikt zwischen der Variablen x und dem Attribut x. Der Zweck der Punkt-Notation ist es, unzweideutig festzulegen, welche Variable gemeint ist.

Man kann die Punkt-Notation in jedem beliebigen Ausdruck verwenden. So sind beispielsweise auch die folgenden Anweisungen legal:

print '(' + str(blank.x) + ', ' + str(blank.y) + ')'
distanceSquared = blank.x * blank.x + blank.y * blank.y

Die erste Zeile gibt (3.0, 4.0) aus; die zweite Zeile berechnet den Wert 25.0.

Du wirst vielleicht versucht sein, den Wert von blank selbst auszudrucken:

>>> print blank
<__main__.Point instance at 80f8e70>

Das Ergebnis zeigt an, dass blank eine Instanz der Punkt Klasse ist und dass sie in __main__ definiert wurde. 80f8e70 ist die eindeutige Identitätsnummer dieses Objekts, geschrieben in Hexadezimaldarstellung. Das ist möglicherweise nicht die informativste Art, ein Punkt Objekt zu beschreiben. Wie man das ändern kann, wirst du aber in Kürze sehen.

Übung: Erzeuge ein Punkt Objekt, und gib es mit der print Anweisung aus. Dann benutze id um die eindeutige Identitätsnummer dieses Objekts auszugeben. Übersetze die Hexadezimalform in die Dezimalform und überzeuge dich, dass sie übereinstimmen.

12.3 Instanzen als Parameter

Man kann eine Instanz auf die übliche Weise als Parameter übergeben. Zum Beispiel:

def printPoint(p):
  print '(' + str(p.x) + ', ' + str(p.y) + ')'

printPoint übernimmt einen Punkt als Argument und stellt ihn im Standardformat dar. Wenn man printPoint(blank) aufruft, ist die Ausgabe (3.0, 4.0).

Übung: Schreibe die distance Funktion aus dem Abschnitt 5.2 um, so dass sie zwei Points als Parameter übernimmt anstatt vier Zahlen.

12.4 Gleichheit

Die Bedeutung des Wortes "gleich" scheint absolut klar zu sein, aber nur so lang, bis man beginnt ein wenig darüber nachzudenken. Dann bemerkt man, dass mehr dahinter steckt, als man erwartet hatte.

Wenn du zum Beispiel sagst "Chris und ich haben das gleiche Auto", meinst du dass seines und deines Autos der gleichen Marke sind und auch das gleiche Modell, dass sie aber zwei verschiedene Autos sind. Sagst du hingegen "Chris und ich haben die gleiche Mutter", meinst du, dass seine Mutter und deine die gleiche Person sind. * Note. Also ist die Bedeutung von "Gleichheit" verschieden, je nach Kontext.

Wenn man über Objekte spricht, gibt es eine ähnliche Zweideutigkeit. Zum Beispiel, wenn zwei Points gleich sind, heisst das dass sie dieselben Daten (Koordinaten) haben? Oder sind sie in der Tat das gleiche Objekt?

Um heraus zu finden, ob zwei Verweise auf dasselbe Objekt verweisen, benütze den == Operator. Zum Beispiel:

>>>   p1 = Point()
>>>   p1.x = 3
>>>   p1.y = 4
>>>   p2 = Point()
>>>   p2.x = 3
>>>   p2.y = 4
>>>   p1 == p2
0

Obwohl p1 und p2 die gleichen Koordinaten haben, sind sie nicht das gleiche Objekt. Wenn wir dagegen p1 zu p2 zuweisen, dann sind die beiden Variablen Aliasnamen des gleichen Objekts:

>>>   p2 = p1
>>>   p1 == p2
1

Dieser Typ von Gleichheit wird flache Gleichheit (shallow equality) genannt, weil hier nur die Verweise, nicht die Inhalte der Objekte verglichen werden.

Um den Inhalt der beiden Objekte zu vergleichen     tiefe Gleichheit (deep equality)     können wir eine Funktion mit dem Namen samePoint schreiben:

def samePoint(p1, p2) :
  return (p1.x == p2.x) and (p1.y == p2.y)

Wenn wir nun zwei verschiedene Objekte erzeugen, die die gleichen Daten enthalten, können wir samePoint benützen, um heraus zu finden, ob sie den gleichen Punkt darstellen:

>>> p1 = Point()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = Point()
>>> p2.x = 3
>>> p2.y = 4
>>> samePoint(p1, p2)
1

Wenn die beiden Variablen auf dasselbe Objekt verweisen, gelten für sie sowohl flache wie auch tiefe Gleichheit.

12.5 Rechtecke

Angenommen wir wollen eine Klasse programmieren um Rechtecke darzustellen. Die Frage ist nun, welche Informationen müssen wir bereitstellen, um ein Rechteck festzulegen? Um die Sache einfach zu halten, nehmen wir an, dass die Rechteckseiten alle vertikal oder horizontal orientiert sind, nicht unter einem Winkel (zu den Koordinatenachsen).

Es gibt hier ein paar Möglichkeiten: wir könnten den Mittelpunkt des Rechtecks (zwei Koordinaten) und seine Größe (Breite und Höhe) festlegen; oder wir könnten eine Ecke und die Größe festlegen; oder wir könnten zwei gegenüberliegende Ecken festlegen. Ein häufige Wahl ist die Festlegung der linken oberen Ecke und der Größe des Rechtecks.

Wir definieren also wieder eine neue Klasse:

class Rectangle:
  pass

Und instanziieren sie:

box = Rectangle()
box.width = 100.0
box.height = 200.0

Dieser Code erzeugt ein neues Rectangle Objekt mit zwei Gleitkommazahlen als Attributen. Um die obere linke Ecke festzulegen, können wir ein Objekt in ein anderes Objekt einbetten!

box.corner = Point()
box.corner.x = 0.0;
box.corner.y = 0.0;

Der Punkt-Operator verbindet. Der Ausdruck box.corner.x bedeutet, "Gehe zu dem Objekt, auf das box verweist und wähle das corner genannte Attribut; dann gehe zu diesem Objekt und wähle das x genannte Attribut."

Das folgende Diagramm zeigt den Zustand dieses Objekts:

12.6 Instanzen und Rückgabewerte

Funktionen können Instanzen zurück geben. Zum Beispiel übernimmt findCenter ein Rectangle als Argument und gibt einen Point, der die Koordinaten des Mittelpunkts des Rectangle hat, zurück:

def findCenter(box):
  p = Point()
  p.x = box.corner.x + box.width/2.0
  p.y = box.corner.y + box.height/2.0
  return p

Wir rufen diese Funktion auf, wobei wir box als ein Argument übergeben und das Ergebnis einer Variablen zuweisen:

>>> center = findCenter(box)
>>> printPoint(center)
(50.0, 100.0)

12.7 Objekte sind veränderbar

Wir können den Zustand eines Objekts ändern, indem wir einem seiner Attribute etwas zuweisen. Um beispielsweise die Größe eines Rechtecks zu ändern ohne seine Position zu verändern, können wir die Werte von width und height ändern:

box.width = box.width + 50
box.height = box.height + 100

Wir können diesen Code in einer Funktion kapseln und so verallgemeinern, um die Größe des Rechtecks um beliebig Werte abzuändern.

def growRect(box, dwidth, dheight) :
  box.width = box.width + dwidth
  box.height = box.height + dheight

Die Variablen dwidth und dheight geben an, wie viel die Rechteckgröße in jeder Richtung geändert werden soll. Ein Aufruf dieser Funktion (Methode) hat die Auswirkung, dass das Rechteck Objekt Rectangle geändert wird, das als Argument übergeben wurde.

Als Beispiel können wir ein neues Rectangle genannt bob erzeugen und an growRect übergeben:

>>> bob = Rectangle()
>>> bob.width = 100.0
>>> bob.height = 200.0
>>> bob.corner = Point()
>>> bob.corner.x = 0.0;
>>> bob.corner.y = 0.0;
>>> growRect(bob, 50, 100)

Während der Ausführung von growRect ist der Parameter box ein Aliasname für bob. Jede Veränderung von box beeinflusst auch bob.

Übung: Schreibe eine Funktion namens moveRect, die ein Rechteck Rectangle als Argument übernimmt und noch zwei weitere Parameter namens dx und dy besitzt. Sie soll die Position des Rechtecks verändern, indem sie dx zu der x Koordinate der Ecke corner addiert und ebenso dy zu der y Koordinate von corner.

12.8 Kopieren

Die Verwendung von Aliasnamen kann dazu führen, dass ein Programm schwer zu lesen ist, weil sich Änderungen, die an einer Stelle vorgenommen wurden unerwartete Effekte an anderen Stellen haben können. Es ist schwierig all die Variablen im Auge zu behalten, die auf ein gegebenes Objekt verweisen könnten.

Kopieren, oder Klonen, eines Objekts ist oft eine Alternative zur Verwendung von verschiedenen Namen für ein Objekt. Das copy Modul enthält eine Funktion namens copy, die von einem beliebigen Objekt eine Kopie herstellen kann:

>>> import copy
>>> p1 = Point()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = copy.copy(p1)
>>> p1 == p2
0
>>> samePoint(p1, p2)
1

Haben wir einmal das copy Modul importiert, können wir die copy Funktion verwenden um einen neuen Point zu erzeugen. p1 und p2 sind nicht der gleiche Punkt, aber sie enthalten die gleichen Daten.

Um ein einfaches Objekt zu kopieren, wie z. B. Point, das keine eingebetteten Objekte enthält, ist copy ausreichend. Dies nennt man flaches Kopieren.

Für Dinge wie ein Rectangle hingegen, das einen Verweis zu einem Point enthält, tut copy leider nicht ganz das Richtige: Es kopiert den Verweis auf das Point Objekt, sodass beide, das alte Rectangle wie auch das neueauf den selben einzelnen Point verweisen.

Wenn wir auf die übliche Weise ein Rechteck b1erzeugen und dann eine Kopie b2 erzeugen, indem wir copy benutzen, sieht das daraus resultierende Zustandsdiagramm so aus:

Das ist ziemlich sicher nicht das, was wir wollen. Denn rufen wir in dieser Situation growRect für eines der beiden Rectangles auf, wird das andere dadurch nicht beeinflusst. Aber ein Aufruf von moveRect für eines von beiden würde beide betreffen. Dieses Verhalten ist verwirrend und führt zu Fehleranfälligkeit.

Glücklicherweise enthält das copy Modul auch noch eine Funktion namens deepcopy, die nicht nur das Objekt selbst, sondern auch jedes darin eingebettete Objekt kopiert. Es wird dich kaum überraschen, dass diese Operation tiefes Kopieren heißt.

>>> b2 = copy.deepcopy(b1)

Nun sind b1 und b2 vollständig verschiedene Objekte.

Wir können deepcopy verwenden, um growRect so umzuschreiben, dass sie, anstatt ein gegebenes Rechteck Rectangle zu verändern, ein neues Rectangle erzeugt, das die gleiche Position wie das alte hat, aber neue Abmessungen:

def growRect(box, dwidth, dheight) :
  import copy
  newBox = copy.deepcopy(box)
  newBox.width = newBox.width + dwidth
  newBox.height = newBox.height + dheight
  return newBox

Übung: schreibe die Funktion moveRect so um, dass sie ein neues Rectangle erzeugt und zurückgibt, anstatt das alte zu verändern.

12.9 Glossar

Klasse
Ein benutzerdefinierter zusammengesetzter Typ. Eine Klasse kann auch als Muster für die Objekte betrachtet werden, die Instanzen von ihr sind.
class
A user-defined compound type. A class can also be thought of as a template for the objects that are instances of it.
instanziieren
Eine Instanz einer Klasse erzeugen.
instantiate
To create an instance of a class.
Instanz
Ein Objekt, das zu einer Klasse gehört.
instance
An object that belongs to a class.
Objekt
Ein zusammengesetzter Datentyp der oft dazu benützt wird, ein Ding oder einen Begriff der realen Welt zu modellieren.
object
A compound data type that is often used to model a thing or concept in the real world.
Konstruktor
Eine Methode, die benützt wird um neue Objekte zu erzeugen.
constructor
A method used to create new objects.
Attribut
Eines der benannten Datenelemente, die zusammen eine Instanz bilden.
attribute
One of the named data items that makes up an instance.
flache Gleichheit
Gleichheit von Verweisen, d. h. zwei Verweise, die auf das gleiche Objekt verweisen.
shallow equality
Equality of references, or two references that point to the same object.
tiefe Gleichheit
Gleichheit von Werten. Zwei Verweise, die auf Objekte verweisen, die den gleichen Wert haben.
deep equality
Equality of values, or two references that point to objects that have the same value.
flaches Kopieren
Kopieren des Inhalts eines Objekts, einschließlich aller Referenzen zu eingebetteten Objekten; durch die copy Funktion des copy Moduls implementiert.
shallow copy
To copy the contents of an object, including any references to embedded objects; implemented by the copy function in the copy module.
tiefes Kopieren
Kopieren des Inhalts eines Objekts sowie auch aller eingebetteter Objekte, in diese eingebetteter Objekte usw.; durch die deepcopy Funktion des copy Moduls implementiert.
deep copy
To copy the contents of an object as well as any embedded objects, and any objects embedded in them, and so on; implemented by the deepcopy function in the copy module.


zurück rauf weiter Englisch Index