Gruppen von Objekten

Dies ist eine ältere Version des Buches, das jetzt als Think Python bekannt ist. Vielleicht ziehen Sie es vor, eine neuere Version zu lesen.

How to Think Like a Computer Scientist

Kapitel 15

15.1 Komposition

Sie haben inzwischen mehrere Beispiele für Komposition gesehen.Eines der ersten Beispiele war die Verwendung eines Methodenaufrufs als Teil eines Ausdrucks. Ein weiteres Beispiel ist die verschachtelte Struktur von Anweisungen; Sie können eine if-Anweisung in eine while-Schleife einfügen, in eine weitere if-Anweisung und so weiter.

Nachdem Sie dieses Muster gesehen und etwas über Listen und Objekte gelernt haben, sollte es Sie nicht überraschen zu erfahren, dass Sie Listen von Objekten erstellen können. Sie können auch Objekte erstellen, die Listen (als Attribute) enthalten; Sie können Listen erstellen, die Listen enthalten; Sie können Objekte erstellen, die Objekte enthalten; und so weiter.

In diesem und dem nächsten Kapitel werden wir uns einige Beispiele für diese Kombinationen ansehen, wobei wir Kartenobjekte als Beispiel verwenden.

15.2 Kartenobjekte

Wenn Sie nicht mit den üblichen Spielkarten vertraut sind, wäre jetzt ein guter Zeitpunkt, sich ein Kartenspiel zu besorgen, sonst macht dieses Kapitel vielleicht nicht viel Sinn.Es gibt zweiundfünfzig Karten in einem Deck, von denen jede zu einer von vier Farben und einem von dreizehn Rängen gehört. Die Farben sind Pik, Herz, Karo und Kreuz (in absteigender Reihenfolge beim Bridge). Die Ränge sind Ass, 2, 3, 4, 5, 6, 7, 8, 9, 10, Bube, Dame und König. Je nach Spiel kann der Rang von Ass höher als König oder niedriger als 2 sein.

Wenn wir ein neues Objekt definieren wollen, das eine Spielkarte darstellt, ist es offensichtlich, welche Attribute es haben sollte: Rang und Farbe. Es ist nicht so offensichtlich, welchen Typ die Attribute haben sollten. Eine Möglichkeit besteht darin, Zeichenketten zu verwenden, die Wörter wie „Spade“ für die Farbe und „Queen“ für den Rang enthalten. Ein Problem bei dieser Implementierung ist, dass es nicht einfach wäre, Karten zu vergleichen, um zu sehen, welche einen höheren Rang oder eine höhere Farbe hat.

Eine Alternative ist, ganze Zahlen zu verwenden, um die Ränge und Farben zu kodieren.Mit „kodieren“ meinen wir nicht das, was manche Leute denken, nämlich zu verschlüsseln oder in einen Geheimcode zu übersetzen. Was ein Informatiker mit „kodieren“ meint, ist „eine Abbildung zwischen einer Zahlenfolge und den Elementen, die ich darstellen möchte, zu definieren“. Zum Beispiel:

Pik -> 3
Herz -> 2
Diamanten -> 1
Clubs -> 0

Ein offensichtliches Merkmal dieser Zuordnung ist, dass die Farben in der Reihenfolge auf ganze Zahlen abgebildet werden, Wir können also Anzüge durch den Vergleich von ganzen Zahlen vergleichen. Die Zuordnung für Ränge ist ziemlich offensichtlich; jeder der numerischen Ränge entspricht der entsprechenden ganzen Zahl, und für Bildkarten:

Bube -> 11
Dame -> 12
König -> 13

Der Grund, warum wir mathematische Notation für diese Zuordnungen verwenden, ist, dass sie nicht Teil des Python-Programms sind. Sie sind Teil des Programmdesigns, aber sie erscheinen nie explizit im Code. Die Klassendefinition für den Typ Card sieht wie folgt aus:

class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank

Wie üblich stellen wir eine Initialisierungsmethode zur Verfügung, die einen optionalen Parameter für jedes Attribut annimmt. Der Standardwert von suit ist0, was für Kreuz steht.

Um eine Karte zu erstellen, rufen wir den Card-Konstruktor mit der Farbe und dem Rang der gewünschten Karte auf.

threeOfClubs = Card(3, 1)

Im nächsten Abschnitt werden wir herausfinden, welche Karte wir gerade erstellt haben.

15.3 Klassenattribute und die __str__-Methode

