Ez a könyv egy régebbi változata, amely most Think Python néven ismert. Lehet, hogy inkább egy frissebb változatot szeretne olvasni.
How to Think Like a Computer Scientist |
15. fejezet
15. fejezet.1 Kompozíció
Mostanra már több példát is láttál a kompozícióra.Az egyik első példa a metódushívás használata volt egy kifejezés részeként. Egy másik példa az utasítások egymásba ágyazott szerkezete;egy if utasítást elhelyezhetünk egy while cikluson belül, egy másik if utasításon belül, és így tovább.
Azt követően, hogy láttuk ezt a mintát, és tanultunk a listákról és objektumokról,nem kell meglepődnünk azon, hogy létrehozhatunk objektumokból álló listákat. Létrehozhatsz olyan objektumokat is, amelyek listákat tartalmaznak (mint attribútumokat); létrehozhatsz listákat, amelyek listákat tartalmaznak; létrehozhatsz objektumokat, amelyek objektumokat tartalmaznak; és így tovább.
Ebben és a következő fejezetben megnézünk néhány példát ezekre a kombinációkra, a Card objektumok példáján keresztül.
15.2 Kártyaobjektumok
Ha nem ismeri a közönséges játékkártyákat, akkor most lenne itt az ideje, hogy beszerezzen egy paklit, különben ennek a fejezetnek nem sok értelme lesz. ötvenkét kártya van egy pakliban, amelyek mindegyike a négyféle és a tizenhárom rang valamelyikéhez tartozik. A színek a pikk, a kőr, a káró és a bubi (a bridzsben csökkenő sorrendben). A rangok az ász, 2, 3, 4, 5, 6, 7, 8, 9, 10, bubi, dáma és király. Attól függően, hogy milyen játékot játszunk, az Ász rangja lehet magasabb, mint a Király vagy alacsonyabb, mint a 2.
Ha egy új objektumot akarunk definiálni egy játékkártya ábrázolására, nyilvánvaló, hogy mik legyenek az attribútumok: rang és szín. Nem olyan nyilvánvaló, hogy milyen típusúak legyenek az attribútumok. Az egyik lehetőség, hogy olyan szavakat tartalmazó karakterláncokat használunk, mint például “Spade” a színekre és “Queen” a rangokra. Az egyik probléma ezzel a megvalósítással az, hogy nem lenne könnyű összehasonlítani a kártyákat, hogy lássuk, melyiknek van magasabb rangja vagy színe.
Egy másik lehetőség, hogy egész számokat használunk a rangok és színek kódolására.A “kódolás” alatt nem azt értjük, amit egyesek gondolnak, vagyis hogy titkosítjuk vagy titkos kódra fordítjuk. Egy informatikus a “kódolás” alatt azt érti, hogy “leképezést definiálok egy számsorozat és a reprezentálni kívánt elemek között”. Például:
Pikk | -> | 3 | |
Szívek | -> | 2 | |
Diamant | -> | -> | 1 |
Klubok | -> | 0 |
Egy nyilvánvaló jellemzője ennek a leképezésnek, hogy az öltönyök sorrendben egész számokra képeznek le, így az öltönyöket egész számok összehasonlításával tudjuk összehasonlítani. A rangok leképezése meglehetősen nyilvánvaló; minden egyes numerikus rangot a megfelelő egész számhoz rendelünk, és az arckártyák esetében:
Bubi | -> | 11 |
Dáma | -> | 12 |
Király | -> | – |
-> | 13 | |
Azért használunk matematikai jelölést ezekre a leképezésekre, mert nem részei a Python programnak. A programtervezés részei, de soha nem jelennek meg explicit módon a kódban. A Card típus osztálydefiníciója így néz ki:
class Card:
def __init__(self, suit=0, rank=2):
self.suit = suit
self.rank = rank
A szokásos módon megadunk egy inicializálási metódust, amely minden attribútumhoz egy opcionális paramétert vesz fel. A suit alapértelmezett értéke0, ami a Treffet jelenti.
Kártya létrehozásához meghívjuk a Card konstruktort a kívánt kártya suitjával és rangjával.
threeOfClubs = Card(3, 1)
A következő részben kiderítjük, milyen kártyát készítettünk.
15.3 Az osztály attribútumai és a __str__ metódus
Azért, hogy a Card objektumokat könnyen olvasható módon nyomtassuk ki, az egész számkódokat szavakra akarjuk leképezni. Ennek természetes módja a karakterláncokból álló listák használata. Ezeket a listákat az osztálydefiníció elején osztályattribútumokhoz rendeljük:
class Card:
suitList =
rankList =
#init method omitted
def __str__(self):
return (self.rankList + ” of ” +
self.suitList)
Az osztály attribútumát bármely metóduson kívül definiáljuk, és az osztály bármelyik metódusából elérhetjük.
A __str__-n belül a suitList és rankList segítségével a suit és rank numerikus értékeit leképezhetjük karakterláncokra.Például a self.suitList kifejezés azt jelenti, hogy “használjuk a self objektum suit attribútumát indexként a suitList nevű osztályattribútumhoz, és válasszuk ki a megfelelő karakterláncot”.”
A rankList első elemében lévő “narf” azért van, hogy helyőrzőként szolgáljon a lista nulladik elemének, amelyet soha nem szabad használni. Az egyetlen érvényes ranglista az 1-től 13-ig terjedő. Ezaz elpazarolt elem nem teljesen szükséges. Kezdhettük volna 0-val is, mint általában, de kevésbé zavaró, ha a 2-t 2-ként, a 3-at 3-ként és így tovább kódoljuk.
Az eddigi módszerekkel létrehozhatunk és kinyomtathatunk kártyákat:
>>> card1 = Card(1, 11)
>>>> print card1
Jack of Diamonds
Az osztály attribútumai, mint például a suitList, minden Cardobjectben közösek. Ennek előnye, hogy bármelyik Cardobject segítségével elérhetjük az osztály attribútumait:
>>>> card2 = Card(1, 3)
>>>> print card2
3 káró
>>>> print card2.suitList
Diamonds
Hátránya, hogy ha egy osztály attribútumát módosítjuk, az az osztály minden példányára kihat. Ha például úgy döntünk, hogy a “Jack of Diamonds”-t valójában “Jack of Swirly Whales”-nek kellene hívni, akkor ezt tehetjük:
>>>> card1.suitList = “Swirly Whales”
>>>> print card1
Jack of Swirly Whales
A probléma az, hogy az összes káró éppenSwirly Whales lett:
>>> print card2
3 of Swirly Whales
Az osztályok attribútumainak módosítása általában nem jó ötlet.
15.4 Kártyák összehasonlítása
A primitív típusok esetében léteznek feltételes operátorok(<, >, ==, stb.)amelyek összehasonlítanakértékeket, és meghatározzák, hogy az egyik nagyobb, kisebb vagy egyenlő egy másiknál. A felhasználó által definiált típusok esetében felülbírálhatjuk a beépített operátorok viselkedését egy__cmp__ nevű metódus megadásával. A konvenció szerint a __cmp__ két paraméterrel rendelkezik, self és other, és 1-et ad vissza, ha az első objektum nagyobb, -1-et, ha a második objektum nagyobb, és 0-t, ha egyenlőek egymással.
Egyes típusok teljesen rendezettek, ami azt jelenti, hogy bármely két elemet össze lehet hasonlítani, és meg lehet mondani, melyik a nagyobb. Például az egészekés a lebegőpontos számok teljesen rendezettek. Néhány halmaz rendezetlen, ami azt jelenti, hogy nincs értelmes módja annak, hogy megmondjuk, hogy az egyik elem nagyobb, mint a másik. Például a gyümölcsök rendezetlenek, ezért nem lehet összehasonlítani az almát és a narancsot.
A játékkártyák halmaza részben rendezett, ami azt jelenti, hogy néha össze lehet hasonlítani a kártyákat, néha nem. Például tudod, hogy a treff 3-as magasabb, mint a treff 2-es, és a káró 3-as magasabb, mint a treff 3-as. De melyik a jobb, a Treff 3-as vagy a Káró 2-es? Az egyiknek magasabb a rangja, de a másiknak magasabb a színe.
A kártyák összehasonlítása érdekében el kell döntened, hogy melyik a fontosabb, a rang vagy a szín. Hogy őszinte legyek, a választás önkényes. A választás kedvéért azt mondjuk, hogy a szín a fontosabb, mert egy új pakli kártyát úgy rendezünk, hogy az összes treff együtt van, aztán az összes káró, és így tovább.
Ezzel a döntéssel megírhatjuk a __cmp__:
def __cmp__(self, other):
# ellenőrizd a színeket
if self.suit > other.suit: return 1
if self.suit < other.suit: return -1
# a színek megegyeznek… ellenőrizd a rangokat
if self.rank > other.rank: return 1
if self.rank < other.rank: return -1
# a rangok megegyeznek… ez döntetlen
return 0
Ebben a sorrendben az ászok alacsonyabbnak tűnnek, mint a kettesek (2-esek).
gyakorlatként módosítsuk a __cmp__-t úgy, hogy az ászok rangsorolása magasabb legyen, mint a királyoké.
15.5 Paklik
Most, hogy vannak objektumaink a kártyák ábrázolására, a következő logikus lépés, hogy definiáljunk egy osztályt a paklik ábrázolására. Természetesen a pakli kártyákból áll, így minden Deck objektum attribútumként tartalmazni fog egy kártyalistát.
A következő a Deck osztály definíciója. Az inicializálási metódus létrehozza az attribútumkártyákat és létrehozza az ötvenkét kártyából álló szabványos készletet:
class Deck:
def __init__(self):
self.cards =
for suit in range(4):
for rank in range(1, 14):
self.cards.append(Card(suit, rank))
A pakli feltöltésének legegyszerűbb módja egy egymásba ágyazott ciklus. A külső hurok 0-tól 3-ig sorolja fel a színeket, a belső hurok 1-től 13-ig sorolja fel a rangokat. Mivel a külső ciklus négyszer, a belső ciklus pedig tizenháromszor iterál, a test összesen ötvenkétszer hajtódik végre (tizenháromszor négy). Minden egyes iteráció létrehoz egy új Card példányt az aktuális színnel és ranggal,és hozzáfűzi ezt a kártyát a cards listához.
Az append módszer listákon működik, de természetesen nem tuplikon.
15.6 A pakli nyomtatása
A szokásos módon, amikor egy új típusú objektumot definiálunk, szeretnénk egy metódust, amely kiírja az objektum tartalmát.Egy Deck kinyomtatásához végigmegyünk a listán, és minden egyes kártyát kinyomtatunk:
class Deck:
…
def printDeck(self):
for card in self.cards:
print card
Itt, és mostantól kezdve az ellipszis (…) jelzi, hogy kihagytuk az osztály többi metódusát.
A printDeck alternatívájaként írhatunk egy __str__ metódust a Deck osztályhoz. A __str__ előnye, hogy rugalmasabb. Ahelyett, hogy csak az objektum tartalmát nyomtatná ki, egy stringreprezentációt generál, amelyet a program más részei a nyomtatás előtt manipulálhatnak, vagy későbbi felhasználásra tárolhatnak.
Itt van az __str__ egy olyan változata, amely egy Deck stringreprezentációját adja vissza.Egy kis pezsgés érdekében a kártyákat kaszkádszerűen rendezi el, ahol minden kártya egy szóközzel jobban van behúzva, mint az előző kártya:
class Deck:
…
def __str__(self):
s = “”
for i in range(len(self.cards)):
s = s + “”*i + str(self.cards) + “\n”
return s
Ez a példa több funkciót is demonstrál. Először is, ahelyett, hogy végigjárnánk a self.cards állományt és minden egyes kártyát egy változóhoz rendelnénk,az i-t ciklusváltozóként és a kártyák listájának indexeként használjuk.
Másodszor, a string szorzás operátort használjuk arra, hogy minden egyes kártyát eggyel több szóközzel vonjunk be, mint az előzőt. A””*i kifejezés az i aktuális értékével megegyező számú szóközt eredményez.
Harmadszor, ahelyett, hogy a print parancsot használnánk a kártyák kinyomtatására,az str függvényt használjuk. Egy objektum átadása argumentumként azstr-nek egyenértékű az __str__ metódus meghívásával az objektumon.
Végül az s változót használjuk akkumulátorként.Kezdetben s az üres karakterlánc. A cikluson keresztül minden egyes alkalommal új karakterláncot generálunk, és az új értéket a sto régi értékével összekapcsolva kapjuk meg az új értéket. Amikor a ciklus véget ér, s tartalmazza a Deck teljes string reprezentációját, amely így néz ki:
>>>> deck = Deck()
>>>> print deck
Ace of Clubs
2 of Clubs
3 of Clubs
4 of Clubs
5 of Clubs
6 of Treff
Treff7
Treff8
Treff9
Treff10
Treff bubi
Treff dáma
Treff király
Káró ász
És így tovább. Bár az eredmény 52 sorban jelenik meg, ez egy hosszú, újsorokat tartalmazó karakterlánc.
15.7 A pakli megkeverése
Ha egy pakli tökéletesen meg van keverve, akkor bármelyik kártya egyforma valószínűséggel jelenik meg a pakliban bárhol, és bármelyik hely a pakliban egyforma valószínűséggel tartalmaz bármelyik kártyát.
A pakli megkeveréséhez a random modul randrange függvényét fogjuk használni. Két egész szám argumentummal,a és b, a randrange véletlen egész számot választ ki az a <= x < b tartományban. Mivel a felső korlát szigorúan kisebb, mint b, használhatjuk a lista hosszát a második argumentumként, és garantáltan legális indexet kapunk.Ez a kifejezés például egy véletlenszerű kártya indexét választja ki egy pakliban:
random.randrange(0, len(self.cards))
Egyszerű módja a pakli megkeverésének, ha végigjárjuk a kártyákat és minden kártyát felcserélünk egy véletlenszerűen kiválasztottal. Lehetséges, hogya kártya önmagával lesz felcserélve, de ez rendben van. Valójában, ha ezt a lehetőséget kizárnánk, a kártyák sorrendje nem lenne teljesen véletlenszerű:
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
Ahelyett, hogy feltételeznénk, hogy ötvenkét kártya van a pakliban, megkapjuk a lista tényleges hosszát, és eltároljuk az nCards-ban.
A pakli minden egyes kártyájához választunk egy véletlenszerű kártyát a még meg nem kevert kártyák közül. Ezután kicseréljük az aktuális kártyát (i) a kiválasztott kártyával (j). A kártyák cseréjéhez tuple hozzárendelést használunk, mint a 9.2. szakaszban:
self.cards, self.cards = self.cards, self.cards
gyakorlatként írjuk át ezt a kódsort szekvencia hozzárendelés nélkül.
15.8 Kártyák eltávolítása és kiosztása
Egy másik metódus, amely hasznos lehet a Deck osztály számára, a removeCard, amely egy kártyát vesz argumentumként, eltávolítja azt, és True-t ad vissza, ha a kártya a pakliban volt, és False-t egyébként:
class Deck:
…
def removeCard(self, card):
if card in self.cards:
self.cards.remove(card)
return True
else:
return False
Az in operátor true-t ad vissza, ha az első operandus a thesecond-ban van, amelynek listának vagy tuple-nak kell lennie. Ha az első operandus egyobjektum, a Python az objektum __cmp__ módszerét használja a lista elemeivel való egyenlőség meghatározására. Mivel a __cmp__ aCard osztályban a mély egyenlőséget ellenőrzi, a removeCard metódus is a mély egyenlőséget ellenőrzi.
A kártyák kiosztásához a legfelső kártyát akarjuk eltávolítani és visszaadni.A lista pop metódusa kényelmes módot biztosít erre:
class Deck:
…
def popCard(self):
return self.cards.pop()
A pop valójában a lista utolsó lapját távolítja el, így a pakli aljáról osztunk.
Egy további művelet, amire valószínűleg szükségünk lesz, a boolean függvényisEmpty, amely igazat ad vissza, ha a pakli nem tartalmaz lapokat:
class Deck:
…
def isEmpty(self):
return (len(self.cards) == 0)
15.9 Fogalomtár
encode Egy értékkészletet egy másik értékkészlet segítségével ábrázolni úgy, hogy leképezést készítünk közöttük. class attribute Egy változó, amely egy osztálydefiníción belül, de bármely metóduson kívül van definiálva. Az osztály attribútumai az osztály bármely metódusából elérhetők, és az osztály minden példánya számára közösek. accumulator Egy változó, amelyet egy ciklusban értékek sorozatának felhalmozására használnak, például egy stringben való összefűzéssel vagy egy futó összeghez való hozzáadással.
Ez egy régebbi változata a most Think Python néven ismert könyvnek. Lehet, hogy inkább egy újabb verziót szeretne olvasni.
How to Think Like a Computer Scientist |