Il s’agit d’une ancienne version du livre désormais connu sous le nom de Think Python. Vous préférerez peut-être lire une version plus récente.
Comment penser comme un informaticien |
Chapitre 15
15.1 Composition
À présent, vous avez vu plusieurs exemples de composition.L’un des premiers exemples était l’utilisation d’une invocation de méthode comme partie d’une expression. Un autre exemple est la structure imbriquée des instructions;vous pouvez mettre une instruction if à l’intérieur d’une boucle while, à l’intérieur d’une autre instruction if, et ainsi de suite.
Ayant vu ce modèle, et ayant appris les listes et les objets,vous ne devriez pas être surpris d’apprendre que vous pouvez créer des listes d’objets. Vous pouvez également créer des objets qui contiennent des listes (en tant qu’attributs) ; vous pouvez créer des listes qui contiennent des listes ; vous pouvez créer des objets qui contiennent des objets ; et ainsi de suite.
Dans ce chapitre et le suivant, nous examinerons quelques exemples de ces combinaisons, en utilisant les objets Card comme exemple.
15.2 Objets cartes
Si vous n’êtes pas familier avec les cartes à jouer courantes, ce serait le bon moment pour vous procurer un jeu, sinon ce chapitre risque de ne pas avoir beaucoup de sens.Il y a cinquante-deux cartes dans un jeu, chacune d’entre elles appartenant à l’une des quatre couleurs et à l’un des treize rangs. Les couleurs sont le pique, le cœur, le carreau et le trèfle (dans l’ordre décroissant au bridge). Les rangs sont les suivants : As, 2, 3, 4, 5, 6, 7, 8, 9, 10, Valet, Reine et Roi. Selon le jeu auquel vous jouez, le rang de l’As peut être supérieur à celui du Roi ou inférieur à celui du 2.
Si nous voulons définir un nouvel objet pour représenter une carte à jouer, il est évident que les attributs doivent être : rang et combinaison. Il n’est pas aussi évident de savoir quel type les attributs doivent être. Une possibilité est d’utiliser des chaînes contenant des mots comme « Spade » pour les couleurs et « Queen » pour les rangs. Un problème avec cette implémentation est qu’il ne serait pas facile de comparer les cartes pour voir laquelle a un rang ou une couleur plus élevée.
Une alternative est d’utiliser des entiers pour coder les rangs et les couleurs.Par « coder », nous ne voulons pas dire ce que certaines personnes pensent, c’est-à-dire crypter ou traduire en un code secret. Ce qu’un informaticien entend par « coder », c’est « définir une correspondance entre une séquence de nombres et les éléments que je veux représenter ». Par exemple :
Pique | -> | 3 |
Cœur | -> | 2 |
Diamant | -> | 1 |
Clubs | -> | 0 |
Une caractéristique évidente de cette correspondance est que les costumes correspondent aux entiers dans l’ordre, donc on peut comparer des combinaisons en comparant des entiers. La correspondance pour les rangs est assez évidente ; chacun des rangs numériques correspond au nombre entier correspondant, et pour les cartes de face :
Jack | -> | 11 |
Queen | -> | 12 |
King | -> | 13 |
La raison pour laquelle nous utilisons une notation mathématique pour ces mappings est qu’ils ne font pas partie du programme Python. Ils font partie de la conception du programme, mais ils n’apparaissent jamais explicitement dans le code. La définition de la classe pour le type Card ressemble à ceci:
classe Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
Comme d’habitude, nous fournissons une méthode d’initialisation qui prend un paramètre optionnel pour chaque attribut. La valeur par défaut de la couleur est0, ce qui représente les trèfles.
Pour créer une carte, nous invoquons le constructeur Card avec la couleur et le rang de la carte que nous voulons.
troisDeClubs = Card(3, 1)
Dans la section suivante, nous allons déterminer quelle carte nous venons de fabriquer.
15.3 Attributs de classe et la méthode __str__
Afin d’imprimer les objets Card d’une manière que les gens peuvent facilement lire, nous voulons mapper les codes entiers sur des mots. Une façon naturelle de le faire est avec des listes de chaînes de caractères. Nous assignons ces listes aux attributs de classe en haut de la définition de la classe:
classe Card:
suitList =
rankList =
#init method omitted
def __str__(self):
return (self.rankList + » de » +
self.suitList)
Un attribut de classe est défini en dehors de toute méthode, et il peut êtreaccessible depuis n’importe quelle méthode de la classe.
Dans __str__, nous pouvons utiliser suitList et rankListpour faire correspondre les valeurs numériques de suit et rank à des chaînes de caractères.Par exemple, l’expression self.suitList signifie « utiliser l’attribut suit de l’objet self comme un index dans l’attribut de classe nommé suitList, et sélectionner la chaîne de caractères appropriée. »
La raison du « narf » dans le premier élément de rankList est d’agir comme un gardien de place pour l’élément zéro-ième de la liste, qui ne devrait jamais être utilisé. Les seuls rangs valides sont de 1 à 13. Cet élément gaspillé n’est pas entièrement nécessaire. Nous aurions pu commencer à 0,comme d’habitude, mais il est moins confus de coder 2 comme 2, 3 comme 3, et ainsi de suite.
Avec les méthodes que nous avons jusqu’à présent, nous pouvons créer et imprimer des cartes:
>>> card1 = Card(1, 11)
>>> print card1
Jack of Diamonds
Les attributs de la classe comme suitList sont partagés par tous les Cardobjects. L’avantage de ceci est que nous pouvons utiliser n’importe quel Cardobject pour accéder aux attributs de classe:
>>> card2 = Card(1, 3)
>>> print card2
3 of Diamonds
>>> print card2.suitList
Diamonds
L’inconvénient est que si nous modifions un attribut de classe, cela affecte chaque instance de la classe. Par exemple, si nous décidons que « Valet de carreau » devrait en réalité s’appeler « Valet de baleines tournoyantes », nous pourrions faire ceci :
>>> card1.suitList = « Swirly Whales »
>>> print card1
Jack of Swirly Whales
Le problème est que tous les Diamants viennent de devenir desSwirly Whales :
>>> imprimer la carte2
3 des Baleines Swirly
Ce n’est généralement pas une bonne idée de modifier les attributs de classe.
15.4 Comparaison de cartes
Pour les types primitifs, il existe des opérateurs conditionnels(<, >, ==, etc.)qui comparent des valeurs et déterminent si l’une est supérieure, inférieure ou égale à une autre. Pour les types définis par l’utilisateur, nous pouvons remplacer le comportement des opérateurs intégrés en fournissant une méthode nommée __cmp__. Par convention, __cmp__ a deux paramètres, self et other, et renvoie1 si le premier objet est plus grand, -1 si ce second objet est plus grand, et 0 s’ils sont égaux l’un à l’autre.
Certains types sont complètement ordonnés, ce qui signifie que vous pouvez comparer n’importe quels deux éléments et dire lequel est le plus grand. Par exemple, les entierset les nombres à virgule flottante sont complètement ordonnés. Certains ensembles ne sont pas ordonnés, ce qui signifie qu’il n’y a aucun moyen significatif de dire qu’un élément est plus grand qu’un autre. Par exemple, les fruits sont non ordonnés, c’est pourquoi vous ne pouvez pas comparer des pommes et des oranges.
L’ensemble des cartes à jouer est partiellement ordonné, ce qui signifie que parfois vous pouvez comparer des cartes et parfois non. Par exemple, vous savez que le 3 de Trèfle est supérieur au 2 de Trèfle, et que le 3 deDiamant est supérieur au 3 de Trèfle. Mais lequel est le meilleur, le 3 de trèfle ou le 2 de carreau ? L’un a un rang supérieur, mais l’autre a une couleur supérieure.
Pour rendre les cartes comparables, il faut décider ce qui est le plus important, le rang ou la couleur. Pour être honnête, le choix est arbitraire. Pour le plaisir de choisir, nous dirons que la couleur est plusimportante, parce qu’un nouveau jeu de cartes arrive trié avec tous les trèfles ensemble, suivis de tous les carreaux, et ainsi de suite.
Avec cette décision, nous pouvons écrire __cmp__:
def __cmp__(self, other):
# vérifier les couleurs
if self.suit > other.suit : return 1
if self.suit < other.suit : return -1
# les couleurs sont les mêmes… vérifier les rangs
if self.rank > other.rank : return 1
if self.rank < other.rank : return -1
# les rangs sont les mêmes… c’est une égalité
return 0
Dans cet ordre, les As apparaissent plus bas que les Deuces (2s).
En guise d’exercice, modifiez __cmp__ pour que les As apparaissent plus haut que les Rois.
15.5 Decks
Maintenant que nous avons des objets pour représenter les Cartes, la prochaine étape logique est de définir une classe pour représenter un Deck. Bien sûr, un Deck est composé de cartes, donc chaque objet Deck contiendra une liste de cartes comme attribut.
Ce qui suit est une définition de classe pour la classe Deck. La méthode d’initialisation crée l’attribut cartes et génère le jeu standard de cinquante-deux cartes:
classe Deck:
def __init__(self):
self.cartes =
for suit in range(4):
for rank in range(1, 14):
self.cards.append Card(suit, rank))
La façon la plus simple de peupler le jeu est avec une boucle imbriquée. La boucle externe énumère les couleurs de 0 à 3, la boucle interne énumère les rangs de 1 à 13. Étant donné que la boucle externe est itérée quatre fois et que la boucle interne est itérée treize fois, le nombre total de fois où le corps est exécuté est de cinquante-deux (treize fois quatre). Chaque itérationcrée une nouvelle instance de Card avec la couleur et le rang actuels, et ajoute cette carte à la liste des cartes.
La méthode append fonctionne sur les listes mais pas, bien sûr, sur les tuples.
15.6 Impression du deck
Comme d’habitude, lorsque nous définissons un nouveau type d’objet, nous voulons une méthode qui imprime le contenu d’un objet.Pour imprimer un Deck, nous parcourons la liste et imprimons chaque carte:
class Deck:
…
def printDeck(self):
for card in self.cards:
print card
Ici, et à partir de maintenant, l’ellipse (….) indique que nous avonsomis les autres méthodes de la classe.
Comme alternative à printDeck, nous pourrions écrire une méthode __str__ pour la classe Deck. L’avantage de __str__ est qu’elle est plus flexible. Plutôt que de simplement imprimer le contenu de l’objet, elle génère une représentation sous forme de chaîne de caractères que d’autres parties du programme peuvent manipuler avant l’impression, ou stocker pour une utilisation ultérieure.
Voici une version de __str__ qui renvoie une représentation sous forme de chaîne de caractères d’un Deck.Pour ajouter un peu de piquant, il arrange les cartes dans une cascade où chaque carte est indentée d’un espace de plus que la carte précédente:
classe Deck:
….
def __str__(self):
s = « »
for i in range(len(self.cards)):
s = s + « »*i + str(self.cards) + « \n »
return s
Cet exemple démontre plusieurs fonctionnalités. Premièrement, au lieu de parcourir self.cards et d’affecter chaque carte à une variable, nous utilisons i comme variable de boucle et comme index dans la liste des cartes.
Deuxièmement, nous utilisons l’opérateur de multiplication de chaîne pour indenter chaque carte par un espace de plus que la dernière. L’expression « »*i donne un nombre d’espaces égal à la valeur courante de i.
Troisièmement, au lieu d’utiliser la commande print pour imprimer les cartes, nous utilisons la fonction str. Passer un objet comme argument àstr est équivalent à invoquer la méthode __str__ sur l’objet.
Enfin, nous utilisons la variable s comme accumulateur.Initialement, s est la chaîne vide. A chaque passage de la boucle, une nouvelle chaîne est générée et concaténée avec l’ancienne valeur de s pour obtenir la nouvelle valeur. Lorsque la boucle se termine, s contient la représentation complète de la chaîne de caractères du Deck, qui ressemble à ceci :
>>> deck = Deck()
>>>> print deck
Ace de trèfle
2 de trèfle
3 de trèfle
4 de trèfle
5 de trèfle
6 de trèfle
7 de trèfle
. Trèfle
7 de Trèfle
8 de Trèfle
9 de Trèfle
10 de Trèfle
Jack de Trèfle
Reine de Trèfle
Roi de Trèfle
Ace de Carreau
Et ainsi de suite. Même si le résultat apparaît sur 52 lignes, il s’agit d’une longue chaîne de caractères qui contient des nouvelles lignes.
15.7 Mélange du jeu
Si un jeu est parfaitement mélangé, alors toute carte a la même probabilité d’apparaître n’importe où dans le jeu, et tout emplacement dans le jeu a la même probabilité de contenir n’importe quelle carte.
Pour mélanger le jeu, nous utiliserons la fonction randrange du module random. Avec deux arguments entiers, a et b, randrange choisit un entier aléatoire dans l’intervalle a <= x < b. Puisque la limite supérieure est strictement inférieure à b, nous pouvons utiliser la longueur d’une liste comme second argument, et nous sommes assurés d’obtenir un index légal.Par exemple, cette expression choisit l’indice d’une carte aléatoire dans un jeu de cartes:
random.randrange(0, len(self.cards))
Un moyen facile de mélanger le jeu de cartes est de parcourir les cartes et d’échanger chaque carte avec une carte choisie au hasard. Il est possible que la carte soit échangée avec elle-même, mais ce n’est pas grave. En fait, si nous excluions cette possibilité, l’ordre des cartes serait moins qu’entièrement aléatoire:
classe 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
Plutôt que de supposer qu’il y a cinquante-deux cartes dans le jeu, nous obtenons la longueur réelle de la liste et la stockons dans nCards.
Pour chaque carte du jeu, nous choisissons une carte aléatoire parmi les cartes qui n’ont pas encore été mélangées. Ensuite, nous échangeons la carte courante (i) avec la carte choisie (j). Pour échanger les cartes, nous utilisons une affectation de tuple, comme dans la section 9.2:
self.cards, self.cards = self.cards, self.cards
En guise d’exercice, réécrivez cette ligne de codesans utiliser d’affectation de séquence.
15.8 Retirer et distribuer les cartes
Une autre méthode qui serait utile pour la classe Deck est removeCard, qui prend une carte comme argument, la retire et renvoie True si la carte était dans le jeu et False else:
classe Deck:
…
def removeCard(self, card):
if card in self.cards:
self.cards.remove(card)
return True
else :
return False
L’opérateur in renvoie true si le premier opérande est dans thesecond, qui doit être une liste ou un tuple. Si le premier opérande est unobjet, Python utilise la méthode __cmp__ de l’objet pour déterminer l’égalité avec les éléments de la liste. Puisque la méthode __cmp__ de la classeCard vérifie l’égalité profonde, la méthode removeCard vérifie l’égalité profonde.
Pour distribuer des cartes, nous voulons retirer et retourner la carte supérieure.La méthode pop de la liste fournit un moyen pratique de le faire:
classe Deck:
…
def popCard(self):
return self.cards.pop()
En fait, pop retire la dernière carte de la liste, donc nous distribuons en fait à partir du bas du paquet.
Une autre opération que nous sommes susceptibles de vouloir est la fonction booléenneisEmpty, qui renvoie vrai si le jeu ne contient aucune carte :
classe Deck:
…
def isEmpty(self):
return (len(self.cards) == 0)
15.9 Glossaire
encoder Représenter un ensemble de valeurs à l’aide d’un autre ensemble de valeurs en construisant une correspondance entre eux. attribut de classe Une variable qui est définie à l’intérieur d’une définition de classe mais en dehors de toute méthode. Les attributs de classe sont accessibles depuis n’importe quelle méthode de la classe et sont partagés par toutes les instances de la classe. accumulateur Variable utilisée dans une boucle pour accumuler une série de valeurs, par exemple en les concaténant sur une chaîne ou en les ajoutant à une somme courante.
Il s’agit d’une ancienne version du livre désormais connu sous le nom de Think Python. Vous pourriez préférer lire une version plus récente.
Comment penser comme un informaticien |
.