Lucidi di Fondamenti di Programmazione 13

Transcript

Lucidi di Fondamenti di Programmazione 13
Fondamenti di Programmazione
Capitolo 13
Programmazione Orientata agli Oggetti
Prof. Mauro Gaspari: [email protected]
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Ereditarietà
●
L'ereditarietà (= inheritance) permette di definire nuove classi utilizzando una versione modificata di classi esistenti.
●
●
La programmazione object based estesa con il concetto di ereditarietà si chiama orientata agli oggetti (= object oriented).
In questo modo è possibile riutilizzare il codice già scritto:
–
●
aggiungere nuovi metodi ad una classe senza modificare la struttura della classe esistente.
In genere l'ereditarietà permette di ereditare i metodi e gli attributi definiti in una classe. © Mauro Gaspari ­ University of Bologna ­ [email protected]
Ereditarietà in Python
●
In Python si ereditano prevalentemente i metodi.
●
Però ereditando il metodo __init__ si ereditano anche gli attributi.
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Una classe per le carte
class Card:
suitList = ["Clubs", "Diamonds", "Hearts", "Spades"]
rankList = ["narf", "Ace", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "Jack", "Queen", "King"]
def __init__(self, suit=0, rank=0):
self.suit = suit
self.rank = rank
def __str__(self):
return (self.rankList[self.rank] + " of " +
self.suitList[self.suit])
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Class Variables
●
●
Notare che le variabili suitList e rankList non vengono inizializzate negli oggetti!
Queste variabili si chiamano anche attributi di classe (= class variables/attributes), sono definiti fuori dai metodi e si possono accedere da tutti i metodi della classe.
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Esempio: Attributi di Classe
>>> card1 = Card(1, 11)
>>> print card1
Jack of Diamonds
>>> card2 = Card(1, 3)
>>> print card2
3 of Diamonds
>>> print card2.suitList[1]
Diamonds
>>> card1.suitList[1] = "Swirly Whales"
>>> print card1
Jack of Swirly Whales
>>> print card2
NB. si consiglia di non
3 of Swirly Whales
modificare attributi di classe © Mauro Gaspari ­ University of Bologna ­ [email protected]
Confronto di carte
def __cmp__(self, other):
# check the suits
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
●
●
Il metodo __cmp__ può essere utilizzato per ottenere l'overloading
dei gli operatori condizionali su tipi definiti dall'utente.
Per convenzione __cmp__ ha due argomenti: self, other e restituisce:
­ 1: se il primo oggetto è più grande. ­ ­1: se il secondo oggetto è più grande.
­ 0: se sono uguali.
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Osservazioni
●
Alcuni insiemi sono completamente ordinati: interi, foating­
point.
●
Altri insiemi sono senza ordine, ovvero non c'èun modo sensato per stabilire un ordine (ad esempio i tipi di frutta).
●
Altri insiemi sono parzialmente ordinati: è possibile confrontare alcuni elementi ma altri no.
●
Per definire __cmp__ è opportuno che l'insieme sia completamente ordinato. Si è deciso che il segno è più importante del valore della carta.
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Esempio: mazzo di carte
class Deck:
NB. append è un metodo
def __init__(self):
che funziona sulle liste e non
self.cards = []
sulle tuple.
for suit in range(4):
for rank in range(1, 14):
self.cards.append(Card(suit, rank))
def __str__(self):
s = ""
for i in range(len(self.cards)):
s = s + " "*i + str(self.cards[i]) + "\n"
return s
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Altri metodi
def printDeck(self):
for card in self.cards:
print card
def shuffle(self):
seleziona un indice a caso import random
nell'intervallo
nCards = len(self.cards)
for i in range(nCards):
j = random.randrange(i, nCards)
self.cards[i], self.cards[j] = self.cards[j], self.cards[i]
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Altri metodi
NB. utilizzo dell'operatore in con oggetti: se il primo argomento
è un oggetto si utilizza il metodo __cmp__ per testare l'appartenenza.
def removeCard(self, card):
if card in self.cards:
self.cards.remove(card)
return 1
else:
return 0
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Altri metodi
def popCard(self):
return self.cards.pop()
def isEmpty(self):
return (len(self.cards) == 0)
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Come riutilizzare il codice per definire il concetto di mano.
class Hand(Deck):
def __init__(self, name=""):
self.cards = []
self.name = name
def addCard(self,card) :
self.cards.append(card)
NB. il metodo removeCard si eredita da Deck quindi non è
necessario ridefinirlo.
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Come dare le carte
●
In quale classe inserire questo metodo?
●
Sembra più naturale in Deck Numero di persone
a cui si danno le carte
class Deck :
...
def deal(self, hands, nCards=999):
nHands = len(hands)
for i in range(nCards):
if self.isEmpty(): break
# break if out of cards
card = self.popCard()
# take the top card
hand = hands[i % nHands]
# whose turn is next?
hand.addCard(card)
# add the card to the hand
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Come stampare una mano?
●
Si può riutilizzare il metodo definito per Deck che viene ereditato.
>>> deck = Deck()
>>> deck.shuffle()
>>> hand = Hand("frank")
>>> deck.deal([hand], 5)
>>> print hand
Hand frank contains
2 of Spades
3 of Spades
4 of Spades
Ace of Hearts
9 of Clubs
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Oppure si può definire un
metodo più specifico.
NB. una volta definito
questo metodo overrides
quello della classe Deck
class Hand(Deck)
...
def __str__(self):
s = "Hand " + self.name
if self.isEmpty():
s = s + " is empty\n"
else:
s = s + " contains\n"
return s + Deck.__str__(self)
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Si chiama il metodo della classe
Deck si può fare perché una mano
è anche un Deck
Osservazioni
●
In genere è sempre possibile usare istanze di una sottoclasse al posto di istanze della sua superclasse.
●
La notazione Classe.metodo si può utilizzare per forzare l'utilizzo di un metodo della superclasse quando c'è anche un metodo nella classe corrente (si applica il “next method”).
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Esempio: gioco di carte
class CardGame:
def __init__(self):
self.deck = Deck()
self.deck.shuffle()
NB. questo è il primo caso in cui la init fa anche un calcolo,
ovvero mescola il mazzo. © Mauro Gaspari ­ University of Bologna ­ [email protected]
Osservazioni
●
●
Questa classe rappresenta un gioco generico.
Posso realizzare giochi specifici ereditando da questa classe.
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Esempio: uomo nero
class OldMaidHand(Hand):
segno dello stesso
def removeMatches(self):
colore
count = 0
originalCards = self.cards[:]
for card in originalCards:
match = Card(3 ­ card.suit, card.rank)
if match in self.cards:
self.cards.remove(card)
self.cards.remove(match)
print "Hand %s: %s matches %s" % (self.name,card,match)
count = count + 1
return count
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Esempio di uso
>>> game = CardGame()
>>> hand = OldMaidHand("frank")
>>> game.deck.deal([hand], 13)
>>> print hand
Hand frank contains
Ace of Spades
2 of Diamonds
7 of Spades
8 of Clubs
6 of Hearts
8 of Spades
7 of Clubs
Queen of Clubs
7 of Diamonds
5 of Clubs
Jack of Diamonds
10 of Diamonds
10 of Hearts
© Mauro Gaspari ­ University of Bologna ­ [email protected]
NB. il metodo __init__ è
ereditato dalla classe Hand
Esempio di match
>>> hand.removeMatches()
Hand frank: 7 of Spades matches 7 of Clubs
Hand frank: 8 of Spades matches 8 of Clubs
Hand frank: 10 of Diamonds matches 10 of Hearts
>>> print hand
Hand frank contains
Ace of Spades
2 of Diamonds
6 of Hearts
Queen of Clubs
7 of Diamonds
5 of Clubs
Jack of Diamonds
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Classe oldMaidGame
●
●
●
oldMadeGame è una sottoclasse di cardGame.
in più si definisce un metodo play che prende come parametro il numero di giocatori.
dato che __init__ è ereditata da cardGame il nuovo gioco parte con un mazzo già mescolato.
© Mauro Gaspari ­ University of Bologna ­ [email protected]
class OldMaidGame(CardGame):
def play(self, names):
self.deck.removeCard(Card(0,12)) # remove Queen of Clubs
self.hands = []
# make a hand for each player
for name in names :
self.hands.append(OldMaidHand(name))
self.deck.deal(self.hands)
# deal the cards
print "---------- Cards have been dealt"
self.printHands()
matches = self.removeAllMatches()
# remove initial matches
print "---------- Matches discarded, play begins"
self.printHands()
turn = 0
# play until all 50 cards are matched
numHands = len(self.hands)
while matches < 25:
matches = matches + self.playOneTurn(turn)
turn = (turn + 1) % numHands
print "---------- Game is Over"
self.printHands()
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Remove all matches
class OldMaidGame(CardGame):
...
def removeAllMatches(self):
count = 0
for hand in self.hands:
count = count + hand.removeMatches()
return count
© Mauro Gaspari ­ University of Bologna ­ [email protected]
play One Turn
class OldMaidGame(CardGame):
...
def playOneTurn(self, i):
if self.hands[i].isEmpty():
return 0
neighbor = self.findNeighbor(i)
pickedCard = self.hands[neighbor].popCard()
self.hands[i].addCard(pickedCard)
print "Hand", self.hands[i].name, "picked", pickedCard
count = self.hands[i].removeMatches()
self.hands[i].shuffle()
return count
© Mauro Gaspari ­ University of Bologna ­ [email protected]
find Neighbor
class OldMaidGame(CardGame):
...
def findNeighbor(self, i):
numHands = len(self.hands)
for next in range(1,numHands):
neighbor = (i + next) % numHands
if not self.hands[neighbor].isEmpty():
return neighbor
© Mauro Gaspari ­ University of Bologna ­ [email protected]
Esempio di partita
>>> import cards
>>> game = cards.OldMaidGame()
>>> game.play(["Allen","Jeff","Chris"])
---------- Cards have been dealt
È opportuno creare un modulo con le definizioni appena date.
Per farlo è necessario salvarle in un file che supponiamo essere:
“cards.py”. Questo file si può caricare con la primitiva import
se si trova nella directory corrente.
© Mauro Gaspari ­ University of Bologna ­ [email protected]