Um Kartenobjekte in einer für Menschen leicht lesbaren Form auszugeben, wollen wir die Integer-Codes auf Wörter abbilden. Ein natürlicher Weg, dies zu tun, ist die Verwendung von Listen von Zeichenketten. Wir weisen diese Listen den Klassenattributen am Anfang der Klassendefinition zu:

class Card:
suitList =
rankList =
#init method omitted
def __str__(self):
return (self.rankList + “ of “ +
self.suitList)

Ein Klassenattribut wird außerhalb einer Methode definiert und kann von jeder der Methoden der Klasse aus aufgerufen werden.

Innerhalb von __str__ können wir suitList und rankList verwenden, um die numerischen Werte von suit und rank auf Zeichenketten abzubilden.Zum Beispiel bedeutet der Ausdruck self.suitList „verwende das Attribut suit des Objekts self als Index in das Klassenattribut namens suitList und wähle die entsprechende Zeichenkette aus.“

Der Grund für das „narf“ im ersten Element von rankList ist, dass es als Platzhalter für das null-te Element der Liste fungiert, das niemals verwendet werden sollte. Die einzigen gültigen Ränge sind 1 bis 13. Dieses überflüssige Element ist nicht unbedingt notwendig. Wir hätten wie üblich bei 0 beginnen können, aber es ist weniger verwirrend, 2 als 2 zu kodieren, 3 als 3 und so weiter.

Mit den bisherigen Methoden können wir Karten erstellen und ausdrucken:

>>> card1 = Card(1, 11)
>>> print card1
Jack of Diamonds

Klassenattribute wie suitList werden von allen Cardobjects geteilt. Das hat den Vorteil, dass wir jedes beliebige Cardobject verwenden können, um auf die Klassenattribute zuzugreifen:

>>> card2 = Card(1, 3)
>>> print card2
3 of Diamonds
>>> print card2.suitList
Diamonds

Der Nachteil ist, dass wenn wir ein Klassenattribut ändern, es jede Instanz der Klasse betrifft. Wenn wir zum Beispiel beschließen, dass „Karo-Bube“ eigentlich „Bube der Wirbelwale“ heißen sollte, könnten wir Folgendes tun:

>>> card1.suitList = „Swirly Whales“
>>> print card1
Jack of Swirly Whales

Das Problem ist, dass alle Diamonds einfach zuSwirly Whales wurden:

>>> print card2
3 of Swirly Whales

Es ist normalerweise keine gute Idee, Klassenattribute zu verändern.

15.4 Karten vergleichen

Für primitive Typen gibt es bedingte Operatoren (<, >, ==, usw.), die Werte vergleichen und feststellen, ob einer größer, kleiner oder gleich einem anderen ist. Für benutzerdefinierte Typen können wir das Verhalten der eingebauten Operatoren außer Kraft setzen, indem wir eine Methode namens__cmp__ bereitstellen. Konventionell hat __cmp__ zwei Parameter, self und other, und gibt1 zurück, wenn das erste Objekt größer ist, -1, wenn das zweite Objekt größer ist, und 0, wenn sie gleich sind.

Einige Typen sind vollständig geordnet, was bedeutet, dass man zwei beliebige Elemente vergleichen und sagen kann, welches größer ist. Zum Beispiel sind die ganzen Zahlen und die Gleitkommazahlen vollständig geordnet. Einige Mengen sind ungeordnet, d. h. es gibt keine sinnvolle Möglichkeit zu sagen, dass ein Element größer als ein anderes ist. Zum Beispiel sind die Früchte ungeordnet, weshalb man Äpfel und Birnen nicht vergleichen kann.

Die Menge der Spielkarten ist teilweise geordnet, was bedeutet, dass man manchmal Karten vergleichen kann und manchmal nicht. Man weiß zum Beispiel, dass die Kreuz 3 höher ist als die Kreuz 2, und die Karo 3 ist höher als die Kreuz 3. Aber welche ist besser, die Kreuz 3 oder die Karo 2? Die eine hat einen höheren Rang, aber die andere hat eine höhere Farbe.

Um Karten vergleichbar zu machen, muss man entscheiden, was wichtiger ist, Rang oder Farbe. Um ehrlich zu sein, ist die Wahl willkürlich. Der Einfachheit halber sagen wir, dass die Farbe wichtiger ist, denn ein neuer Kartensatz kommt sortiert, mit allen Kreuzkarten zusammen, gefolgt von allen Karo-Karten und so weiter.

Mit dieser Entscheidung können wir __cmp__ schreiben:

