Fondamenti di Programmazione Capitolo 13 Programmazione Orientata agli Oggetti Prof. Mauro Gaspari: gaspari@cs.unibo.it
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.
Ereditarietà in Python In Python si ereditano prevalentemente i metodi. Però ereditando il metodo init si ereditano anche gli attributi.
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])
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.
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 3 of Swirly Whales NB. si consiglia di non modificare attributi di classe
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.
Osservazioni Alcuni insiemi sono completamente ordinati: interi, foatingpoint. 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.
Esempio: mazzo di carte class Deck: NB. append è un metodo def init (self): che funziona sulle liste e non self.cards = [] for suit in range(4): sulle tuple. 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
Altri metodi def printdeck(self): for card in self.cards: print card seleziona un indice a caso nell'intervallo def shuffle(self): import random 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]
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
Altri metodi def popcard(self): return self.cards.pop() def isempty(self): return (len(self.cards) == 0)
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.
Come dare le carte In quale classe inserire questo metodo? Sembra più naturale in Deck class Deck :... def deal(self, hands, ncards=999): nhands = len(hands) for i in range(ncards): if self.isempty(): break card = self.popcard() hand = hands[i % nhands] hand.addcard(card) Numero di persone a cui si danno le carte # break if out of cards # take the top card # whose turn is next? # add the card to the hand
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
Oppure si può definire un metodo più specifico. NB. una volta definito class Hand(Deck) questo metodo overrides... quello della classe 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) 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 ).
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.
Osservazioni Questa classe rappresenta un gioco generico. Posso realizzare giochi specifici ereditando da questa classe.
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
>>> 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 Esempio di uso 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
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.
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()
Remove all matches class OldMaidGame(CardGame):... def removeallmatches(self): count = 0 for hand in self.hands: count = count + hand.removematches() return count
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
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
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.