8. Keerulisemad Pythoni võimalused
Käesolev Pythoni kursus teeb hea sissejuhatuse Pythoniga programmeerimisse, aga puhtalt aine läbimisega ennast võluriks kutsuda ei saa. Selle jaoks peab läbima selle viimase silmaringimaterjalide peatüki.
Siin peatükis vaatame, kuidas teha lihtsaid asju keerulisemalt, et vähendada üleliigse koodi kirjutamist. Õpime keerulisemaid Pythoni võimalusi, et teha elu lihtsamaks.
Ettevalmistus
Selle peatüki läbimiseks piisab Pythoni installatsioonist: midagi paigaldama ei pea.
Et peatükist täiesti aru saada, peab olema läbitud õpiku esimene ja teine osa.
Lühem if-lause
Vahepeal on vaja millegi väga lihtsa jaoks kasutada if-lauset. Näiteks, sõna kääne muutub osastavaks, kui see viitab mitmele esemele: ostukorvis on 1 toode, aga ostukorvis võib olla mitu toodet.
Sellises olukorras peame kasutama if-lauset:
if toodete_arv == 1: print("Ostukorvis on {} toode".format(toodete_arv)) else: print("Ostukorvis on {} toodet".format(toodete_arv))
Et vähendada kirjutatud koodi, saab if-lause kirjutada ühele reale:
print("Ostukorvis on {} {}".format(toodete_arv, "toode" if toodete_arv == 1 else "toodet"))
Lühema if-lause valem:
väärtus_kui_tõene if tingimus else väärtus_kui_väär
Näited:
>>> "tõene" if True else "väär" 'tõene' >>> "tõene" if False else "väär" 'väär' >>> "paaris" if 20 % 2 == 0 else "paaritu" 'paaris' >>> "paaris" if 21 % 2 == 0 else "paaritu" 'paaritu' >>> "tühi" if len([]) == 0 else "täis" 'tühi' >>> "tühi" if len([5]) == 0 else "täis" 'täis'
Proovi kirjutada lühike tingimuslause, mis annab väärtuseks "fizz"
, kui arv jagub kolmega ning arvu ise, kui see ei jagu kolmega.
Meil on järjend, mis sisaldab esemeid, mida on keelatud lennukile kaasa võtta keelatud.
keelatud = ["veepudel", "kahvel", "žilett", "ilutulestikud", "elevant"]
Kirjuta programm, mis küsib kasutajalt eset ning väljastab, kas see on keelatud või mitte nii, et programmil ei ole ühtegi taanet.
Sisesta ese: telefon telefon ei ole keelatud lennukile kaasa võtmiseks
Sisesta ese: veepudel veepudel on keelatud lennukile kaasa võtmiseks
Järjendi hõlmamine
Kui tahame luua uut järjendit ja täita seda mingite andmetega, oleme siiamaani loonud uue järjendi ning seejärel for-tsükliga sinna elemente juurde lisanud.
Näiteks, meil on järjend sõnadest ja me tahame saada järjendit nende pikkustest.
sõned = ["Python", "on", "üldotstarbeline", "interpreteeritav", "programmeerimiskeel"] pikkused = [] for sõne in sõned: pikkused.append(len(sõne)) print(pikkused) # [6, 2, 15, 16, 19]
Sama asja saab kirja panna vähema koodiga:
sõned = ["Python", "on", "üldotstarbeline", "interpreteeritav", "programmeerimiskeel"] pikkused = [len(sõne) for sõne in sõned] print(pikkused) # [6, 2, 15, 16, 19]
Seda võtet nimetatakse järjendi hõlmamiseks (ingl. k. list comprehension).
Veel üks näide: tahame saada kahe astmeid. Pikemalt saab teha nii:
kahe_astmed = [] for arv in range(10): kahe_astmed.append(2**arv) print(kahe_astmed) # [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
Järjendi hõlmamisega:
kahe_astmed = [2**arv for arv in range(10)] print(kahe_astmed) # [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
Ülesanne võib olla natuke keerulisem. Näiteks soovime kahe astmeid ainult siis, kui need lõppevad neljaga.
kahe_astmed_neljaga = [] for arv in range(23): if 2**arv % 10 == 4: kahe_astmed_neljaga.append(2**arv) print(kahe_astmed_neljaga) # [4, 64, 1024, 16384, 262144, 4194304]
Ka lisatingimusi saame panna järjendi hõlmamisse:
kahe_astmed_neljaga = [2**arv for arv in range(23) if 2**arv % 10 == 4] print(kahe_astmed_neljaga) # [4, 64, 1024, 16384, 262144, 4194304]
Proovi järjendi hõlmamisega luua järjend, mis sisaldab ainult arve, mis ei jagu arvudega 3 ega 5:
[1, 2, 4, 7, 8, 11, 13, 14, 16, 17, 19, ...]
Meil on järjend, mis sisaldab kõige sagedasemaid sõnade põhivorme (lemmasid) koos nende järkudega:
[(1, "olema"), (2, "ja"), (3, "see"), (4, "tema"), (5, "mina"), (6, "ei"), (7, "et"), (8, "kui"), (9, "mis"), (10, "ka")]
Proovi järjendi hõlmamisega luua järjend, mis jätab alles ainult sõnad:
["olema", "ja", "see", "tema", "mina", "ei", "et", "kui", "mis", "ka"]
Proovi hõlmamist ka kuhjadega, sõnastikega ja ennikutega.
Funktsioonide kasutamine parameetrites
Sorteerimine
Kui me tahame sorteerida arvude järjendit, siis saab kasutada funktsiooni sorted
või meetodit sort
.
>>> arvud = [1, 5, 9, 2, 6, 5] >>> sorted(arvud) # ei sorteeri muutujat, tagastab sorteeritud järjendi [1, 2, 5, 5, 6, 9] >>> arvud [1, 5, 9, 2, 6, 5] >>> arvud.sort() # sorteerib muutuja >>> arvud [1, 2, 5, 5, 6, 9]
Mis siis, kui meil on vaja midagi keerulisemat sorteerida? Näiteks meil on järjendisse salvestatud ennikud, mille üks element on arv. Kuidas öelda sorteerimisfunktsioonile, et sorteerida arvu järgi?
maletajad = [("Ding Liren", 3), ("Nepomnjaštši", 4), ("Caruana", 2), ("Carlsen", 1)]
Sorteerimisfunktsioonidel on parameeter key
, millele saab panna väärtuseks funktsioone. See funktsioon jooksutatakse iga järjendi elemendi peal läbi nii, et parameetriks pannakse element ning järjend sorteeritakse selle funktsiooni tagastuste põhjal. Meie järjendi puhul saaksime teha funktsiooni, mis tagastab enniku teise liikme.
def teine_liige(ennik): return ennik[1]
Nüüd peab meelde tuletama, et funktsioonid on ka muutujad, lihtsalt neid saab välja kutsuda. Seega meie funktsioon on salvestatud muutujasse teine_liige
. Sorteerime järjendi nii, et key
parameetri väärtuseks läheb see funktsioon.
>>> maletajad [('Ding Liren', 3), ('Nepomniachtchi', 4), ('Caruana', 2), ('Carlsen', 1)] >>> maletajad.sort(key=teine_liige) >>> maletajad [('Carlsen', 1), ('Caruana', 2), ('Ding Liren', 3), ('Nepomniachtchi', 4)]
Töötas! Proovime veel. Kui meil on sõnede järjend, sorteeritakse neid tavaliselt tähestiku järjekorras:
>>> sõned = ["aaaa", "b", "ccc", "ddddd", "ee"] >>> sorted(sõned) ['aaaa', 'b', 'ccc', 'ddddd', 'ee']
Aga mis siis, kui tahame neid sorteerida sõnepikkuse järjekorras? Peaksime tegema funktsiooni, mis tagastab sõne pikkuse.
def sõne_pikkus(sõne): return len(sõne)
Oota, selline funktsioon on juba olemas ja seda kasutasime isegi siin funktsioonis: len
. Paneme sorteerimisel parameetri key
väärtuseks lihtsalt selle!
>>> sorted(sõned, key=len) ['b', 'ee', 'ccc', 'aaaa', 'ddddd']
Saime isegi meeldiva ja loetava koodi.
Kui funktsiooni pole veel kirjutatud, peame selle ise defineerima, nagu tegime esimeses näites. Üsna tüütu on nii lihtsat funktsiooni ise defineerida. Võiks ju olla lihtsam viis funktsiooni defineerimiseks ilma, et peab mitu rida koodi juurde kirjutama.
Lambda-funktsioonid
Et vähendada koodi kirjutamist, saame kasutada lambda-funktsioone. Need on funktsioonid, mida saab kirjutada lühidalt ja kasutada anonüümselt ehk neid ei pea muutujasse salvestama.
Vaatame funktsiooni, mida me enne kirjutasime:
def teine_liige(ennik): return ennik[1]
Seda saab samaväärselt kirjutada lambdana:
teine_liige = lambda ennik: ennik[1]
Defineerisime just funktsiooni teine_liige
, mis võtab parameetriks muutuja ennik
ning tagastab selle teise liikme.
Saame neid samamoodi kasutada teiste funktsioonide parameetrites:
>>> maletajad [('Ding Liren', 3), ('Nepomniachtchi', 4), ('Caruana', 2), ('Carlsen', 1)] >>> maletajad.sort(key=lambda ennik: ennik[1]) >>> maletajad [('Carlsen', 1), ('Caruana', 2), ('Ding Liren', 3), ('Nepomniachtchi', 4)]
Tegelikult on sellised funktsioonid ka juba Pythoni standardteegis olemas, aga nende kasutamiseks peab ühe mooduli importima ning nendega sai teha hea sissejuhatuse lambdadesse.
Vaatame veel funktsioone, mis nõuavad parameetritena teisi funktsioone.
Funktsioon map
Üks asi, mis võib tihti ette tulla, on kogu järjendi elementidele mingi funktsiooni rakendamine. Näiteks on vaja arvusõnede järjend muuta arvude järjendiks. Selle jaoks peaks need for-tsükliga läbi käima ja kuskile uude järjendisse lisama.
sõned = ["3", "1", "4", "1", "5", "9"] arvud = [] for sõne in sõned: arvud.append(int(sõne)) print(arvud) # [3, 1, 4, 1, 5, 9]
See on päris palju koodi nii lihtsa asja jaoks. Et säästa koodi kirjutamist, on Pythonis funktsioon map
. Sellele saab parameetriteks panna ühe funktsiooni ning järjendi, et jooksutada seda funktsiooni kõikide järjendi elementide peal. See tagastab objekti, mida saab läbida for-tsükliga või muuta järjendiks funktsiooniga list
. Eelmine kood uuesti, kasutades funktsiooni map
:
sõned = ["3", "1", "4", "1", "5", "9"] arvud = list(map(int, sõned)) print(arvud) # [3, 1, 4, 1, 5, 9]
Tegime palju lühema koodiga sama asja.
Võimalik, et funktsioon nõuab mitut parameetrit, näiteks round
, mille esimene parameeter on ümardatav arv ja teine on kohtade arv, mitmeni peaks ümardama.
>>> round(3.14159265, 2) 3.14
Sel juhul saab map
parameetriteks panna 2 järjendit: esimesed parameetrid ja teised parameetrid. Ümardame näiteks ujukomaarvude järjendi arvud vastavalt teisele järjendile.
arvud = [3.14159, 2.71828, 1.41421, 6.28318, 1.61803] kohtadeni = [2, 3, 4, 3, 2] ümardatud = list(map(round, arvud, kohtadeni)) print(ümardatud) # [3.14, 2.718, 1.4142, 6.283, 1.62]
Proovi map
funktsiooniga kõik järjendi sõned suurtähestada (sõne meetod upper
).
Funktsioon filter
Vahepeal on vaja järjendeid filtreerida. Näiteks tahame järjendist jätta alles ainult positiivsed arvud. Võib jälle teha for-tsükli:
arvud = [3, -6, 1, -2, 4, -8, 1, -3] positiivsed = [] for arv in arvud: if arv > 0: positiivsed.append(arv) print(positiivsed) # [3, 1, 4, 1]
Seda saab lühemalt teha funktsiooniga filter
. See funktsioon nõuab parameetritesse funktsiooni ning järjendit. Tagastatud järjendisse jäetakse alles ainult need elemendid, mille peal funktsioon tagastab True
. Eelmine koodijupp funktsiooniga filter
:
arvud = [3, -6, 1, -2, 4, -8, 1, -3] positiivsed = list(filter(lambda arv: arv > 0, arvud)) print(positiivsed) # [3, 1, 4, 1]
Seda saab ka kasutada sõnede peal. Näiteks saame alles jätta kõik tähed ja tühikud tekstis ilma muude sümboliteta:
lause = "„Funktsionaalprogrammeerimine on lihtne,” ütles mitte keegi." print("".join(filter(lambda x: x.isalpha() or x == " ", lause))) # Funktsionaalprogrammeerimine on lihtne ütles mitte keegi
Kuna filter
tagastab järjendilaadse objekti, peab sõne saamiseks selle kokku liitma sõne meetodiga join
.
Kasuta filter
funktsiooni, et saada kätte salajane sõnum järgnevast tekstist:
XXXSXXXXXXAXXXXXXLXXXXXXAXXXXXXJXXXXXXAXXXXXXNXXXXXXEXXXXXX XXXXXXSXXXXXXÕXXXXXXNXXXXXXUXXXXXXMXXX
Veel üks huvitav funktsioon on reduce.
Funktsioon zip
Kui meil on mitu järjendit sarnaste andmetega ja me tahame neid kokku pakkida, siis peaksime need for-tsükliga indekseerima:
eesnimed = ["Jüri", "Mailis", "Tõnis", "Tanel", "Urmas"] perenimed = ["Ratas", "Reps", "Lukas", "Kiik", "Reinsalu"] ministrid = [] for i in range(len(eesnimed)): ministrid.append((eesnimed[i], perenimed[i])) print(ministrid) # [('Jüri', 'Ratas'), ('Mailis', 'Reps'), ('Tõnis', 'Lukas'), ('Tanel', 'Kiik'), ('Urmas', 'Reinsalu')]
Funktsioon zip
lubab seda palju lihtsamalt teha:
eesnimed = ["Jüri", "Mailis", "Tõnis", "Tanel", "Urmas"] perenimed = ["Ratas", "Reps", "Lukas", "Kiik", "Reinsalu"] print(list(zip(eesnimed, perenimed))) # [('Jüri', 'Ratas'), ('Mailis', 'Reps'), ('Tõnis', 'Lukas'), ('Tanel', 'Kiik'), ('Urmas', 'Reinsalu')]
See on kasulik, kui meil on näiteks erinevaid andmeid samade asjade kohta ja me tahame neid kokku liita.
Funktsioonile zip võib sisse sööta lõpmatu arvu parameetreid:
>>> list(zip([1], [2], [3], [4], [5])) [(1, 2, 3, 4, 5)]
Seega, kui me sisestame saadud paarid zip
funktsiooni parameetritesse, siis saame tagasi eesnimede ja perenimede järjendid:
>>> list(zip(('Jüri', 'Ratas'), ('Mailis', 'Reps'), ('Tõnis', 'Lukas'), ('Tanel', 'Kiik'), ('Urmas', 'Reinsalu'))) [('Jüri', 'Mailis', 'Tõnis', 'Tanel', 'Urmas'), ('Ratas', 'Reps', 'Lukas', 'Kiik', 'Reinsalu')]
Aga kui meil on paarid järjendi sees, siis kuidas kõiki elemente funktsiooni parameetritesse saada?
Et järjendit funktsiooni parameetritena kasutada, tuleb järjend panna parameetriks ning selle ette kirjutada tärn. Proovime algul lihtsama näitega: paneme funktsiooni round parameetriks kaheliikmelise järjendi.
>>> round(*[3.14159265368, 2]) 3.14
Proovime sama võtet zip
funktsiooni peal:
>>> ministrid = [('Jüri', 'Ratas'), ('Mailis', 'Reps'), ('Tõnis', 'Lukas'), ('Tanel', 'Kiik'), ('Urmas', 'Reinsalu')] >>> list(zip(*ministrid)) [('Jüri', 'Mailis', 'Tõnis', 'Tanel', 'Urmas'), ('Ratas', 'Reps', 'Lukas', 'Kiik', 'Reinsalu')]
Töötab! Oleme edukalt järjendid kokku ja lahti pakkinud.
Kokkuvõte
Kasutasime erinevaid võtteid, et vähendada üleliigset koodi kirjutamist. Võibolla tundub tõesti, et see teeb lihtsaid asju keerulisemaks, aga piisava harjutamisega muutuvad need lihtsamaks ning pikema koodi kirjutamise soov kaob ära.
Kuigi õpitud võtted lubavad lahendada peaaegu kõiki ülesandeid ühe reaga, peab meelde tuletama, et Pythoni ametlik stiiliõpetus nõuab, et read on maksimaalselt 79 tähemärgi pikkused. Päris programmides tuleks kood mõistlikult paigutada mitmele reale.
Paljud siin peatükis rakendatud funktsioonid on seotud funktsionaalprogrammeerimisega. Sellesse teemasse süveneb aine "Programmeerimiskeeled" (MTAT.03.006) keeltega Haskell ja Scala.
Enesekontrolliküsimused
Ülesanded
1. Antud on järjendid nimedest, matriklinumbritest ja keskmistest hinnetest.
nimed = ["Jaan", "Martin", "Katrin", "Margus", "Tiiu", "Jüri", "Anna", "Sirje", "Ülle", "Kristjan", "Anne", "Julia", "Andres", "Marina", "Rein", "Aivar", "Tiina", "Urmas", "Toomas", "Maria"] matriklinumbrid = ["B69310", "B28761", "B79826", "B14207", "B16122", "B61619", "B14708", "B59695", "B50264", "B32270", "B88961", "B73302", "B29125", "B87856", "B48386", "B22124", "B52814", "B80444", "B56290", "B57742"] hinded = [4.15, 3.61, 3.59, 3.28, 4.5, 4.6, 4.16, 3.83, 4.97, 4.26, 4.92, 3.93, 3.64, 4.12, 4.03, 4.75, 4.38, 4.65, 3.09, 4.04]
Kirjuta ühe reaga funktsioon, mis tagastab ennikud nendest, kelle keskmine hinne on vähemalt 4.6, sorteeritud keskmise hinde järgi kahanevalt.
>>> stipisaajad(nimed, matriklinumbrid, hinded) [('Ülle', 'B50264', 4.97), ('Anne', 'B88961', 4.92), ('Aivar', 'B22124', 4.75), ('Urmas', 'B80444', 4.65), ('Jüri', 'B61619', 4.6)]
2. Tekstifaili evolutsioon.txt
on salvestatud tekst imelikul kujul: tekst algab ülevalt paremalt ja liigub alla. Kirjuta ühe reaga funktsioon korrasta
, mis võtab parameetrina failinime ning tagastab õiges järjekorras loetud teksti. Lahendamiseks piisab siin peatükis kasutatud funktsioonidest, v.a faili sisselugemine. Faili sulgemine siin ülesandes pole tähtis.
nne mE g,raon , fnsd audtl enl e vd mbs o hoes laasa vrvtuf eee to d wir .bbofm eenus iedl
>>> with open("ül1.py") as f: print(len(f.readlines())) 1 >>> %Run 'ül1.py' >>> korrasta("evolutsioon.txt") 'Endless forms most beautiful and most wonderful have been, and are being, evolved. '