Insiemi di oggetti

Questa è una vecchia versione del libro ora conosciuto come Think Python. Potreste preferire leggere una versione più recente.

Come pensare come un informatico

Capitolo 15

15.1 Composizione

Ora avete visto diversi esempi di composizione: uno dei primi esempi era l’uso di un’invocazione di metodo come parte di un’espressione. Un altro esempio è la struttura annidata delle istruzioni; potete mettere un’istruzione if all’interno di un ciclo while, all’interno di un’altra istruzione if, e così via.

Avendo visto questo modello, e avendo imparato a conoscere liste e oggetti, non dovreste essere sorpresi di imparare che potete creare liste di oggetti. Potete anche creare oggetti che contengono liste (come attributi); potete creare liste che contengono liste; potete creare oggetti che contengono oggetti; e così via.

In questo capitolo e nel prossimo, vedremo alcuni esempi di queste combinazioni, usando gli oggetti Card come esempio.

15.2 Oggetti carta

Se non avete familiarità con le comuni carte da gioco, questo sarebbe un buon momento per procurarvi un mazzo, altrimenti questo capitolo potrebbe non avere molto senso.Ci sono cinquantadue carte in un mazzo, ognuna delle quali appartiene a uno dei quattro semi e a uno dei tredici gradi. I semi sono Picche, Cuori, Quadri e Fiori (in ordine decrescente nel bridge). I gradi sono Asso, 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, Regina e Re. A seconda del gioco che state giocando, il rango di Asso può essere più alto di Re o più basso di 2.

Se vogliamo definire un nuovo oggetto per rappresentare una carta da gioco, è ovvio quali dovrebbero essere gli attributi: rango e titolo. Non è altrettanto ovvio quale tipo dovrebbero essere gli attributi. Una possibilità è quella di usare stringhe contenenti parole come “Spade” per i semi e “Regina” per i gradi. Un problema con questa implementazione è che non sarebbe facile confrontare le carte per vedere quale ha un rango o un seme più alto.

Un’alternativa è usare numeri interi per codificare i ranghi e i semi.Con “codificare”, non intendiamo quello che alcune persone pensano, cioè codificare o tradurre in un codice segreto. Ciò che un informatico intende per “codificare” è “definire una mappatura tra una sequenza di numeri e gli elementi che voglio rappresentare”. Per esempio:

Spade -> 3
Cuori -> 2
Diamanti -> 1
Club -> 0

Una caratteristica ovvia di questa mappatura è che i semi mappano gli interi in ordine, quindi possiamo confrontare i semi confrontando i numeri interi. La mappatura per i ranghi è abbastanza ovvia; ciascuno dei ranghi numerici mappa all’intero corrispondente, e per le carte di faccia:

Jack -> 11
Queen -> 12
King -> 13

La ragione per cui stiamo usando la notazione matematica per queste mappature è che non fanno parte del programma Python. Fanno parte del design del programma, ma non appaiono mai esplicitamente nel codice. La definizione della classe per il tipo Card appare così:

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

Come al solito, forniamo un metodo di inizializzazione che prende un parametro opzionale per ogni attributo. Il valore predefinito di suit è0, che rappresenta i Clubs.

Per creare una Card, invochiamo il costruttore Card con il suit e il rank della carta che vogliamo.

threeOfClubs = Card(3, 1)

Nella prossima sezione capiremo quale carta abbiamo appena fatto.

15.3 Attributi di classe e il metodo __str__

Per stampare gli oggetti Card in un modo che le persone possano leggere facilmente, vogliamo mappare i codici interi in parole. Un modo naturale per farlo è con liste di stringhe. Assegniamo queste liste a classattributes all’inizio della definizione della classe:

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

Un attributo di classe è definito al di fuori di qualsiasi metodo, e può essere accessibile da qualsiasi metodo della classe.

All’interno di __str__, possiamo usare suitList e rankList per mappare i valori numerici di suit e rank in stringhe.Per esempio, l’espressione self.suitList significa “usare l’attributo suit dell’oggetto self come indice dell’attributo di classe chiamato suitList, e selezionare la stringa appropriata.”

La ragione per il “narf” nel primo elemento in rankList è di agire come un custode di posto per l’elemento zero-esimo della lista, che non dovrebbe mai essere usato. Gli unici ranghi validi sono da 1 a 13. Questo elemento sprecato non è del tutto necessario. Avremmo potuto iniziare da 0, come al solito, ma è meno confuso codificare 2 come 2, 3 come 3, e così via.

Con i metodi che abbiamo finora, possiamo creare e stampare carte:

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