def __cmp__(self, other):
# prüfe die Farben
if self.suit > other.suit: return 1
if self.suit < other.suit: return -1
# suits are the same… check ranks
if self.rank > other.rank: return 1
if self.rank < other.rank: return -1
# ranks are the same… it’s a tie
return 0

In dieser Reihenfolge erscheinen Asse niedriger als Zweien (2s).

Als Übung modifizieren Sie __cmp__ so, dass Asse höher als Könige eingestuft werden.

15.5 Decks

Nachdem wir nun Objekte haben, die Karten repräsentieren, ist der nächste logische Schritt, eine Klasse zu definieren, die ein Deck repräsentiert. Natürlich besteht ein Deck aus Karten, so dass jedes Deck-Objekt eine Liste von Karten als Attribut enthält.

Im Folgenden finden Sie eine Klassendefinition für die Klasse Deck. DieInitialisierungsmethode erzeugt das Attribut cards und generiert den Standardsatz von zweiundfünfzig Karten:

class Deck:
def __init__(self):
self.cards =
for suit in range(4):
for rank in range(1, 14):
self.cards.append(Card(suit, rank))

Am einfachsten lässt sich das Deck mit einer verschachtelten Schleife auffüllen. Die äußere Schleife zählt die Farben von 0 bis 3 auf, die innere Schleife zählt die Ränge von 1 bis 13 auf. Da die äußere Schleife viermal und die innere Schleife dreizehnmal durchlaufen wird, wird der Körper insgesamt zweiundfünfzigmal ausgeführt (dreizehnmal vier). Jede Iteration erzeugt eine neue Instanz von Card mit der aktuellen Farbe und dem aktuellen Rang und hängt diese Karte an die Kartenliste an.

Die Methode append funktioniert bei Listen, aber natürlich nicht bei Tupeln.

15.6 Drucken des Decks

Wie üblich, wenn wir einen neuen Objekttyp definieren, wollen wir eine Methode, die den Inhalt eines Objekts druckt.Um ein Deck auszudrucken, durchlaufen wir die Liste und drucken jede Karte:

class Deck:

def printDeck(self):
for card in self.cards:
print card

Hier, und von nun an, zeigt die Ellipse (…) an, dass wir die anderen Methoden der Klasse weggelassen haben.

Als Alternative zu printDeck könnten wir eine __str__-Methode für die Deck-Klasse schreiben. Der Vorteil von __str__ ist, dass es flexibler ist. Anstatt nur den Inhalt des Objekts zu drucken, erzeugt sie eine String-Repräsentation, die andere Teile des Programms vor dem Drucken manipulieren oder für eine spätere Verwendung speichern können.

Hier ist eine Version von __str__, die eine String-Repräsentation eines Decks zurückgibt.Um ein wenig Pep hinzuzufügen, ordnet es die Karten in einer Kaskade an, wobei jede Karte um ein Leerzeichen mehr eingerückt ist als die vorherige Karte:

class Deck:

def __str__(self):
s = „“
for i in range(len(self.cards)):
s = s + „“*i + str(self.cards) + „\n“
return s

Dieses Beispiel demonstriert mehrere Funktionen. Erstens: Anstatt self.cards zu durchlaufen und jede Karte einer Variablen zuzuweisen, verwenden wir i als Schleifenvariable und als Index in der Liste der Karten.

Zweitens: Wir verwenden den String-Multiplikationsoperator, um jede Karte um ein Leerzeichen weiter einzurücken als die letzte. Der Ausdruck““*i ergibt eine Anzahl von Leerzeichen, die dem aktuellen Wert von i entspricht.

Drittens verwenden wir die Funktion str, anstatt den Befehl print zu verwenden, um die Karten zu drucken. Die Übergabe eines Objekts als Argument anstr ist gleichbedeutend mit dem Aufruf der __str__-Methode für das Objekt.

Schließlich verwenden wir die Variable s als Akkumulator. s ist anfangs die leere Zeichenkette. Jedes Mal, wenn die Schleife durchlaufen wird, wird eine neue Zeichenkette erzeugt und mit dem alten Wert von sto verkettet, um den neuen Wert zu erhalten. Wenn die Schleife endet, enthält s die vollständige Zeichenkettendarstellung des Decks, die wie folgt aussieht:

>>> deck = Deck()
>>> print deck
Kreuz-Ass
2 Kreuz
3 Kreuz
4 Kreuz
5 Kreuz
6 Kreuz
7 Kreuz
8 Kreuz
9 Kreuz
10 Kreuz
Kreuz-Bube
Kreuz-Dame
Kreuz-König
Karo-Ass

