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]