Gli attributi della classe come suitList sono condivisi da tutti i Cardobjects. Il vantaggio di questo è che possiamo usare qualsiasi Cardobject per accedere agli attributi della classe:

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

Lo svantaggio è che se modifichiamo un attributo di classe, ciò influisce su ogni istanza della classe. Per esempio, se decidiamo che “Jack of Diamonds” dovrebbe essere chiamato “Jack of Swirly Whales”, potremmo fare così:

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

Il problema è che tutti i Diamanti sono appena diventatiSwirly Whales:

>>> print card2
3 di Balene Girevoli

Di solito non è una buona idea modificare gli attributi di classe.

15.4 Confrontare le carte

Per i tipi primitivi, ci sono operatori condizionali (<, >, ==, ecc.) che confrontano i valori e determinano quando uno è maggiore, minore o uguale ad un altro. Per i tipi definiti dall’utente, possiamo sovrascrivere il comportamento degli operatori incorporati fornendo un metodo chiamato__cmp__. Per convenzione, __cmp__ ha due parametri, self e other, e restituisce1 se il primo oggetto è più grande, -1 se il secondo oggetto è più grande, e 0 se sono uguali tra loro.

Alcuni tipi sono completamente ordinati, il che significa che potete confrontare qualsiasi due elementi e dire quale è più grande. Per esempio, gli interi e i numeri in virgola mobile sono completamente ordinati. Alcuni insiemi sono disordinati, il che significa che non c’è un modo significativo per dire che un elemento è più grande di un altro. Per esempio, la frutta è disordinata, per questo non si possono confrontare mele e arance.

L’insieme delle carte da gioco è parzialmente ordinato, il che significa che a volte si possono confrontare le carte e a volte no. Per esempio, sai che il 3 di fiori è superiore al 2 di fiori, e il 3 di quadri è superiore al 3 di fiori. Ma qual è meglio il 3 di Fiori o il 2 di Quadri? Uno ha un rango più alto, ma l’altro ha un seme più alto.

Per rendere le carte comparabili, devi decidere quale è più importante, il rango o il seme. Per essere onesti, la scelta è arbitraria. Per il gusto di scegliere, diremo che il seme è più importante, perché un nuovo mazzo di carte viene ordinato con tutti i Club insieme, seguito da tutti i Diamanti, e così via.

Con questo deciso, possiamo scrivere __cmp__:

def __cmp__(self, other):
# controlla i semi
if self.suit > other.suit: return 1
if self.suit < other.suit: return -1
# i semi sono uguali… controlla i gradi
if self.rank > altro.rank: return 1
if self.rank < altro.rank: return -1
# i ranghi sono uguali… è un pareggio
return 0

In questo ordinamento, gli Assi appaiono più bassi dei Due (2).

Come esercizio, modificate __cmp__ in modo che gli Assi siano classificati più in alto dei Re.

15.5 Mazzi

Ora che abbiamo oggetti per rappresentare le Carte, il prossimo passo logico è definire una classe per rappresentare un Mazzo. Naturalmente, un mazzo è fatto di carte, quindi ogni oggetto Deck conterrà un elenco di carte come attributo.

Quella che segue è una definizione di classe per la classe Deck. Il metodo di inizializzazione crea l’attributo cards e genera il set standard di cinquantadue carte:

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

Il modo più semplice per popolare il mazzo è con un ciclo annidato. Il ciclo esterno elenca i semi da 0 a 3. Il ciclo interno elenca i gradi da 1 a 13. Poiché il ciclo esterno itera quattro volte e il ciclo interno itera tredici volte, il numero totale di volte che il corpo viene eseguito è cinquantadue (tredici volte quattro). Ogni iterazione crea una nuova istanza di Card con il seme e il grado correnti, e aggiunge quella carta alla lista cards.

Il metodo append funziona sulle liste ma non, ovviamente, sulle tuple.

15.6 Stampare il mazzo

Come al solito, quando definiamo un nuovo tipo di oggetto vogliamo un metodo che stampi il contenuto di un oggetto.Per stampare un mazzo, attraversiamo la lista e stampiamo ogni carta:

class Deck:

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

Qui, e da ora in poi, l’ellissi (….) indica che abbiamo omesso gli altri metodi della classe.

In alternativa a printDeck, potremmo scrivere un metodo __str__ per la classe Deck. Il vantaggio di __str__ è che è più flessibile. Piuttosto che stampare semplicemente il contenuto dell’oggetto, genera una rappresentazione della stringa che altre parti del programma possono manipolare prima di stampare, o memorizzare per un uso successivo.

Ecco una versione di __str__ che restituisce una rappresentazione della stringa di un Deck.Per aggiungere un po’ di vivacità, dispone le carte in una cascata dove ogni carta è rientrata di uno spazio in più rispetto alla carta precedente:

class Deck:

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

Questo esempio dimostra diverse caratteristiche. Primo, invece di attraversare self.cards e assegnare ogni carta ad una variabile, stiamo usando i come variabile del ciclo e un indice nella lista delle carte.

Secondo, stiamo usando l’operatore di moltiplicazione delle stringhe per indentare ogni carta di uno spazio in più dell’ultima. L’espressione””*i produce un numero di spazi uguale al valore corrente di i.

In terzo luogo, invece di usare il comando print per stampare le carte, usiamo la funzione str. Passare un oggetto come argomento a str equivale a invocare il metodo __str__ sull’oggetto.

Infine, usiamo la variabile s come accumulatore. Ogni volta attraverso il ciclo, una nuova stringa viene generata e concatenata con il vecchio valore di s per ottenere il nuovo valore. Quando il ciclo finisce, s contiene la rappresentazione completa della stringa del Deck, che assomiglia a questa:

>>> deck = Deck()
>>> print deck
Ace di Fiori
2 di Fiori
3 di Fiori
4 di Fiori
5 di Fiori
6 di Clubs
7 di Clubs
8 di Clubs
9 di Clubs
10 di Clubs
Jack of Clubs
Queen of Clubs
King of Clubs
Ace of Diamonds

E così via. Anche se il risultato appare su 52 linee, è una lunga stringa che contiene newlines.

15.7 Mescolare il mazzo

Se un mazzo è perfettamente mescolato, allora ogni carta ha la stessa probabilità di apparire in qualsiasi punto del mazzo, e ogni posizione nel mazzo ha la stessa probabilità di contenere qualsiasi carta.

Per mescolare il mazzo, useremo la funzione randrange del modulo random. Con due argomenti interi, a e b, randrange sceglie un intero casuale nell’intervallo a <= x < b. Poiché il limite superiore è strettamente inferiore a b, possiamo usare la lunghezza di una lista come secondo argomento, e abbiamo la garanzia di ottenere un indice legale.Per esempio, questa espressione sceglie l’indice di una carta a caso in un mazzo:

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

Un modo semplice per mescolare il mazzo è attraversare le carte e scambiare ogni carta con una scelta a caso. È possibile che la carta venga scambiata con se stessa, ma questo va bene. Infatti, se escludessimo questa possibilità, l’ordine delle carte sarebbe meno che interamente casuale:

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

Piuttosto che assumere che ci siano cinquantadue carte nel mazzo, otteniamo la lunghezza reale della lista e la memorizziamo in nCards.

Per ogni carta nel mazzo, scegliamo una carta a caso tra le carte che non sono ancora state mescolate. Poi scambiamo la carta corrente (i) con la carta selezionata (j). Per scambiare le carte usiamo un’assegnazione di tuple, come nella Sezione 9.2:

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

Come esercizio, riscrivete questa linea di codice senza usare un’assegnazione di sequenza.

15.8 Rimuovere e distribuire le carte

Un altro metodo che sarebbe utile per la classe Deck è removeCard, che prende una carta come argomento, la rimuove, e restituisce True se la carta era nel mazzo e False altrimenti:

class Deck:

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

L’operatore in restituisce true se il primo operando è in thesecond, che deve essere una lista o una tupla. Se il primo operando è un oggetto, Python usa il metodo __cmp__ dell’oggetto per determinare la parità con gli elementi della lista. Poiché il __cmp__ nella classeCard controlla l’uguaglianza profonda, il metodo removeCard controlla l’uguaglianza profonda.

Per dare le carte, vogliamo rimuovere e restituire la carta superiore.Il metodo pop della lista fornisce un modo conveniente per farlo:

class Deck:

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

In realtà, pop rimuove l’ultima carta della lista, quindi stiamo effettivamente distribuendo dal fondo del mazzo.

Un’altra operazione che probabilmente vorremo è la funzione booleanaisEmpty, che restituisce vero se il mazzo non contiene carte:

class Deck:

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

15.9 Glossario

codificare Rappresentare un insieme di valori usando un altro insieme di valori costruendo una mappatura tra di essi. attributo di classe Una variabile che è definita all’interno di una definizione di classe ma fuori da qualsiasi metodo. Gli attributi di classe sono accessibili da qualsiasi metodo della classe e sono condivisi da tutte le istanze della classe. accumulatore Una variabile usata in un ciclo per accumulare una serie di valori, come ad esempio concatenandoli in una stringa o aggiungendoli ad una somma corrente.

Questa è una vecchia versione del libro ora conosciuto come Think Python. Potresti preferire leggere una versione più recente.

How to Think Like a Computer Scientist

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.