Und so weiter. Obwohl das Ergebnis in 52 Zeilen erscheint, handelt es sich um eine einzige lange Zeichenkette, die Zeilenumbrüche enthält.

15.7 Mischen des Decks

Wenn ein Deck perfekt gemischt ist, dann ist es gleich wahrscheinlich, dass jede Karte irgendwo im Deck auftaucht, und es ist gleich wahrscheinlich, dass jede Stelle im Deck eine Karte enthält.

Um das Deck zu mischen, verwenden wir die Funktion randrange aus dem Zufallsmodul. Mit zwei ganzzahligen Argumenten, a und b, wählt randrange eine zufällige ganze Zahl im Bereich a <= x < b. Da die obere Grenze strikt kleiner als b ist, können wir die Länge einer Liste als zweites Argument verwenden, und wir erhalten garantiert einen zulässigen Index.Zum Beispiel wählt dieser Ausdruck den Index einer zufälligen Karte in einem Stapel:

random.randrange(0, len(self.cards))

Eine einfache Möglichkeit, den Stapel zu mischen, besteht darin, die Karten zu durchlaufen und jede Karte mit einer zufällig ausgewählten Karte zu vertauschen. Es ist möglich, dass die Karte mit sich selbst vertauscht wird, aber das ist in Ordnung. Wenn wir diese Möglichkeit ausschließen würden, wäre die Reihenfolge der Karten sogar weniger als völlig zufällig:

class Deck:

def shuffle(self):
import random
nCards = len(self.cards)
for i in range(nCards):
j = random.randrange(i, nCards)
self.cards, self.cards = self.cards, self.cards

Anstatt davon auszugehen, dass es zweiundfünfzig Karten im Deck gibt, ermitteln wir die tatsächliche Länge der Liste und speichern sie in nCards.

Für jede Karte im Deck wählen wir eine zufällige Karte aus den Karten, die noch nicht gemischt worden sind. Dann vertauschen wir die aktuelle Karte (i) mit der ausgewählten Karte (j). Um die Karten zu tauschen, verwenden wir eine Tupel-Zuweisung, wie in Abschnitt 9.2:

self.cards, self.cards = self.cards, self.cards

Schreiben Sie als Übung diese Code-Zeile neu, ohne eine Sequenz-Zuweisung zu verwenden.

15.8 Entfernen und Austeilen von Karten

Eine weitere Methode, die für die Deck-Klasse nützlich wäre, ist removeCard, die eine Karte als Argument nimmt, sie entfernt und True zurückgibt, wenn die Karte im Deck war, und False andernfalls:

class Deck:

def removeCard(self, card):
if card in self.cards:
self.cards.remove(card)
return True
else:
Rückgabe False

Der in-Operator gibt true zurück, wenn der erste Operand in thesecond ist, was eine Liste oder ein Tupel sein muss. Wenn der erste Operand ein Objekt ist, verwendet Python die __cmp__-Methode des Objekts, um die Qualität mit den Elementen in der Liste zu bestimmen. Da __cmp__ in der KlasseCard auf tiefe Gleichheit prüft, prüft die Methode removeCard auf tiefe Gleichheit.

Um Karten zu verteilen, wollen wir die oberste Karte entfernen und zurückgeben.Die Listenmethode pop bietet eine bequeme Möglichkeit, dies zu tun:

class Deck:

def popCard(self):
return self.cards.pop()

Aktuell entfernt pop die letzte Karte in der Liste, so dass wir ineffektiv vom Ende des Decks aus austeilen.

Eine weitere Operation, die wir wahrscheinlich benötigen, ist die boolesche FunktionisEmpty, die true zurückgibt, wenn das Deck keine Karten enthält:

class Deck:

def isEmpty(self):
return (len(self.cards) == 0)

15.9 Glossar

encode Einen Satz von Werten durch einen anderen Satz von Werten darstellen, indem man ein Mapping zwischen ihnen konstruiert. class attribute Eine Variable, die innerhalb einer Klassendefinition, aber außerhalb einer Methode definiert ist. Klassenattribute sind von jeder Methode der Klasse aus zugänglich und werden von allen Instanzen der Klasse gemeinsam genutzt. Akkumulator Eine Variable, die in einer Schleife verwendet wird, um eine Reihe von Werten zu akkumulieren, z. B. durch Verkettung mit einer Zeichenkette oder durch Addition zu einer laufenden Summe.

Dies ist eine ältere Version des Buches, das jetzt als Think Python bekannt ist. Vielleicht ziehen Sie es vor, eine neuere Version zu lesen.

How to Think Like a Computer Scientist

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.