Osnove programiranja v jeziku Python za neračunalničarje Miha Moškon 2020 Univerza v Ljubljani Fakulteta za računalništvo in informatiko Kataložni zapis o publikaciji (CIP) pripravili v Narodni in univerzitetni knjižnici v Ljubljani COBISS.SI-ID=31230723 ISBN 978-961-7059-02-1 (pdf) Copyright c 2020 Založba UL FRI. All rights reserved. Elektronska izdaja knjige je na voljo na URL: http://zalozba.fri.uni-lj.si/moskon2020.pdf Recenzenta: prof. dr. Janez Demšar, doc. dr. Aleksander Sadikov Založnik: Založba UL FRI, Ljubljana Izdajatelj: UL Fakulteta za računalništvo in informatiko, Ljubljana Urednik: prof. dr. Franc Solina Kazalo 1 Uvod 5 1.1 Ra unalniötvo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.2 Zakaj se u iti programiranja . . . . . . . . . . . . . . . . . . . . . . 5 1.3 Ra unalniötvo in kemija (in druge vede) . . . . . . . . . . . . . . . 6 1.4 Kaj je programiranje? . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.5 Zakaj Python? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.6 Kaj me aka na koncu knjige? . . . . . . . . . . . . . . . . . . . . . 8 1.7 Nekaj navodil za branje knjige . . . . . . . . . . . . . . . . . . . . . 8 2 Spoznavanje z okoljem 11 2.1 Izbira in namestitev okolja . . . . . . . . . . . . . . . . . . . . . . . 11 2.2 Okolje IDLE . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.3 Ukazna vrstica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.4 Podatkovni tipi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.5 Funkcije . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.6 Spremenljivke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.7 Pisanje programov . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 2.8 Funkcija print . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.9 Funkcija input . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 2.10 Pretvarjanje med podatkovnimi tipi . . . . . . . . . . . . . . . . . . 20 2.11 Pisanje komentarjev . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3 Pogojni stavek 23 3.1 Zakaj pogojni stavki? . . . . . . . . . . . . . . . . . . . . . . . . . . 23 3.2 Osnovna oblika stavka if . . . . . . . . . . . . . . . . . . . . . . . 24 3.3 Kaj je pogoj? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24 3.3.1 Primerjalni operatorji in podatkovni tip bool . . . . . . . . 25 3.3.2 Operatorja vsebovanosti . . . . . . . . . . . . . . . . . . . . 27 3.3.3 Zdruûevanje rezultatov primerjanja . . . . . . . . . . . . . . 28 3.4 Veja else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 3.5 Veja elif in gnezdenje stavkov if . . . . . . . . . . . . . . . . . . 31 iii iv Kazalo 4 Zanka while 35 4.1 Kaj so zanke? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.2 Zanka while . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 4.3 ätetje z zanko while . . . . . . . . . . . . . . . . . . . . . . . . . . 36 4.4 Iskanje najve jega skupnega delitelja . . . . . . . . . . . . . . . . . 37 4.5 Stavek += . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.6 Neskon na zanka . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.7 Stavek break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 4.8 Veja else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42 5 Seznami in metode 45 5.1 Sekven ni podatkovni tipi . . . . . . . . . . . . . . . . . . . . . . . 45 5.2 Kaj so seznami? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 5.3 Indeksiranje seznamov . . . . . . . . . . . . . . . . . . . . . . . . . 46 5.4 Operatorji nad seznami . . . . . . . . . . . . . . . . . . . . . . . . . 47 5.5 Spreminjanje in brisanje elementov seznama . . . . . . . . . . . . . 49 5.6 Vgrajene funkcije nad seznami . . . . . . . . . . . . . . . . . . . . . 49 5.7 Metode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 5.8 Dodajanje elementov . . . . . . . . . . . . . . . . . . . . . . . . . . 50 5.9 Branje seznamov iz ukazne vrstice . . . . . . . . . . . . . . . . . . . 52 5.10 Sortiranje seznamov . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 5.11 Seznami seznamov . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 5.12 Generiranje seznamov s funkcijo range . . . . . . . . . . . . . . . . 57 5.13 Rezine . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60 5.14 Indeksiranje nizov . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 5.15 Sprehajanje ez sezname . . . . . . . . . . . . . . . . . . . . . . . . 64 6 Zanka for 65 6.1 Sprehajanje ez sezname z zanko for . . . . . . . . . . . . . . . . . 65 6.2 Sprehajanje s funkcijo range in sprehajanje ez indekse . . . . . . . 67 6.3 Sprehajanje ez elemente ali ez indekse? . . . . . . . . . . . . . . . 67 6.4 Spreminjanje elementov seznama z zanko for . . . . . . . . . . . . 69 6.5 Zanka for ali zanka while? . . . . . . . . . . . . . . . . . . . . . . 70 6.6 Stavek break . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 6.7 Veja else . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72 6.8 Gnezdenje zank . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 6.9 Izbirni argumenti funkcij in izbirni argumenti funkcije print . . . . 76 7 Uporaba in pisanje funkcij 79 7.1 Kaj so funkcije in zakaj so uporabne? . . . . . . . . . . . . . . . . . 79 7.2 Kako definiramo funkcijo? . . . . . . . . . . . . . . . . . . . . . . . 80 7.3 Globalni imenski prostor . . . . . . . . . . . . . . . . . . . . . . . . 81 Kazalo v 7.4 Kaj se zgodi ob klicu funkcije in lokalni imenski prostor . . . . . . . 81 7.5 Vsaka funkcija vra a rezultat . . . . . . . . . . . . . . . . . . . . . 87 7.6 Izbirni argumenti . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90 8 Uporaba in pisanje modulov 93 8.1 Kaj so moduli? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 8.2 Uporaba modulov . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 8.3 Definicija in uporaba lastnih modulov . . . . . . . . . . . . . . . . . 95 8.4 Nameö anje novih modulov . . . . . . . . . . . . . . . . . . . . . . 95 9 Spremenljivost podatkovnih tipov in terke 97 9.1 Kaj je spremenljivost? . . . . . . . . . . . . . . . . . . . . . . . . . 97 9.2 Kaj se zgodi ob prirejanju spremenljivk? . . . . . . . . . . . . . . . 98 9.3 Kaj se zgodi ob spreminjanju vrednosti spremenljivk? . . . . . . . . 98 9.4 Ali funkcije spreminjajo vrednosti svojim argumentom? . . . . . . . 100 9.5 Terke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 9.6 Uporaba terk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 9.7 Seznami terk in razpakiranje elementov terk . . . . . . . . . . . . . 103 9.8 Pakiranje seznamov v sezname terk . . . . . . . . . . . . . . . . . . 104 9.9 Zahteva po nespremenljivosti . . . . . . . . . . . . . . . . . . . . . . 106 10 Slovarji 109 10.1 Zakaj slovarji? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 10.2 Kako uporabljamo slovarje? . . . . . . . . . . . . . . . . . . . . . . 110 10.3 Iskanje vrednosti . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110 10.4 Dodajanje in spreminjanje vrednosti . . . . . . . . . . . . . . . . . 111 10.5 Brisanje vrednosti . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113 10.6 Klju i in vrednosti . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 10.7 Imenski prostor in slovarji . . . . . . . . . . . . . . . . . . . . . . . 116 11 Mnoûice 117 11.1 In öe mnoûice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 11.2 Uporaba mnoûic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 11.3 Omejitve pri uporabi mnoûic . . . . . . . . . . . . . . . . . . . . . . 118 11.4 Osnovne operacije nad mnoûicami . . . . . . . . . . . . . . . . . . . 118 11.5 Presek, unija in razlika . . . . . . . . . . . . . . . . . . . . . . . . . 119 11.6 Metode mnoûic: dodajanje in brisanje elementov . . . . . . . . . . . 120 11.7 Zgled uporabe mnoûic . . . . . . . . . . . . . . . . . . . . . . . . . 121 12 Oblikovanje nizov 129 12.1 Delo z nizi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 12.2 Iskanje podnizov . . . . . . . . . . . . . . . . . . . . . . . . . . . . 131 vi Kazalo 12.3 Odstranjevanje in spreminjanje (pod)nizov . . . . . . . . . . . . . . 132 12.4 Razdruûevanje in zdruûevanje nizov . . . . . . . . . . . . . . . . . . 133 12.5 Odstranjevanje praznega prostora . . . . . . . . . . . . . . . . . . . 134 12.6 Prilagajanje izpisa in formatiranje nizov . . . . . . . . . . . . . . . 136 12.7 Formatiranje nizov in f-Strings . . . . . . . . . . . . . . . . . . . . . 137 13 Delo z datotekami 139 13.1 Zakaj pisati v datoteke in brati iz njih? . . . . . . . . . . . . . . . . 139 13.2 Kaj je datoteka? . . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 13.3 Tekstovne datoteke . . . . . . . . . . . . . . . . . . . . . . . . . . . 140 13.4 Odpiranje datoteke . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 13.5 Branje datoteke . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 142 13.6 Pisanje v datoteko . . . . . . . . . . . . . . . . . . . . . . . . . . . 146 13.7 Kodiranje znakov . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 13.8 Datoteke CSV . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154 14 Vizualizacija podatkov 161 14.1 Knjiûnica Matplotlib in njena namestitev . . . . . . . . . . . . . . . 161 14.2 Funkciji plot in show . . . . . . . . . . . . . . . . . . . . . . . . . . 161 14.3 Dodajanje oznak . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 14.4 äe malo prilagajanja oznak . . . . . . . . . . . . . . . . . . . . . . . 166 14.5 Ostale prilagoditve izrisa . . . . . . . . . . . . . . . . . . . . . . . . 170 14.6 Ostali tipi grafov . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 14.7 Risanje matemati nih funkcij . . . . . . . . . . . . . . . . . . . . . 175 15 Knjiûnica NumPy in hitro ra unanje 181 15.1 NumPy in ndarray . . . . . . . . . . . . . . . . . . . . . . . . . . . 181 15.2 Aritmeti ni operatorji in strutkura ndarray . . . . . . . . . . . . . 182 15.3 Primerjalni operatorji, indeksiranje s seznami in filtiranje . . . . . . 183 15.4 Generiranje strukture ndarray . . . . . . . . . . . . . . . . . . . . . 183 15.5 Funkcije nad strukturo ndarray . . . . . . . . . . . . . . . . . . . . 184 15.6 Ve dimenzij . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 15.7 Ostale uporabne funkcije . . . . . . . . . . . . . . . . . . . . . . . . 187 15.8 Posebne vrednosti . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 15.9 Uvaûanje vrednosti in omejitve strukture ndarray . . . . . . . . . . 189 15.10Omejitve knjiûnice NumPy . . . . . . . . . . . . . . . . . . . . . . . 192 16 Knjiûnica pandas 195 16.1 Delo s podatki . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 16.2 Knjiûnica pandas in dataframe . . . . . . . . . . . . . . . . . . . . 195 16.3 Indeksiranje tabel . . . . . . . . . . . . . . . . . . . . . . . . . . . . 197 16.4 Filtriranje vrednosti . . . . . . . . . . . . . . . . . . . . . . . . . . 199 Kazalo vii 16.5 Risanje grafov . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 200 16.6 Izvoz podatkov . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 203 17 Okolje Jupyter 205 17.1 Interaktivni zvezki . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 17.2 Celice, tipi celic in njihovo poganjanje . . . . . . . . . . . . . . . . . 205 Predgovor Pred vami je knjiga, ki je namenjena u enju osnov programiranja v programskem jeziku Python. Kljub temu, da je bila knjiga napisana za ötudente, ki posluöajo enega izmed predmetov Osnove programiranja na ötudijih, ki se izvajajo na Fakulteti za kemijo in kemijsko tehnologijo Univerze v Ljubljani, je knjiga primerna za vsakogar, ki bi se rad nau il osnov programiranja. Nekateri zgledi so sicer usmerjeni na podro je kemije oziroma biokemije, celotna razlaga pa je sploöna in primerna za kogarkoli. Knjiga je predvsem primerna za ötudente, ki nimajo izkuöenj s programiranjem in ra unalniötvo ni njihov osrednji ötudij. Veliko veselja pri programiranju vam ûelim! Miha Moökon, 2020 1 Zahvala Zahvalil bi se vsem asistentom in svojim predhodnikom, ki so s svojim delom posredno ali neposredno predmeta Osnove programiranja in Osnove programiranja – izbirni pripeljali do oblike, v kateri se danes izvajata. Prav tako bi se rad zahvalil vsem ötudentom za njihove pripombe, za vse javljene napake v osnutku knjige ter za vse kritike in pohvale. Nenazadnje bi se zahvalil recenzentoma prof. dr. Janezu Demöarju in doc. dr. Aleksandru Sadikovu za vse popravke in pripombe, ki so pripomogli k izboljöanju te knjige. Hvala! 3 1 Uvod 1.1 Ra unalniötvo Ra unalniötvo je v zadnjih letih postalo nepogreöljiv del naöega vsakdana. Ra-unalniki niso zgolj naprave, s katerimi igramo igrice, piöemo seminarske naloge in brskamo po internetu, temve predstavljajo ra unalniki klju ne komponente prakti no vseh sodobnih elektronskih ali z elektroniko podprtih naprav od telefonov pa vse do pametnih kuhinjskih aparatov in avtomobilov. Eden od pomembnih dejavnikov, ki je povzro il taköen razmah ra unalnikov, je zagotovo internet, ki je za novejöe generacije zemljanov postal nepogreöljiv u no-izobraûevalni, prosto asni in tudi socialni pripomo ek. Ra unalnike torej sre ujemo na vsakem koraku. Prav je, da vsaj pribliûno razumemo, kako ra unalniki razmiöljajo. e ne drugega, to v danaönjem asu spada v kontekst sploöne razgledanosti. Poleg tega nam razumevanje delovanja ra unalni- ökih algoritmov odpira tudi malo bolj kriti en pogled do sodobnih tehnologij. 1.2 Zakaj se u iti programiranja Dobro je torej vedeti, kako ra unalniki razmiöljajo. Ra unalniki pa sami po sebi ne razmiöljajo, ampak namesto njih ‘razmiöljajo‘ programi, ki jih ra unalniki poganjajo. In programiranje je veja ra unalniötva, ki se ukvarja s pisanjem ra unalniökih programov. e znamo programirati, torej vsaj pribliûno vemo, na kaköen na in ra unalniki razmiöljajo. Programiranje je poleg tega nadvse uporabno, saj nam omogo a, da piöemo lastne programe in da naloge, ki bi zahtevale, da razmiöljamo mi, predamo ra unalnikom. Obstaja rek, da so vsi dobri programerji veliki lenivci1. Obstaja pa tudi prepri-anje, da bi moralo u enje programiranja spadati v osnovno izobrazbo vsakega posameznika, saj nas u i algoritmi nega na ina razmiöljanja. In da bi se morali otroci konceptov programiranja u iti ûe v prvih letih öolanja, e ne ûe v vrtcih. Algoritmi en na in razmiöljanja se osredoto a na reöevanje problemov na tak na in, da dolo imo recept oziroma algoritem, ki opisuje korake, s katerimi bomo problem imbolj u inkovito reöili. Tak na in razmiöljanja se zna izkazati za nadvse koristnega 1Povzeto po Larryju Wallu – avtorju programskega jezika Perl. 5 6 Poglavje 1 Uvod pri vsakodnevnem premagovanju problemov. In dolo anje algoritma predstavlja osnovo pri programiranju. Kompleksen (ali pa tudi ne) problem ûelimo v tem kontekstu razbiti na sekvenco korakov oziroma sestavin, ki jih bomo uporabili pri reöevanju. Te na koncu med seboj poveûemo v smiselno zaporedje, to zameöamo v naö program in problem je reöen. Programiranje velikokrat spominja na kuhanje, ko poskuöamo dolo iti recept izbrani jedi, potem pa ta recept prevedemo v meöanje sestavin, njihovo obdelavo z ustreznimi postopki (kuhanje, vzhajanje, peka), v asih pa seveda tudi malo improviziramo. In tako nekako bomo tudi programirali. 1.3 Ra unalniötvo in kemija (in druge vede) Ne samo da je ra unalniötvo postalo del naöega vsakdana, ampak je postalo tudi podporna veda za razli na strokovna podro ja, med katerimi je tudi kemija. Po eni strani ra unalniötvo naöemu strokovnemu delu daje podporo in pove uje naöo u inkovitost, saj nudi ötevilna orodja za zajem, shranjevanje, obdelavo, vizualizacijo in deljenje podatkov, omogo a krmiljenje kompleksnih naprav itd. Po drugi strani obstajajo ötevilne aplikacije, ki jih brez ra unalniötva sploh ne bi bilo. Kot prvi primer vzemimo Nobelovo nagrado za kemijo iz leta 2013, ki je bila podeljena za ra unalniöke modele in simulacije v kemiji. Tovrstni modeli odpirajo nova odkritja na podro ju razumevanja sistemov, ki jih je teûko analizirati zgolj eksperimentalno. Drugi primer je uporaba ra unalnikov pri analizi velike koli ine eksperimentalnih podatkov, ki jih ro no ne bi mogli nikoli pregledati, kaj öele, da bi iz njih potegnili kaj uporabnega. V tem kontekstu lahko omenimo ve letni projekt sekvenciranja in anotacije loveökega genoma, ki je temeljil na podlagi sekvenciranja in sestavljanja fragmentov genoma razli nih ljudi. Pri tem je bil projekt zelo ambiciozen tudi iz ra unalniöke perspektive. Poskrbeti je bilo namre treba za shranjevanje ogromne koli ine podatkov in napisati algoritme za odkrivanje vzorcev v podatkih ter zdruûevanje fragmentov v kon no celoto. Povezati je bilo potrebno najmodernejöe sekvenatorje z najmodernejöimi ra unalniki oziroma superra unalniki in mediji za shranjevanje velike koli ine podatkov. Kljub temu, da rezultati uspeöno izvedenega projekta niso pripeljali do tako revolucionarnih odkritij, kot so nekateri sprva domnevali (npr. uspeöno zdravljenje vseh genetskih bolezni), je v sled uspeöno zaklju enem projektu sekvenciranje genoma posameznika v danaönjem asu postalo skoraj rutinska klini na metoda za diagnostiko in ocenjevanje tveganja za razvoj dolo enih bolezni. Dolo ene drûave so celo investirale sredstva za dolo itev svojega nacionalnega genoma (na Islandiji so npr. vsaj delno sekvencirali genom ve kot polovici prebivalcem) in usmeritev financiranja raziskav v smeri zdravljenja bolezni, za katere so njihovi prebivalci bolj dovzetni. Poleg tega je cenovno dostopno sekvenciranje odprlo nove veje pri razvoju medicine, kot je npr. sistemska in personalizirana medicina, ki na podlagi genetskih lastnosti posameznika(ce) poskuöa dolo iti optimalno terapijo posebej zanj(o). 1.4 Kaj je programiranje? 7 1.4 Kaj je programiranje? Ra unalnik lahko v grobem razdelimo na dva dela, in sicer na strojno opremo, ki predstavlja ‘platformo‘, na kateri pa poganjamo programe in ki je brez programske opreme ‘mrtva‘, in programsko opremo, ki predstavlja ‘ûiv ni sistem‘ ra unalnika. Programiranje se ukvarja s pisanjem programov oziroma razvojem programske opreme. Proces izvajanja naro il ra unalniku, tj. naredi nekaj zame, zakomplicira dejstvo, da mi ra unalnika v osnovi ne razumemo, ra unalnik pa v osnovi ne razume nas. Programiranje predstavlja proces zapisovanja naöih navodil v jeziku, ki je pogojno razumljiv nam ( e pa znamo programirati), hkrati pa je razumljiv posebnim programom (ali pa skupini programov), ki naöa zaporedja ukazov oziroma kodo, prevedejo (angl. compile) ali pretolma ijo (angl. intepret) v zapis, ki je razumljiv ra unalniku. Sekvenco navodil/ukazov oziroma kodo pa lahko piöemo v razli nih programskih jezikih. Eni izmed teh so bliûe ra unalnikovem osnovnem jeziku (niûjenivojski jeziki) in nam omogo ajo ve svobode, imajo pa zato tudi ve prostora za to, da lahko naredimo kaköno neumnost ter so na sploöno teûji za uporabo. V to skupino bi lahko uvrstili npr. programski jezik C. Drugi so bliûje naöemu na inu razmiöljanja (viöjenivojski jeziki), zato je programiranje z njimi laûje, je pa izvedba tako napisanih programov mogo e nekoliko po asnejöa. V to skupino bi lahko uvrstili npr. programski jezik Python. 1.5 Zakaj Python? Ta knjiga opisuje programiranje v jeziku Python, ki je po mnenju marsikoga dale najprimernejöi jezik za za etek u enja programiranja. Koda napisana v tem jeziku je enostavno berljiva in intuitivna za razumevanje. Poleg tega programiranje v jeziku Python poteka na nekoliko viöjem nivoju (pravimo, da je nivo abstrakcije viöji), kar pomeni, da nam ni potrebno zelo natan no vedeti, kaj se npr. v ozadju dogaja s pomnilniökim prostorom, zato lahko s programiranjem brez dodatnega teoretiziranja za nemo kar takoj. Enostavnost jezika pa ne omejuje njegove uporabnosti, Python se namre uporablja pri razvoju ötevilnih popularnih aplikacij (oziroma programov), kot je npr. brskalnik Google, YouTube, DropBox in Instagram. V letu 2020 je bil tretji najbolj popularen programski jezik (za programskima jezikoma Java in C), njegova popularnost pa öe naraö a (medtem ko jezikoma Java in C upada). Poleg tega, da je jezik Python hitro u ljiv, to öe ne pomeni, da ni zmogljiv. Njegova dodatna prednost je razpoloûljivost ötevilnih ‘knjiûnic‘,2 ki razöirjajo njegove osnovne funkcionalnosti. Na voljo imamo tudi ötevilne knjiûnice, ki jih 2Knjiûnica predstavlja zbirko ûe napisanih delov kode, ki jo lahko enostavno pokli emo iz naöega programa. e bi naredili analogijo s kuhanjem, bi lahko rekli, da imamo z uporabo knjiûnic na razpolago bolj kompleksne sestavine, ki jih samo öe sestavimo skupaj – npr. namesto, da pe emo testo za torto od za etka, vzamemo ûe narejen biskvit, ki ga samo öe namaûemo in okrasimo. 8 Poglavje 1 Uvod lahko uporabljamo pri reöevanju problemov npr. na podro ju kemije, kot je knjiûnica chempy za uporabo v analizni kemiji, knjiûnica chemlab namenjeno ra unski kemiji ter vizualizaciji molekul in knjiûnica biopython za izvajanje bioinformati nih analiz. Kaj pa po asnost? Hitrost izvajanja programov v jeziku Python je sicer po asnejöa od hitrosti programov, ki so napisani v jezikih, kot je C. Vseeno pa lahko pri pisanju programov uporabimo knjiûnice, ki ‘ra unsko intenzivne‘ operacije izvajajo hitreje, ali pa ‘ra unsko intenzivne‘ dele naöih programov napiöemo v jeziku, kot je npr. jezik C, in te poveûemo s preostalo kodo napisano v jeziku Python. Osnov programiranja v jeziku Python se boste lahko torej nau ili relativno hitro, poleg tega pa ga boste lahko uporabljali tudi, e se boste po zaklju ku u enja osnov lotili bolj resnega programiranja. 1.6 Kaj me aka na koncu knjige? Ta knjiga je namenjena u enju osnov programiranja v jeziku Python. Pogledali si bomo osnovne gradnike, ki jih uporabljamo pri programiranju in par pogosto uporabljenih knjiûnic, predvsem za analizo in vizualizacijo podatkov. Ko boste knjigo prebrali, naj bi znali brez problemov slede e: • pisati ra unalniöke programe za zajem podatkov (od uporabnika, iz datotek, iz spleta ipd.), • pisati ra unalniöke programe za obdelavo podatkov (dolo itev najpomemb-nejöih podatkov, sortiranje podatkov, izvajanje statisti nih analiz podatkov), • pristopiti k vizualizaciji podatkov, • pristopiti k reöevanju problemov na algoritmi en na in. Nenazadnje boste dobili osnovno znanje programiranja, ki ga boste v prihodnosti zlahka nadgradili, e boste tako hoteli. Za konec uvoda pa öe opozorilo. Programiranja se öe nih e ni nau il z branjem knjig. Kdor ho e znati programirati, mora programirati. Knjiga sluûi zgolj kot pripomo ek, ki ga lahko uporabljate pri u enju, njeno branje pa je zagotovo potrebno dopolniti s treningom. Zglede v knjigi torej poskusite v imve jem obsegu reöiti sami, reöujte vaje in poskusite znanje programiranja uporabiti pri reöevanju problemov, s katerimi se soo ate pri drugih predmetih (namesto npr. uporabe orodja Excel). Programiranje je namre kot öport in za spretnost zahteva svoj trening. Trenirajte. 1.7 Nekaj navodil za branje knjige Posamezno poglavje v knjigi vsebuje zglede in njihove reöitve. Opis teh je podan v poöevnem tekstu, poleg tega pa je za etek zgledov in reöitev ozna en in oötevil en. 1.7 Nekaj navodil za branje knjige 9 Konec posamezne reöitve ozna uje simbol —. Koda v jeziku Python je zapisana v pisavi Courier. e so na za etku vrstice pred kodo zapisani trije lomljeni oklepaji oziroma trije znaki za relacijo ve je (>>>), to pomeni, da kodo zapisujemo v ukazno vrstico okolja Python. e so vrstice kode oötevil ene, to pomeni, da kodo piöemo v obliki programa. 2 Spoznavanje z okoljem 2.1 Izbira in namestitev okolja Razlaga in zgledi v knjigi temeljijo na okolju Python 3. Osnovno okolje, ki bo za spoznavanje osnov programiranja isto dovolj, lahko dobite na Pythonovi doma i strani (https://www.python.org/), kjer si izberete öe svoj operacijski sistem in verzijo, ki jo ûelite namestiti (izberite si razli ico Python 3...). e uporabljate operacijski sistem Linux ali OS X, imate Python po vsej verjetnosti ûe nameö en. Pazite le na to, da uporabljate Python 3, saj ve ina zgledov v okolju Python 2 ne bo delovala ali pa bo celo delovala nekoliko druga e. Ob nameö anju okolja Python je koristno, e na za etku obkljukate izbiro Add Python 3... to PATH, saj si boste s tem mogo e prihranili kaköno teûavo proti koncu knjige. e ste ambiciozni in pri akujete, da boste Python v prihodnosti bolj resno uporabljali, si mogo e ûe zdaj namestite distribucijo Anaconda, ki vklju uje malo ve knjiûnic za bolj napredno ra unanje, analizo podatkov in risanje grafov. Dobite jo na strani https://www.anaconda.com/. 2.2 Okolje IDLE Tekom razvoja enostavnejöih ali kompleksnih programov ponavadi uporabljamo razli na razvojna okolja (angl. Integrated Development Environment, IDE), ki zdru- ûujejo orodja za pisanje, poganjanje in razhroö evanje (angl. debugging) programov. Ti pripomo ki nam nekoliko olajöajo delo in naredijo razvoj kompleksnejöih (ali pa tudi enostavnejöih) programov nekoliko bolj pregleden. Z namestitvijo osnovnega okolja Python smo na svoj ra unalnik namestili tudi okolje IDLE, ki predstavlja enostavno razvojno okolje za jezik Python in katerega bomo uporabljali pri spo-znavanju osnov programiranja. Zaûenimo ga in za nimo (v operacijskem sistemu Windows boste IDLE zagnali tako, da v Start meni napiöete idle in pritisnete tipko Enter). 11 12 Poglavje 2 Spoznavanje z okoljem 2.3 Ukazna vrstica Pred nami se je pojavila ukazna oziroma pozivna vrstica, s pomo jo katere lahko za nemo naö pogovor s tolma em jezika Python (angl. Python interpreter). Mi mu bomo v obliki stavkov podajali ukaze, on pa jih bo izvröil in nam ponavadi tudi vrnil ali pa izpisal nek rezultat. Ukaz napiöemo in tolma u poöljemo tako, da pritisnemo tipko Enter. Poskusimo nekaj osnovnih stvari. >>> 1 + 2 3>>> 1 - 2 -1 >>> 1 * 2 2>>> 1 - 3 * 4 -11 Tolma u torej lahko podam nek izraz sestavljen iz operandov (v tem primeru ötevil) in operatorjev (npr. +, * in -) in on mi bo vrnil rezultat. Iz zadnjega izraza vidimo tudi, da ima mnoûenje prednost pred odötevanjem. Uporabim lahko tudi oklepaje, s katerimi dolo im vrstni red ra unanja: >>> (1 - 3) * 4 -8 Kaj pa deljenje? >>> 5 / 4 1.25 >>> 10 / 5 2.0 Uporabim lahko tudi celoötevilsko deljenje (//) in ostanek pri deljenju oziroma operacijo modulo (%): >>> 5 // 4 1>>> 10 // 5 2.0 >>> 10 % 4 2 2.4 Podatkovni tipi 13 2.4 Podatkovni tipi Celoötevilsko deljenje o itno vedno vrne celo ötevilo, obi ajno deljenje pa vrne decimalno ötevilo, tudi e je decimalni del tega ötevila enak 0. Operaciji sta definirani tako, da vsaki vrneta dolo en podatkovni tip, ki je poleg tega odvisen tudi od podatkovnega tipa vhodov. Podatkovni tip? Vse vrednosti, ki smo jih tolma u podali, so imele dolo en podatkovni tip, in sicer smo v vseh primerih zgoraj podajali cela ötevila (angl. integer), ki jim Python pravi int. Rezultati izvedbe gornjih stavkov so v dolo enih primerih prav tako predstavljali cela ötevila, pri deljenju pa smo dobili decimalna ötevila oziroma ötevila v plavajo i vejici (angl. floating point number), ki jim Python pravi float. Decimalna ötevila lahko tolma u podamo tudi kot vhodne podatke: >>> 5.4 + 4.6 10.0 >>> 6.3 / 2 3.15 >>> 4 ** 0.5 2.0 Mimogrede, operator ** dolo a potenciranje, potenciranje z vrednostjo 0.5 pa je enako kvadratnemu korenu. Python zna poleg s ötevili delati tudi z drugimi podatkovnimi tipi, npr. nizi (angl. string), oziroma po njegovo s podatkovnim tipom str. Nizi vsebujejo sekvenco poljubnih znakov ( rke, ötevila, lo ila, posebni simboli ipd.), zapisujemo pa jih znotraj dvojnih (") ali enojnih navednic (’). Poskusimo: >>> " niz1 " ’niz1 ’ >>> ’niz2 ’ ’niz2 ’ Python o itno ne lo i med dvojnimi in enojnimi navednicami. Mi jih lahko uporabljamo isto po navdihu. Kako pa znotraj niza zapisati enojne navednice? Tako da jih ovijemo v dvojne navednice. In obratno. Poskusimo: >>> ’Rekel je: "Daj mi mir !" ’ ’Rekel je: "Daj mi mir !" ’ >>> "’Znamo ’ programirati ." "’Znamo ’ programirati ." Ali lahko tudi nad nizi izvajamo operacije od prej. Poskusimo: >>> ’niz1 ’ + ’niz2 ’ ’niz1niz2 ’ >>> ’niz1 ’ + ’ ’ + ’niz2 ’ 14 Poglavje 2 Spoznavanje z okoljem ’niz1 niz2 ’ >>> ’niz1 ’ * 3 ’niz1niz1niz1 ’ >>> ’niz1 ’ * ’niz2 ’ TypeError : can ’t multiply sequence by non -int of type ’str’ Python nam je v zadnjem primeru vrnil napako, kar pomeni, da operacija, ki smo jo hoteli izvesti ni podprta. Ko dobimo napako, se je ne ustraöimo, ampak jo preberimo, saj nam sporo a kaj je narobe1. Python torej mnoûenja dveh nizov med seboj ne podpira. Tudi seötevanje in mnoûenje je nad nizi definirano nekoliko druga e kot nad ötevili. Kaj pa e niz vsebuje samo ötevila? >>> ’3’ + ’5’ ’35 ’ >>> ’3’ * ’5’ TypeError : can ’t multiply sequence by non -int of type ’str’ Rezultat je podoben kot prej. Podatkovni tip operanda oziroma podatka torej dolo a kaj in kako lahko s posameznim podatkom po nemo. ätevila lahko npr. seötevamo, e pa poskusimo seöteti dva niza, izvajamo operacijo lepljenja nizov oziroma njihovo konkatenacijo. 2.5 Funkcije Poleg osnovnih operatorjev so v osnovnem okolju jezika Python vgrajene tudi dolo ene funkcije. Podobno kot v matematiki tudi pri programiranju funkcije sprejemajo dolo ene vhode oziroma argumente. Funkcijo pokli emo tako, da podamo njeno ime in argumente oziroma vhode funkciji zapiöemo znotraj oklepajev. Takole: ime_funkcije ( argument_1 , argument_2 , ... , argument_n ) Kot vhode oziroma argumente lahko funkciji podamo fiksne vrednosti ali pa kar imena spremenljivk, za katerimi se dolo ene vrednosti nahajajo (glej naslednji razdelek). Po klicu se bo funkcija izvedla in nekaj koristnega naredila in/ali vrnila nek uporaben rezultat. e bi ûeleli izvedeti, kateremu podatkovnemu tipu pripada nek podatek, bi lahko npr. poklicali funkcijo type: >>> type (1) < class ’int ’> >>> type (1.0) < class ’float ’> >>> type(’niz ’) 1Poro ila o napakah so v knjigi nekoliko skrajöana in vsebujejo le tip in pojasnilo napake. Python sicer poro a tudi lokaciji napake. 2.6 Spremenljivke 15 < class ’str ’> Poglejmo si öe funkcijo abs, ki izra una absolutno vrednost podanega argumenta: >>> abs ( -1) 1>>> abs(-1.4) 1.4 >>> abs (5) 5>>> abs(’niz’) TypeError : bad operand type for abs (): ’str ’ Tudi argumenti funkcij so torej omejeni na dolo ene podatkovne tipe, kar je smiselno. Funkcija abs je na primer omejena zgolj na ötevila, kot so ötevila tipa int ali float, saj absolutne vrednosti niza ne moremo izra unati. Python ima vgrajenih öe kar nekaj funkcij, ki pa jih bomo ve inoma spoznavali sproti. 2.6 Spremenljivke Do zdaj smo v ukazno vrstico pisali izraze sestavljene iz operatorjev in operandov (podatkov).2 Python je po vsakem pritisku tipke Enter podani izraz pognal in vrnil rezultat, ki pa ga je takoj zatem pozabil. Do dobljenega rezultata tako ne moremo ve priti druga e, kot da öe enkrat podamo enak izraz, ki ga bo Python ponovno ovrednotil in vrnil enak rezultat. Pogosto pa si ûelimo rezultate izvedenih stavkov. zapomniti oziroma ûelimo, da jih Python shrani za kasneje. Na ta na in lahko sestavljamo kompleksnejöe izraze (brez da bi pisali dolge ka e ez ve vrstic), izra unan podatek uporabimo ve krat in z njim delamo razli ne stvari (npr. uporaba v drugih izrazih, izpis na zaslon, shranjevanje na trdi disk itd.). Python omogo a, da posameznemu podatku dodelimo ime, preko katerega bomo lahko do tega podatka dostopali öe kasneje. Takole: >>> x = 1 >>> y = 2 - 3.5 >>> niz = ’abc ’ Zdaj Python ni ni esar izpisal, je pa vrednost na desni strani prireditvenega stavka priredil imenu na levi strani prireditvenega stavka. Izvedli smo torej prireditev vrednosti na desni imenu na levi, ki mu pravimo tudi spremenljivka. Pri tem smo uporabili prireditveni operator =. Pozor: to ni operator enakosti, saj vedno deluje samo v eno smer, in sicer tistemu, kar napiöemo na levi strani, priredi tisto, kar napiöemo na desni strani. e npr. napiöemo 2Tovrstnim izrazom lahko re emo tudi ukazi ali pa kar stavki. 16 Poglavje 2 Spoznavanje z okoljem >>> x = x + 2 to ni nereöljiva ena ba (kot bi bila v primeru, ko ena aj obravnavamo kot operator enakosti), ampak zgolj pomeni, da vzamemo vrednost, ki stoji za imenom x, to vrednost pove amo za 2 in priredimo imenu x. Zgornji stavek torej vrednost v spremenljivki x pove a za 2. Kako pa dostopamo do vrednosti posamezne spremenljivke oziroma do vrednosti, ki stoji za dolo enim imenom? To smo naredili ûe zgoraj – tako da podamo ime spremenljivke. Ko smo zgoraj napisali ime x na desni strani prireditvenega stavka, je Python pogledal kaj za tem imenom stoji in ime zamenjal z vrednostjo za njim. Druga e je, e ime uporabimo na levi strani prireditvenega stavka. S tem namre imenu priredimo novo vrednost, e pa imena pred tem öe nismo definirali, s tem ustvarimo tudi novo ime. Temu re emo definicija spremenljivke. Do vrednosti spremenljivke x ali y bi zdaj lahko dostopali tako, da podamo njeno ime: >>> x 3>>> y -1.5 Ime spremenljivke lahko uporabimo tudi kot argument funkcije, npr. takole: >>> abs(x) 3>>> abs(y) 1.5 Prav tako lahko izhod funkcije priredimo novi (ali obstoje i) spremenljivki: >>> z = abs(y) Zdaj Python ni izpisal ni esar, je pa ustvaril novo spremenljivko, do katere lahko dostopamo: >>> z 1.5 Kaj pa bi se zgodilo, e pokli emo ime spremenljivke, ki je öe nimamo: >>> novo_ime NameError : name ’novo_ime ’ is not defined Ker tega imena Python ne pozna, saj ga öe nismo definirali, javi napako. Dostopamo lahko torej le do imen, ki smo jih bodisi definirali mi ali pa so ûe definirana (kot npr. abs). Kaj pa bi se zgodilo, e bi imenu vgrajene funkcije priredili neko vrednost? e bi npr. izvedli prireditev 2.7 Pisanje programov 17 >>> abs = 5 bo Python to brez pritoûevanja tudi izvedel. Poskusimo zdaj öe enkrat izra unati absolutno vrednost tistega, kar se skriva za spremenljivko y: >>> abs(y) TypeError : ’int ’ object is not callable Seveda bo priölo do napake, saj smo si funkcijo za izra un absolutne vrednosti povozili z vrednostjo, ki pripada podatkovnemu tipu int. Za imenom abs po novem Python nima ve shranjene funkcije za izra un absolutne vrednosti, ampak ötevilo 5. Nerodno. Stvar lahko reöimo tako, da okolje IDLE resetiramo (Shell æ Restart Shell oziroma kombinacija tipk Ctrl + F6). V sploönem velja, da se moramo pri poimenovanju spremenljivk drûati dolo enih pravil. Kot smo videli prej, uporaba imen, ki so ûe rezervirana oziroma uporabljena, ni priporo ena. Prireditev vrednosti rezerviranim imenom ni zgolj slaba, ampak celo vrne napako: >>> if = 5 SyntaxError : invalid syntax Zgoraj vidimo, da je besedica if t.i. rezervirano ime, saj jo IDLE obarva druga e kot ostale besede oziroma jo odebeli. Njeno uporabo bomo spoznali prav kmalu. Pri imenih spremenljivk nam Python poleg tega ne bo pustil uporabe presledkov: >>> moje ime = ’Miha ’ SyntaxError : invalid syntax Stvar lahko reöimo tako, da presledke zamenjamo s pod rtaji (_): >>> moje_ime = ’Miha ’ >>> moje_ime ’Miha ’ Pri poimenovanju spremenljivk se je dobro drûati dolo enih priporo il. Navedimo jih nekaj: • spremenljivke naj imajo smiselna imena, ki programerju sporo ajo pomen spremenljivke; • pri poimenovanju spremenljivk se izogibajmo rkam, ki ne nastopajo v osnovni angleöki abecedi (izogibamo se npr. rkam , û in ö); • imena so lahko sestavljena iz ve besed, pri emer te lo imo s pod rtaji. 2.7 Pisanje programov S tolma em smo se do zdaj pogovarjali preko ukazne vrstice, emur bi pa teûko rekli programiranje. V ukazno vrstico bi lahko sicer napisali zaporedje stavkov, 18 Poglavje 2 Spoznavanje z okoljem s katerim bi nekaj izra unali, oziroma s katerim bi reöili nek problem. e pa bi hoteli to zaporedje stavkov pognati öe enkrat (mogo e na drugih podatkih), bi morali v ukazno vrstico stavke v enakem vrstnem redu napisati ponovno. O itno je, da to ni najbolj priro en na in programiranja. V sploönem zaporedja stavkov zapisujemo v tekstovne datoteke – v programe, ki jih potem v poganjanje predamo naöemu tolma u. Tolma bo ukaze v datoteki izvedel po vrsti na podoben na in, kot e bi te zapisali v ukazno vrstico. Kako lahko za pisanje takih programov uporabimo orodje IDLE? Najprej bomo ustvarili novo datoteko – program, in sicer z izbiro menija File æ New File oziroma s kombinacijo tipk Ctrl + N. V primeru, da datoteka z nekim programom ûe obstaja, lahko to odpremo preko menija File æ Open oziroma s kombinacijo tipk Ctrl + O. V obeh primerih s tem odpremo tudi IDLE-ov urejevalnik teksta, s pomo jo katerega lahko napiöemo program, tega shranimo in na koncu poûenemo oziroma v poganjanje damo tolma u. Kot ste verjetno ûe vajeni, kon nica datotek nakazuje kaj pribliûno datoteka vsebuje. Datoteke, v katerih je shranjen program v jeziku Python, prepoznamo preko kon nice .py. Programe, ki jih bomo pisali, bomo torej tudi mi opremili s tako kon nico. Napiöimo krajöi program, ki temperaturo v stopinjah Celzija pretvori v temperaturo v stopinjah Fahrenheit z upoötevanjem ena be T = F TC ú 1 . 8 + 32, pri emer T predstavlja temperaturo v stopinjah Celzija, pa temperaturo v stopinjah C TF Fahrenheit. Pri tem zaenkrat predpostavljajmo, da je T , ki ga ûelimo pretvoriti, C enak 20. Program bo slede 3: 1 T_C = 20 2 T_F = T_C * 1.8 + 32 Program lahko zdaj shranimo, npr. v datoteko z imenom temperatura.py in poûenemo z izbiro menija Run æ Run Module oziroma s pritiskom na tipko F5. Izvröil se je preklop na ukazno vrstico, nikjer pa ni vidnega rezultata izvröitve naöega programa. Ali se je program res izvedel? Preverimo lahko tako, da v ukazno vrstico napiöemo imeni spremenljivk, ki smo jih v programu definirali. e se program ni pognal, bo Python vrnil napako. >>> T_C 20 >>> T_F 68.0 Program se je o itno izvedel, saj sta spremenljivki definirani. Program pa pred tem ni ni esar izpisal. Program ne izpiöe ni esar, tudi e ga dopolnimo z zgornjima vrsticama: 3Kadar bomo v zgledih kode vrstice ozna ili z njihovimi ötevilkami, bo to pomenilo, da gre za program. 2.8 Funkcija print 19 1 T_C = 20 2 T_F = T_C * 1.8 + 32 3 T_C 4 T_F Zakaj ne? Ko svojo kodo zapakiramo v programe, ti izpisujejo vrednosti samo takrat, ko to od njih eksplicitno zahtevamo. Kako? S funkcijo print. 2.8 Funkcija print Funkcija print nam omogo a izpisovanje vrednosti znotraj programov. Pokli emo jo tako, da ji kot argumente naötejemo vrednosti, ki jih ûelimo izpisati in funkcija print bo vednosti izpisala, vmes bo dala presledke, na koncu izpisa pa bo sko ila v novo vrstico. Dopolnimo zgornji program, tako da izpiöe obe temperaturi: 1 T_C = 20 2 T_F = T_C * 1.8 + 32 3 print(T_C) 4 print(T_F) Funkciji smo podali ime spremenljivke, izpisala pa je vrednost, ki stoji za imenom: 20 68.0 Funkcija print je na koncu vsakega izpisa avtomatsko sko ila v novo vrstico. Obe temperaturi bi lahko izpisali tudi v isti vrstici, in sicer takole: print(T_C , T_F) V tem primeru je izpis slede : 20 68.0 Funkcija print je torej podani vrednosti avtomatsko lo ila s presledkom. Poskusimo zdaj izpis öe malo olepöati. Poleg imen spremenljivk lahko kot argumenti nastopajo tudi konstantne vrednosti. Lahko bi v izpis dodali öe niz, ki bi naredil vse skupaj malo bolj informativno. Takole print(T_C , " ¶ C je enako ", T_F , " ¶ F.") V tem primeru je izpis slede : 20 ¶ C je enako 68.0 ¶ F. Imena spremenljivk je Python torej zamenjal z njihovimi vrednostmi, nize pa je izpisal kakor so bili podani. 20 Poglavje 2 Spoznavanje z okoljem 2.9 Funkcija input äe malo pa bomo napisali naö prvi pravi program. Pretvarjanje iz stopinj Celzija v stopinje Fahrenheit sicer deluje, malo pa je mote e to, da lahko pretvarjamo samo eno vrednost, ki je ûe vnaprej dolo ena. Program bi bil bistveno bolj uporaben, e bi lahko vrednost, ki jo ûelimo pretvoriti, podal kar uporabnik ob zagonu programa. To nam omogo a funkcija input. Funkcija input prav tako kot print na zaslon izpiöe podan argument. Za razliko od funkcije print, funkcija input sprejema samo en argument tipa str. Preko tega argumenta bomo funkciji input podali navodilo za uporabnika. Na primer takole: >>> input(" Vnesi svoje ime: ") Funkcija input aka na uporabnikov vnos in pritisk tipke Enter. e smo to funkcijo pognali iz ukazne vrstice, bo uporabnikov vnos ponovila. Lahko pa uporabnikov vnos shranimo v spremenljivko in ga kasneje uporabimo. Takole >>> ime = input(" Vnesi svoje ime: ") Rezultat funkcije input smo torej priredili spremenljivki ime. Zdaj lahko do tistega, kar je uporabnik vnesel, dostopamo preko imena spremenljivke. Uporabimo to na naöem programu za pretvarjanje med temperaturami. Temperaturo v stopinjah bomo zdaj prebrali od uporabnika preko funkcije input. 1 T_C = input(" Vnesi temperaturo v ¶ C: ") 2 T_F = T_C * 1.8 + 32 3 print(T_C , " ¶ C je enako ", T_F , " ¶ F.") Program pa v tej obliki ûal öe ne bo deloval. Funkcija input namre vedno vrne niz, saj je to podatkovni tip, v katerega lahko zapiöe karkoli bo pa uporabnik vnesel. Tudi e bo uporabnik vnesel ötevilo, bo to predstavljeno kot niz oziroma podatkovni tip str. Kakor se spomnimo od prej pa nizov ne moremo mnoûiti z decimalnimi ötevili pa tudi seötevanje je denifinirano tokrat za nas nekoliko neugodno. Prebrano ötevilo, ki je zapisano kot niz, moramo torej pred nadaljnjo obdelavo pretvoriti v nekaj, s imer lahko ra unamo, npr. float. 2.10 Pretvarjanje med podatkovnimi tipi Pretvorbo podatka v posamezen podatkovni tip lahko izvedemo z vgrajenimi funkcijami, ki nosijo enako ime, kot podatkovni tip, v katerega ûelimo pretvarjati. e bi ûeleli nekaj pretvoriti v niz, bi torej uporabili funkcijo str, v celo ötevilo funkcijo int, v decimalno pa funkcijo float. Poskusimo: >>> niz = str (20) >>> niz 2.10 Pretvarjanje med podatkovnimi tipi 21 ’20 ’ >>> int(niz) 20 >>> float(niz) 20.0 >>> x = 5.4 >>> str(x) ’5.4 ’ >>> int(x) 5 Rezultat izvedbe posamezne funkcije je torej zapis podatka v ûeljenem podatkovnem tipu. Pri tem so seveda upoötevane omejitve posameznega podatkovnega tipa. Ko npr. pretvarjamo v podatkovni tip int, funkcija int poreûe decimalke za decimalno piko (brez zaokroûevanja). >>> int (6.9) 6 Drugi primer take omejitve je, da nize lahko pretvarjamo v ötevila le, ko vsebujejo zgolj in samo ötevila (in decimalno piko): >>> float("6.9") 6.9 >>> int("6a") ValueError : invalid literal for int () with base 10: ’6a’ >>> float(" stevilo ") ValueError : could not convert string to float: ’stevilo ’ Dokon ajmo zdaj naö prvi program. Zgled 1. Napiöi program, ki od uporabnika prebere temperaturo v stopinjah Fahrenheit in to pretvori v stopinje Celzija ter poda izpis obeh temperatur. Reöitev 1. Reöitev naloge prakti no ûe imamo. Malenkost jo moramo le öe dopolniti. 1 niz = input(" Vnesi temperaturo v ¶ C: ") 2 T_C = float(niz) 3 T_F = T_C * 1.8 + 32 4 print(T_C , " ¶ C je enako ", T_F , " ¶ F.") Vrstici ötevilka 1 in 2 bi lahko zdruûili tudi v eno samo: 1 T_C = float(input(" Vnesi temperaturo v ¶ C: ")) 2 T_F = T_C * 1.8 + 32 3 print(T_C , " ¶ C je enako ", T_F , " ¶ F.") 22 Poglavje 2 Spoznavanje z okoljem — Program zdaj deluje kakor bi ûeleli. Do problema pride samo takrat, ko uporabnik vnese kaköno neumnost. 2.11 Pisanje komentarjev Ponavadi je dobro, da programe piöemo na tak na in, da jih bodo razumeli tudi drugi in da jih bomo mogo e razumeli sami, ko jih bomo ez par mesecev ponovno pregledovali. Pri tem pomaga ûe to, da se trudimo pisati lepo in pregledno kodo ter spremenljivke poimenujemo tako, da vsaj pribliûno vemo kaj predstavljajo (T_F in T_C). Dodatno pa k razumevanju napisanih programov pripomorejo komentarji. Komentarji sluûijo opombam, ki jih sebi ali drugim piöemo znotraj naöih programom, niso pa namenjeni izvajanju. Pythonu moramo torej nekako povedati, da naj komentarjev ne poganja. To mu lahko sporo imo z uporabo dolo enih znakov. Znak # ozna uje vrsti ni komentar, in sicer bo Python presko il vso kodo od za etka znaka # do konca vrstice, v kateri se ta znak nahaja. Primer uporabe je slede : >>> # napiöemo lahko karkoli >>> x = 1 # prireditev bo pognana , komentar pa ne >>> x 1 V asih si ûelimo napisati daljöi (ve vrsti ni komentar). Tega za nemo s tremi enojnimi ali tremi dvojnimi narekovaji in ga kon amo s tremi enojnimi ali tremi dvojnimi narekovaji (s tistimi, s katerimi smo komentar za eli). Povadimo zdaj oboje na naöem prvem programu. 1 """ 2 Program , ki pretvori prebrano temperaturo 3 iz stopinj Celzija v stopinje Fahrenheit 4 """ 56 # branje in pretvorba v decimalno ötevilo 7 T_C = float(input(" Vnesi temperaturo v ¶ C: ")) 89 T_F = T_C * 1.8 + 32 # ena ba pretvorbe 10 11 # izpis na zaslon 12 print(T_C , " ¶ C je enako ", T_F , " ¶ F.") 3 Pogojni stavek 3.1 Zakaj pogojni stavki? Vsi programi, ki smo jih do zdaj napisali (je bil mogo e samo eden?), so se izvedli po vnaprej dolo enem zaporedju. Od zgoraj navzdol so se namre lepo po vrsti izvedli vsi stavki v programu. V dolo enih primerih pa bi posamezne stavke radi izvedli samo ob izpolnjenosti (ali pa neizpolnjenosti) izbranega pogoja. Poglejmo si spodnji primer: Zgled 2. Napiöi program, ki od uporabnika prebere telesno maso in viöino in izpiöe uporabnikov indeks telesne mase. Reöitev 2. Preko funkcije input bomo torej prebrali telesno maso in viöino. Ker funkcija vra a niz, bomo obe vrednosti pretvorili v decimalno ötevilo (float). Potem bomo uporabili ena bo za izra un indeksa telesne mase: itm = masa . Pri tem visina 2 mora biti telesna masa podana v kilogramih, viöina pa v metrih. 1 masa = float(input("Vpiöi svojo telesno maso [kg ]: ")) 2 visina = float(input("Vpiöi svojo viöino [m]: ")) 3 itm = masa/ visina **2 4 print("Tvoj ITM je", itm) — Zgornji program je popolnoma pravilen, bi ga pa radi öe malo dopolnili. Veliko uporabnikov verjetno ne ve, kaj posamezna vrednost indeksa telesne mase (ITM) pomeni. Ali mora shujöati, je njegova telesena masa ustrezna, ali bi se moral malo zrediti? e malo poenostavimo, lahko ljudi razdelimo v tri skupine, in sicer na tiste s premajhno telesno maso, tiste z ustrezno telesno maso in tiste s preveliko telesno maso: pogoj skupina ITM < 18.5 podhranjenost 18.5 Æ ITM Æ 25 normalna telesna masa 25 < ITM debelost 23 24 Poglavje 3 Pogojni stavek Naö program bi torej radi dopolnili tako, da bo uporabniku izpisal tudi informacijo o tem, v katero skupino spada. Z drugimi besedami, e bo izpolnjen prvi pogoj (ITM < 18.5), bi radi izpisali, da je uporabnik podhranjen, e bo izpolnjen drugi pogoj (18.5 Æ ITM Æ 25), da je njegova telesna masa ustrezna in e bo izpolnjen tretji pogoj (25 < ITM), da je preteûak. Radi bi torej, da se dolo eni deli naöega programa (v konkretnem primeru razli ni izpisi) izvedejo v odvisnosti od vrednosti ITM. 3.2 Osnovna oblika stavka if Za pisanje pogojnih stavkov bomo uporabili Pythonov stavek if. Njegova osnovna oblika je slede a: if pogoj : # pogojni_stavki so zamaknjeni # pogojni stavki # e je pogoj izpolnjen ... # skupni stavki # ni ve zamaknjeno ... Za nemo torej z rezervirano besedico if, ki ji sledi pogoj. Temu sledi dvopi je (:), s katerim povemo, da je konec pogoja. Potem sledi pogojni del, ki se bo izvedel samo v primeru, da je podan pogoj izpolnjen. Pogojnemu delu sledijo stavki, ki se bodo izvedli v vsakem primeru, ne glede na izpolnjenost pogoja. Kako Pythonu povemo kje je konec pogojnega dela? Z zamikom (angl. indent). Zgoraj smo tiste stavke, ki se izvedejo samo v primeru izpolnjenosti pogoja zamaknili, tako da smo pred njih vstavili tabulator (tipka Tab) ali par presledkov ( e smo natan ni, se drûimo dogovora ötirih presledkov). Pogojni del smo zaklju ili tako, da smo enostavno nehali zamikati. Izvedbo zgornje kode prikazuje diagram poteka na sliki 3.1. 3.3 Kaj je pogoj? Kaj pravzaprav predstavlja pogoj za izvedbo pogojnega dela stavka? Kot je razvidno iz slike 3.1 je pogoj nekaj, kar je lahko resni no (angl. true) ali neresni no (angl. false). Pogoj moramo torej zastaviti kot vpraöanje, na katerega lahko odgovorimo bodisi z odgovorom da ali z odgovorom ne. Pri formiranju vpraöanja oziroma pogoja bomo torej ve inoma uporabljali operatorje, ki vra ajo take odgovore. Tem operatorjem pravimo pogojni operatorji. Pa si poglejmo nekaj njihovih primerov. 3.3 Kaj je pogoj? 25 False True g j g j i a ki ( e je g j i je ) k i a ki Slika 3.1 Diagram poteka osnovne oblike stavka if. V primeru, e je pogoj izpolnjen, se izvede pogojni del. 3.3.1 Primerjalni operatorji in podatkovni tip bool Prva skupina operatorjev, ki jih bomo uporabljali pri pisanju pogojev so t.i. primerjalni operatorji, ki jih poznamo ûe iz matematike. To so npr. operator enakosti == (ker je enojni ena aj uporabljen za prirejanje, moramo za primerjanje uporabiti dvojni ena aj), operator neenakosti !=, ve ji >, ve ji ali enak >= itd. Poskusimo: >>> 1 == 1 True >>> 1 == 2 False >>> 1 != 2 True >>> 1 > 2 False >>> 1 <= 2 True >>> 1 == 2.5 False 26 Poglavje 3 Pogojni stavek >>> 1 == 1.0 True S primerjalnimi operatorji torej med seboj primerjamo dva podatka, rezultat primerjanja pa je bodisi vrednost True ali False. Rezultat primerjanja je podatek, ki lahko zavzame samo te dve vrednosti. Kaköen je podatkovni tip tega podatka? >>> type( True ) < class ’bool ’> >>> type( False ) < class ’bool ’> Za oblikovanje pogojev imamo torej na voljo poseben podatkovni tip, tj. bool, oziroma po angleöko boolean, ki lahko zavzame samo dve vrednosti, tj. True ali False. Poskusimo uporabo primerjalnih operatorjev v kombinaciji s pogojnim stavkom if uporabiti na zgledu iz za etka poglavja. Zgled 3. Napiöi program, ki od uporabnika prebere telesno maso in viöino in izpiöe uporabnikov indeks telesne mase (ITM). Poleg tega program uporabniku pove, v katero skupino spada. Reöitev 3. Program od prej bomo dopolnili s pogojnim stavkom. e je ITM manjöi od 17.5, lahko program izpiöe, da je uporabnikova telesna masa premajhna. e je ITM ve ji od 25, lahko program izpiöe, da je uporabnikova telesna masa prevelika. Kaj pa vmes? Tega pa zaenkrat öe ne znamo. 1 masa = float(input("Vpiöi svojo telesno maso [kg ]: ")) 2 visina = float(input("Vpiöi svojo viöino [m]: ")) 3 itm = masa/ visina **2 4 print("Tvoj ITM je", itm) 5 if ITM < 17.5: 6 print(" Tvoja telesna masa je premajhna ") 7 if ITM > 25: 8 print(" Tvoja telesna masa je prevelika ") — Primerjalne operatorje lahko uporabimo tudi nad podatki, ki niso ötevila. >>> "abc" == "abc" True >>> "abc" == "ABC" False >>> "abc" < "b" 3.3 Kaj je pogoj? 27 True >>> "abc" < "abc" False >>> "abc" < "abd" True Iz zgornjih zgledov vidimo tudi to, da Python lo i med velikimi in malimi rkami in da so dolo eni nizi manjöi od drugih. Kako pa primerjanje dveh nizov poteka? Na enak na in, kot primerjamo nize, ko poskuöamo besede sortirati po abecedi (npr. v slovarju ali telefonskem imeniku). Dve besedi primerjamo znak po znaku od za etka do konca, dokler ne pridemo do dveh znakov, ki se razlikujeta ali do konca ene izmed besed. e se besedi ujemata po vseh znakih in je ena beseda krajöa, je krajöa beseda zagotovo manjöa. Npr., beseda "beda"je manjöa od besede "bedarija"(v slovarju bo beda nastopala pred bedarijo): >>> " beda " < " bedarija " True Beseda "bedno"pa ni manjöa od besede "bedarija", eprav je od nje krajöa. Zakaj ne? Zato, ker se besedi razlikujeta v etrtem primerjanju na znakih "n"in "a"in ker "n"ni manjöi od znaka "a". >>> " bedno " < " bedarija " False Takemu primerjanju pravimo leksikografsko primerjanje. 3.3.2 Operatorja vsebovanosti Ko smo ravno pri nizih, lahko omenimo öe operatorja vsebovanosti, ki preverjata ali nekaj je (in) oziroma ni (not in) v posameznem nizu vsebovano. Operatorja bomo uporabljali tudi na drugih podatkovnih tipih, ki podobno kot nizi, vsebujejo druge podatke – nizi so sestavljeni iz ve znakov oziroma podnizov. e je nek niz podniz vsebovan v nekem nizu niz, lahko preverim takole: >>> podniz in niz Povadimo: >>> " beda " in " bedarija " True >>> " Beda " in " bedarija " False >>> "ana" in " anakonda " True >>> "ana" in " sanatorij " True 28 Poglavje 3 Pogojni stavek >>> "a" in " abeceda " True Spet vidimo, da je znak "b" nekaj drugega kot znak "B" in da Python lo i med malimi in velikimi rkami. 3.3.3 Zdruûevanje rezultatov primerjanja Pri reöevanju naloge z izpisovanjem podatkov o ITM imamo öe vedno teûave s primerom, kjer morata biti izpolnjena dva pogoja hkrati (ITM >= 18.5 in ITM <= 25). Kon en pogoj za izvedbo izpisa Tvoja telesna masa je ustrezna, moramo torej sestaviti iz dveh pogojev. Za ta namen lahko uporabimo t.i. logi ne operatorje, ki omogo ajo medsebojno zdruûevanje ve spremenljivk tipa bool. Osnovna logi na operatorja, ki ju bomo uporabljali v takem primeru sta operator and in operator or. Njuno delovanje lahko ponazorimo s spodnjo tabelo: pogoj1 pogoj2 pogoj1 and pogoj2 pogoj1 or pogoj2 False False False False False True False True True False False True True True True True Poglejmo si öe en malo bolj konkreten primer: 17 >= 18.5 17 <= 25 17 >= 18.5 and 17 <= 25 17 >= 18.5 or 17 <= 25 False True False True V primeru, da morata biti izpolnjena oba vhodna pogoja, torej uporabimo operator and. e je dovolj, da je izpolnjen samo eden izmed vhodnih pogojev, uporabimo operator or. Pogosto uporabljen logi ni operator je öe operator not, ki True spremeni v False in obratno: >>> not True False >>> not False True >>> 17 >= 18.5 False >>> not 17 >= 18.5 True Zdaj lahko dokon amo zgled z izpisovanjem podatkov o ITM. Zgled 4. Napiöi program, ki od uporabnika prebere telesno maso in viöino in izpiöe uporabnikov indeks telesne mase (ITM). Poleg tega program uporabniku pove, v katero skupino spada. 3.4 Veja else 29 Reöitev 4. Zdaj lahko dodamo öe pogojni stavek, pri katerem bo pogoj sestavljen iz dveh delov. V tem primeru mora biti vrednost spremenljivke ITM ve ja ali enaka od 17.5 in manjöa ali enaka od 25, kar lahko zapiöemo s pogojem 17.5 <= ITM and ITM <= 25. 1 masa = float(input("Vpiöi svojo telesno maso [kg ]: ")) 2 visina = float(input("Vpiöi svojo viöino [m]: ")) 3 itm = masa/ visina **2 4 print("Tvoj ITM je", itm) 5 if ITM < 17.5: 6 print(" Tvoja telesna masa je premajhna ") 7 if ITM > 25: 8 print(" Tvoja telesna masa je prevelika ") 9 if 17.5 <= ITM and ITM <= 25: 10 print(" Tvoja telesna masa je ustrezna ") — Programiranja se u imo v jeziku Python med drugim tudi zato, ker ima kar nekaj uporabnih sladkor kov (funkcionalnosti), ki jih drugi jeziki nimajo. Sestavljen pogoj 17.5 <= ITM and ITM <= 25 lahko v tem jeziku zapiöemo tudi malo krajöe, in sicer takole: 17.5 <= ITM <= 25. 3.4 Veja else Zgornji program je sicer pravilen, ni pa najlepöi. V primeru, da je npr. izpolnjen prvi pogoj, tj. ITM < 17.5, ni nobene potrebe po tem, da preverjamo öe izpolnjenost drugega in tretjega pogoja. To sicer v tem primeru ni narobe (lahko bi bilo), je pa nepotrebno in po eni strani naredi naöo kodo manj pregledno, po drugi strani pa trati dragocen procesorski as, saj preverja, e je dolo en pogoj izpolnjen, kljub temu, da vemo, da zagotovo ne more biti. Potek programa, ki smo ga napisali zgoraj, ponazarja slika 3.2. Do sedaj smo v primeru neizpolnjenosti pogoja vedno sko ili na del skupni stavki, torej na del, ki se izvede neodvisno od izpolnjenost pogoja. V sploönem pa stavek if omogo a, da del kode izvedemo samo takrat, ko pogoj ni izpolnjen. To kodo podamo v veji else stavka if: if pogoj : # pogojni stavki # e je pogoj izpolnjen ... else: # pogojni stavki 30 Poglavje 3 Pogojni stavek False True pogoj1 pogoj1 i polnjen skupni stavki False True pogoj2 pogoj2 i polnjen skupni stavki Slika 3.2 Izpolnjenost pogoja pogoj2 se preverja ne glede na izpolnjenost pogoja pogoj1. # e pogoj ni izpolnjen ... # skupni stavki ... Potek izvajanja zgornje kode prikazuje slika 3.3. Uporabimo zgornji stavek za poenostavljeno reöitev naloge z izpisovanjem podatkov o ITM. Zgled 5. Napiöi program, ki od uporabnika prebere telesno maso in viöino in izpiöe uporabnikov indeks telesne mase (ITM). Poleg tega program uporabniku pove, e je njegova telesna masa ustrezna ali ne. Reöitev 5. Tokrat bomo preverjali le pogoj o ustreznosti uporabnikove telesne mase. 1 masa = float(input("Vpiöi svojo telesno maso [kg ]: ")) 2 visina = float(input("Vpiöi svojo viöino [m]: ")) 3.5 Veja elif in gnezdenje stavkov if 31 False True g j g j i g j i a ki ( e a ki ( e je g j g j i i je ) i je ) k i a ki Slika 3.3 Dopolnitev stavka if z vejo else. Veja else se izvede samo v primeru, ko pogoj ni izpolnjen. 3 itm = masa/ visina **2 4 print("Tvoj ITM je", itm) 5 if 17.5 <= ITM <= 25: 6 print(" Tvoja telesna masa je ustrezna ") 7 else: 8 print(" Tvoja telesna masa ni ustrezna ") — 3.5 Veja elif in gnezdenje stavkov if Uporabnik zdaj ve, e je njegova telesna masa ustrezna. e njegova telesna masa ni ustrezna, informacije o tem ali je preteûak ali prelahek nima (verjetno se mu to sicer dozdeva). Zgornji primer bi torej radi dopolnili tako, da znotraj veje else izvedemo dodatno primerjanje, na podlagi katerega bomo lahko ugotovili ali je ITM prevelik ali premajhen. To lahko naredimo na dva na ina. Elegantnejöi na in je uporaba stavka elif1, ki omogo a preverjanje dodatnega pogoja znotraj veje else. Celoten stavek if z vejo elif zapiöemo takole: 1Po slovensko bi lahko stavku elif rekli sicer pa, e velja. 32 Poglavje 3 Pogojni stavek if pogoj1 : # pogojni stavki # pogoj1 izpolnjen ... elif pogoj2 : # pogojni stavki # pogoj1 ni izpolnjen # pogoj2 izpolnjen ... else: # nobeden izmed pogojev # ni izpolnjen # skupni stavki ... V tem primeru se izpolnjenost pogoja pogoj2 preverja samo, e pogoj pogoj1 ni izpolnjen, veja else pa se izvede samo v primeru, ko ni bil izpolnjen nobeden izmed prejönjih pogojev. Potek programa prikazuje slika 3.4. False True g 1 False True g 1 g 2 e bede g 1 ed e , g e g 2 e e a Slika 3.4 Dopolnitev stavka if z vejama elif in else. Veja elif se izvede samo v primeru, ko pogoj pogoj1 ni izpolnjen, pogoj pogoj2 pa je. 3.5 Veja elif in gnezdenje stavkov if 33 Zdaj lahko kon no podamo lepöo reöitev zgleda z izpisovanjem podatkov o ITM. Zgled 6. Napiöi program, ki od uporabnika prebere telesno maso in viöino in izpiöe uporabnikov indeks telesne mase (ITM). Poleg tega program uporabniku pove, v katero skupino spada. Reöitev 6. Najprej bomo preverili enega izmed pogojev, npr. e je ITM manjöi od 17.5. V primeru, da je pogoj izpolnjen, izpiöemo, da je telesna masa premajhna. V veji elif lahko preverimo naslednji pogoj, npr. e je ITM ve ji od 25. V primeru, da je izpolnjen ta pogoj, izpiöemo, da je telesna masa prevelika. e ni izpolnjen nobeden izmed obeh pogojev, lahko izpiöemo, da je telesna masa ustrezna. 1 masa = float(input("Vpiöi svojo telesna masa [kg ]: ")) 2 visina = float(input("Vpiöi svojo viöino [m]: ")) 3 itm = masa/ visina **2 4 print("Tvoj ITM je", itm) 5 if ITM < 17.5: 6 print(" Tvoja telesna masa je premajhna ") 7 elif ITM > 25: 8 print(" Tvoja telesna masa je prevelika ") 9 else: 10 print(" Tvoja telesna masa je ustrezna ") — Drug pristop k reöevanju enakega problema je uporaba dodatnega stavka if znotraj veje else. Temu re emo tudi gnezdenje ali ugnezdeni stavek if. Kodo bi napisali takole: if pogoj1 : # pogojni stavki # pogoj1 izpolnjen ... else:if pogoj2: # tukaj so uporabljeni dvojni zamiki # pogojni stavki # pogoj1 ni izpolnjen # pogoj2 izpolnjen ... else: # tukaj so uporabljeni dvojni zamiki # nobeden izmed pogojev # ni izpolnjen 34 Poglavje 3 Pogojni stavek # skupni stavki ... Za etek ugnezdenega stavka if je zamaknjen enkrat, s imer povemo, da naj se izvede samo v primeru, ko pogoj pogoj1 ni izpolnjen. Vsebino ugnezdenega stavka moramo zamakniti dvakrat. Izvedba zgornje kode bo enaka kot v primeru z uporabo veje elif in jo prikazuje slika 3.4. Uporabimo ugnezden stavek if öe pri reöevanju naöe naloge. Zgled 7. Napiöi program, ki od uporabnika prebere telesno maso in viöino in izpiöe uporabnikov indeks telesne mase (ITM). Poleg tega program uporabniku pove, v katero skupino spada. Uporabi ugnezden stavek if. Reöitev 7. Potek programa bo podoben kot prej za razliko od gnezdenja stavka if. 1 masa = float(input("Vpiöi svojo telesno maso [kg ]: ")) 2 visina = float(input("Vpiöi svojo viöino [m]: ")) 3 itm = masa/ visina **2 4 print("Tvoj ITM je", itm) 5 if ITM < 17.5: 6 print(" Tvoja telesna masa je premajhna ") 7 else: 8 if ITM > 25: 9 print(" Tvoja telesna masa je prevelika ") 10 else: 11 print(" Tvoja telesna masa je ustrezna ") — 4 Zanka while 4.1 Kaj so zanke? Z uporabo stavka if lahko torej izbrane stavke izvedemo samo v primeru, ko je nek pogoj izpolnjen. V asih pa bi ûeleli izbrane stavke izvajati vse dokler (angl. while) je nek pogoj izpolnjen. To omogo ajo zanke. V slede em poglavju si bomo podrobneje pogledali zanko while. 4.2 Zanka while Razliko med izvedbo pogojnega stavka if in zanko while prikazuje slika 4.1. Slika 4.1 Razlika med izvedbo pogojnega stavka if ( rtkana linija rde e barve) in zanko while (polna linija rde e barve). Po izvedbi pogojnega dela stavka if se izvajanje programa nadaljuje v delu, ki sledi pogojnemu stavku. Po drugi strani se po izvedbi pogojnega dela zanke while, recimo tem stavkom raje telo zanke, izpolnjenost pogoja v glavi zanke (glej primer v nadaljevanju) ponovno preveri. Telo oziroma vsebina zanke se bo torej izvajalo vse dokler bo pogoj izpolnjen. Zanko while lahko zapiöemo takole: while pogoj : # glava zanke 35 36 Poglavje 4 Zanka while # telo zanke ... # nadaljevanje programa ... Zapis zanke while je torej zelo podoben zapisu stavka if. Glavi zanke sledi telo zanke oziroma njena vsebina, ki se izvaja vse dokler je pogoj izpolnjen. Enemu obhodu zanke pravimo tudi iteracija zanke. Pogoje za izvedbo nove iteracije zanke lahko zapisujemo na popolnoma enak na in kot pri stavku if. Prav tako kot pri stavku if vsebino zanke definiramo tako, da stavke znotraj telesa zanke zamikamo. Izvajanje zanke while ponazarja diagram poteka na sliki 4.2. Slika 4.2 Potek izvajanja zanke while. Zanke torej lahko uporabljamo takrat, ko ûelimo nekaj ponavljati, dokler je dolo en pogoj izpolnjen. Npr., dokler uporabnik ne poda veljavnega vnosa, dokler ni spremenljivka – ötevec, ki öteje koliko ponovitev smo ûe naredil, dosegla dolo ene vrednosti ali pa dokler sta ötevili razli ni. 4.3 ätetje z zanko while Zanko while bi lahko uporabili torej tudi za ötetje. Za ta namen je sicer boljöa zanka for, ki jo bomo spoznali malo kasneje. Poglejmo si zgled. Zgled 8. Napiöi program, ki öteje od 0 do ötevila, ki ga je vnesel uporabnik. Vsa ötevila naj program tudi izpiöe Reöitev 8. ätevilo, do katerega smo ûe preöteli, si bomo morali nekam zabeleûiti, npr. v spremenljivko z imenom i. äteti bomo glede na navodila za eli s ötevilom 0. Torej bomo spremenljivko i na za etku postavili na vrednost 0. Kon ali bomo s ötevilom, ki ga je vnesel uporabnik, recimo n. Pogoj za ötetje naprej bo torej i 4.4 Iskanje najve jega skupnega delitelja 37 <= n. Znotraj zanke while bomo trenutno ötevilo (i) izpisali, poleg tega moramo trenutno ötevilo tudi pove ati, saj bo sicer pogoj za vedno izpolnjen. 1 n = int(input("Vpiöi ö tevilo do katerega ûeliö öteti: ")) 2 i = 0 # ötevec s katerim bomo öteli 3 while i <= n: # smo ze preöteli do konca? 4 print(i) 5 i = i + 1 # pove anje ötevca za 1 — 4.4 Iskanje najve jega skupnega delitelja Povadimo uporabo zanke while na programu, ki poiö e najve ji skupni delitelj dveh ötevil. Tega bi malo teûje napisali z zanko for (vsaj v tej obliki). Zgled 9. Napiöi program, ki od uporabnika prebere dve celi ötevili in poiö e najve ji skupni delitelj teh dveh ötevil z uporabo Evklidovega algoritma. Reöitev 9. Osnovna razli ica Evklidovega algoritma deluje tako, da manjöe ötevilo odöteva od ve jega, dokler sta ötevili razli ni. Ko ötevili postaneta enaki, smo naöli skupnega delitelja. Program bo torej manjöe ötevilo odöteval od ve jega, dokler sta ötevili razli ni. Uporabili bomo zanko while (dokler sta ötevili razli ni). Znotraj zanke bomo uporabili öe stavek if, s pomo jo katerega bomo ugotovili, katero ötevilo je manjöe. Ko bosta ötevili postali enaki, bomo enega izmed njih izpisali (vseeno katerega, ker sta enaki), saj ta predstavlja najve ji skupni delitelj ötevil, ki sta bili podani na za etku. 1 st1 = int(input(" Vnesi prvo ö tevilo : ")) 2 st2 = int(input(" Vnesi drugo ö tevilo : ")) 34 while st1 != st2: # dokler sta ötevili razli ni 5 if st1 < st2: # drugo ötevilo je ve je 6 st2 = st2 - st1 7 else: # prvo ötevilo je ve je 8 st1 = st1 - st2 9 10 # konec vsebine zanke 11 # ötevili sta tu enaki , zato je vseeno katero izpiöem 12 print(st1) Opomba: vsebina stavka if je zamaknjena dvakrat, saj je zapisana tako znotraj stavka if kot tudi znotraj zanke while! — 38 Poglavje 4 Zanka while 4.5 Stavek += Znotraj zanke smo ötevec pove ali za 1 z izvedbo prireditvenega stavka i = i + 1 # pove anje ötevca za 1 Ker je tak na in pove evanja vrednosti zelo pogost, v jeziku Python obstaja bliûnjica i += 1 # pove anje ötevca za 1 Bistvo zgornjega stavka je, da izvedemo aritmeti no operacijo seötevanja in rezultat priredimo spremenljivki, nad katero smo operacijo izvedli. Na podoben na in lahko operator prirejanja = kombiniramo z drugimi aritmeti nimi operatorji in ötevili: >>> x = 10 >>> x += 1 # pove aj za 1 >>> x 11 >>> x -= 2 # zmanjöaj za 2 >>> x 9>>> x *= 5 # pomnoûi s 5 >>> x 45 >>> x /= 9 # deli z 9 >>> x 5.0 >>> x **= 2 # potenciraj na 2 >>> x 25.0 4.6 Neskon na zanka Kaj pa bi se zgodilo, e bi ötevec v prejönjem zgledu znotraj zanke pozabili pove ati? Spremenljivka i (oziroma ötevec) bi ostala na vrednosti 0 ne glede na to koliko iteracij zanke bi se izvedlo. To pomeni, da bi bil pogoj za vedno izpolnjen (True). Kdaj bi se taka zanka kon ala? Ker je pogoj vedno resni en, se taka zanka nikoli ne kon a in tak program je potrebno kon ati na silo (v okolju Python je temu namenjena kombinacija tipk ctrl + c). Na take stvari moramo torej pri programiranju z zanko while paziti. Zanki, ki se nikoli ne kon a, pravimo neskon na zanka (angl. infinite loop). 4.7 Stavek break 39 4.7 Stavek break Zanko pa lahko prekinemo tudi druga e kot z neizpolnjenostjo pogoja v glavi zanke. Uporabimo lahko namre stavek break, ki prekine izvajanje zanke brez preverjanja pogoja v glavi zanke. Primer uporabe stavka break ponazarja spodnja koda while pogoj : # telo zanke ... if dodaten_pogoj : break # prekine izvajanje zanke # nadaljevanje programa ... Izvedbo primera prikazuje slika 4.3. Slika 4.3 Primer uporabe stavka break znotraj zanke while. Stavek break ponavadi uporabljamo v kombinaciji z dodatnim pogojem. V primeru, da je slednji izpolnjen, se izvajanje zanke prekine pred asno. Poglejmo si to na enostavnem zgledu. Zgled 10. Napiöi program, ki od uporabnika sprejme njegovo ime in ga pozdravi. Program naj uporabnika ne spusti naprej, dokler ne vnese veljavnega imena. Pri tem naj bo veljavno vsako ime, ki je razli no od praznega niza. Reöitev 10. Program lahko napiöemo tako, da od uporabnika preberemo ime in ga shranimo kot niz. Potem izvajamo zanko while vse dokler je niz prazen. V telesu 40 Poglavje 4 Zanka while zanke moramo uporabniku seveda dati moûnost, da vnese nekaj drugega kot prazen niz (sicer smo spet pri neskon ni zanki). Za zanko lahko uporabnika pozdravimo, saj tukaj niz zagotovo ni ve prazen. 1 ime = input(" Vnesi ime: ") 2 # e je vnos pravilen , se telo zanke nikoli ne izvede 3 while ime == "": # lahko bi pisali tudi while not ime: 4 # popravni izpit 5 print(" Vnesel si prazen niz!") 6 ime = input(" Vnesi ime: ") 7 # zdaj ime zagotovo ni prazen niz 8 print(" Pozdravljen (a)", ime) Kar ni najlepöe pri tej reöitvi je ponavljanje iste kode na dveh mestih ( ime = input("Vnesi ime: ") ). Stvar lahko reöimo tako, da naredimo neskon no zanko, znotraj katere beremo ime. e je prebrano ime neprazen niz, zanko prekinemo. Kako, e je pa zanka neskon na? Enostavno. S stavkom break. Kako pa naredimo neskon no zanko? Tako, da tej nastavimo pogoj, ki bo vedno resni en. Kot pogoj bi lahko v tem primeru napisali 1>0 , "a"=="a" ali pa kar True . 1 while True: # vedno izpolnjen pogoj 2 ime = input(" Vnesi ime: ") 3 if ime: # enako kot if ime != "" 4 break 5 # e smo priöli do tu , je niz prazen 6 print(" Vnesel si prazen niz!") 7 print(" Pozdravljen (a)", ime) — Poglejmo si öe en malo teûji zgled z uporabo stavka break. Zgled 11. Napiöi program, ki od uporabnika prebere dve celi ötevili in izpiöe, e sta ötevili tuji. ätevili sta tuji, e nimata nobenega skupnega delitelja, ki je ve ji od 1. Reöitev 11. Nalogo bi lahko reöili tako, da bi poiskali najve ji skupni delitelj podanih ötevil (naj bosta to ötevili st1 in st2) in pogledali, e je ta ve ji od 1. Tokrat se bomo reöitve lotili na malo druga en na in. Preverili bomo, e med kandidati za skupne delitelje obstaja kaköno ötevilo, ki deli obe ötevili, pri emer bodo kandidati v razponu od ötevila 2 do manjöega ötevila, torej do vrednosti min(st1, st2). V primeru, da med kandidati najdemo eno ötevilo, ki deli obe podani ötevili (ostanek po deljenju posameznega ötevila s kandidatom je enak 0 (st % delitelj == 0), lahko takoj izpiöemo, da ötevili nista tuji. 4.7 Stavek break 41 1 st1 = int(input(" Vnesi prvo ö tevilo : ")) 2 st2 = int(input(" Vnesi drugo ö tevilo : ")) 34 delitelj = 2 # za etna vrednost kandidata 56 # kandidat/delitelj gre do manjöega 7 while delitelj <= min(st1 , st2 ): 8 # ali delitelj deli obe ötevili? 9 if st1 % delitelj == 0 and st2 % delitelj == 0: 10 print("ä tevili nista tuji") 11 delitelj += 1 Reöitev je öe nepopolna, saj izpis poda samo v primeru, ko ötevili nista tuji. Kako bi lahko program dopolnili, tako da bi izpis podal tudi v primeru, ko sta ötevili tuji. Tak izpis lahko podamo samo v primeru, ko smo pregledali vse kandidate in nismo naöli nobenega, ki deli obe ötevili. Pomagamo si lahko s pomoûno spremenljivko tipa bool, v katero bomo shranili informacijo o tem, ali smo ûe naöli kaönega delitelja. Pri tem bomo na za etku predpostavljali, da delitelja ni. e ga bomo naöli, bomo predpostavko popravili. Na koncu bomo preverili, e smo kakönega delitelja naöli. e bo odgovor ne (nasli == False), bomo izpisali, da sta si ötevili tuji. 1 st1 = int(input(" Vnesi prvo ö tevilo : ")) 2 st2 = int(input(" Vnesi drugo ö tevilo : ")) 34 delitelj = 2 # za etna vrednost kandidata 5 # predpostavljamo , da skupnega delitelja öe nismo naöli: 6 nasli = False 78 # kandidat/delitelj gre do manjöega 9 while delitelj <= min(st1 , st2 ): 10 # ali delitelj deli obe ötevili? 11 if st1 % delitelj == 0 and st2 % delitelj == 0: 12 print("ä tevili nista tuji") 13 nasli = True # popravimo predpostavko 14 delitelj += 1 15 16 # e do tu skupnega delitelja nismo naöli , potem ga ni 17 if nasli == False : 18 print("ä tevili sta tuji") Program sicer deluje pravilno, je pa njegov izpis mote , v primeru, da najdemo ve skupnih deliteljev dveh ötevil. Vsaki , ko najdemo skupnega delitelja, namre izpiöemo, da smo ga naöli. Poleg tega bi lahko izvajanje zanke while prekinili takoj, 42 Poglavje 4 Zanka while ko smo naöli enega skupnega delitelja, saj je to zadosten pogoj, da si ötevili nista tuji. Uporabimo lahko torej stavek break. Kon na reöitev bo slede a. 1 st1 = int(input(" Vnesi prvo ö tevilo : ")) 2 st2 = int(input(" Vnesi drugo ö tevilo : ")) 34 delitelj = 2 # za etna vrednost kandidata 5 # predpostavljamo , da skupnega delitelja nismo naöli: 6 nasli = False 78 # kandidat za skupni delitelj gre do manjsega 9 while delitelj <= min(st1 , st2 ): 10 # ali delitelj deli obe ötevili? 11 if st1 % delitelj == 0 and st2 % delitelj == 0: 12 print("ä tevili nista tuji") 13 nasli = True # popravimo predpostavko 14 break # lahko prenehamo z iskanjem 15 delitelj += 1 16 17 # e do tu skupnega delitelja nismo naöli , potem ga ni 18 if nasli == False : 19 print("ä tevili sta tuji") — 4.8 Veja else Ena izmed posebnosti jezika Python je tudi to, da lahko vejo else uporabljamo tudi v kombinaciji z zanko while. Takole: while pogoj : # telo zanke ... else: # ko pogoj ni ve izpolnjen ... # nadaljevanje programa ... Veja else se torej izvede, ko pogoj ni ve izpolnjen. Vpraöanje pa je ali se veja else izvede vsaki , ko se izvajanje zanke zaklju i? Kaköna je razlika med stavki, ki sledijo zanki while, in stavki znotraj veje else zanke while? 4.8 Veja else 43 Do bistvene razlike med vejo else in obi ajnimi stavki, ki sledijo zanki while, pride, kadar zanko prekinemo s stavkom break. V tem primeru namre sko imo iz zanke, s imer presko imo tudi njeno else vejo. Slednja se izvede samo v primeru, ko smo zanko prekinili po obi ajni poti, tj. z neizpolnjenostjo pogoja v glavi zanke. while pogoj : # telo zanke ... if dodaten_pogoj : break # prekini izvajanje zanke else: # e zanka ni bila prekinjena z break # ko pogoj ni ve izpolnjen ... # nadaljevanje programa ... Delovanje zgornjega programa ponazarja slika 4.4. Slika 4.4 Primer uporabe stavka break v kombinaciji z vejo else za zanko while. Z vejo else lahko dolo ene stavke po zaklju ku zanke while izvedemo samo v primeru, ko zanka ni bila prekinjena s stavkom break. Povadimo na prejönjem zgledu öe tole. Zgled 12. Napiöi program, ki od uporabnika prebere dve celi ötevili in izpiöe, e sta ötevili tuji. ätevili sta tuji, e nimata nobenega skupnega delitelja, ki je ve ji od 1. Reöitev 12. Del programa, kjer izpisujemo, da si ötevili nista tuji, bo ostal bolj ali manj nespremenjen. Skrajöamo pa lahko tiste dela programa, ki jih potrebujemo 44 Poglavje 4 Zanka while za izpis, da sta si ötevili tuji. ätevili sta si tuji, ko nismo naöli nobenega skupnega delitelja. To se zgodi takrat, ko se je zanka while odvrtela do konca in nismo naöli nobenega skupnega delitelja, torej je posledi no nismo prekinili s stavkom break. e zanko while dopolnimo z vejo else, se bo ta izvedla natanko takrat, ko zanka ni bila prekinjena s stavkom break, torej takrat, ko nismo naöli nobenega skupnega delitelja. Znotraj veje else lahko torej zapiöemo, da sta si ötevili tuji. Na tak na in se lahko znebimo spremenljivke nasli in naredimo program krajöi in bistveno bolj pregleden. 1 st1 = int(input(" Vnesi prvo ö tevilo : ")) 2 st2 = int(input(" Vnesi drugo ö tevilo : ")) 34 delitelj = 2 # za etna vrednost kandidata 56 # kandidat/delitelj gre do manjöega 7 while delitelj <= min(st1 , st2 ): 8 # ali delitelj deli obe ötevili? 9 if st1 % delitelj == 0 and st2 % delitelj == 0: 10 print("ä tevili nista tuji") 11 break # lahko prenehamo z iskanjem 12 delitelj += 1 13 else: # ali se je zanka odvrtela do konca 14 # zanke nismo prekinili s stavkom break 15 print("ä tevili sta tuji") — 5 Seznami in metode 5.1 Sekven ni podatkovni tipi Podatkovni tipi, ki smo jih sre ali do sedaj, so bili ve inoma namenjeni temu, da vanje shranimo posamezen (en) podatek. V spremenljivko, ki je pripadala podatkovnemu tipu int, smo npr. lahko shranili eno ötevilo. V dolo enih primerih pa se sre amo z veliko koli ino med seboj podobnih podatkov, nad katerimi ûelimo izvajati enake ali podobne operacije. V praksi bi to lahko pomenilo, da izvajamo ponavljajo e meritve enake koli ine, npr. dolûine skoka smu arjev skakalcev. Kaj narediti v takem primeru? Na podlagi naöega dosedanjega znanja bi lahko za vsakega skakalca definirali svojo spremenljivko, kar pa ne bi bila ravno najboljöa reöitev. Prvi problem tega pristopa bi bil, da je lahko skakalcev zelo veliko. V primeru skakalcev bi bila stvar mogo e öe lahko obvladljiva, kaj pa e npr. merimo prisotnost transkriptov genov v celici, ki ima par tiso genov? Drugi problem je ta, da moramo vsako izmed spremenljivk obravnavati lo eno, kar nas bo pripeljalo do ogromne koli ine nepregledne ponavljajo e se kode. Tretji problem tega pristopa je, da v asih ne vemo isto to no koliko meritev bomo imeli in koliko spremenljivk bo potrebno definirati (koliko bo skakalcev, koliko je genov v opazovani celici) in zato teûko povemo koliko spremenljivk moramo posebej obravnavati. K sre i pa obstajajo t.i. sekven ni podatkovni tipi, v katere lahko shranjujemo ve jo koli ino podatkov oziroma ve kot en podatek. Prednost uporabe sekven nih podatkovnih tipov je ta, da lahko podatke sproti dodajamo in ne potrebujemo vnaprej definirati ötevila podatkov, ki jih bomo na koncu imeli. Mimogrede, tudi nizi so sekven ni podatkovni tipi, saj lahko vanje shranjujemo ve jo koli ino podatkov, ki v tem primeru predstavljajo znake oziroma eno rkovne nize. 5.2 Kaj so seznami? Pomemben predstavnik sekven nih podatkovnih tipov je seznam oziroma list. Za razliko od nizov lahko vanj shranimo poljubne podatke, kot so npr. ötevila, nizi in tudi drugi seznami. Dodatna prednost uporabe seznamov je ta, da lahko elemente v seznamu dodajamo sproti, zato dolûine seznama ni potrebno vnaprej definirati. Lahko torej za nemo s praznim seznamom in vsaki , ko dobimo podatek o novi 45 46 Poglavje 5 Seznami in metode meritvi, tega v seznam dodamo. Sezname definiramo z oglatimi oklepaji [ in ], znotraj katerih naötejemo elemente. Prazen seznam bi naredili takole >>> prazen_seznam = [] Seznam, ki vsebuje pribliûno naklju ne dolûine skokov smu arjev skakalcev pa takole >>> dolzine = [121.4 , 143.1 , 105.2 , 99.3] V isti seznam bi lahko zapisali tudi razli ne podatkovne tipe, npr. 3 cela ötevila, 1 decimalno ötevilo, 5 nizov itd., eprav v praksi tega ne sre amo pogosto. Ponavadi v sezname shranjujemo podatke, ki pripadajo istemu podatkovnemu tipu, saj se ti podatki nanaöajo na ponavljajo e izvajanje npr. dolo ene meritve. Na koncu lahko zato z uporabo seznamov izvedemo dolo ene statistike, npr. kdo je sko il najdlje, kaköna je povpre na dolûina skoka, koliko ljudi je sko ilo itd. 5.3 Indeksiranje seznamov Seznami so urejeni. To pomeni, da je vrstni red, v katerem naötejemo elemente seznama, pomemben. Vsak element v seznamu ima namre svoj indeks. Pri tem se indeksiranje za ne s ötevilom 01, indeksi pa vedno predstavljajo cela ötevila in se (od leve proti desni) pove ujejo za 1. Indeksi bodo torej öli od ötevila 0 do dolûine seznama – 1. V primeru zgoraj definiranega seznama dolzine gredo torej indeksi od 0 do 3, saj seznam vsebuje 4 elemente: indeksi 0 1 2 3 dolzine = [121.4, 143.1, 105.2, 99.3] Do elementa na dolo enem indeksu lahko pridemo z indeksiranjem, ki ga izvedemo tako, da za imenom spremenljivke indeks zapiöemo v oglatih oklepajih: ime_seznama [ indeks ] Do dolûine skoka 0-tega skakalca (tistega, ki je v seznamu na za etku) bi torej priöli takole: >>> dolzine [0] 121.4 Kaj pa do zadnjega skakalca (tistega, ki je v seznamu na koncu)? Do dolûine seznama lahko pridemo preko vgrajene funkcije len: >>> len( dolzine ) 4 1V ra unalniötvu ponavadi za nemo ötetje s ötevilom 0 in ne s ötevilom 1, kot smo sicer navajeni. 5.4 Operatorji nad seznami 47 Funkcijo lahko torej uporabimo pri indeksiranju, kadar ne vemo to no, koliko elementov ima seznam. Do zadnjega elementa torej pridemo takole: >>> dolzine [len( dolzine ) -1] 99.3 Zakaj moramo od dolûine seznama odöteti 1? Ker smo za eli öteti s ötevilom 0, bo zadnji indeks enak dolûini seznama – 1. Kaj pa e vseeno poskusimo indeksirati po indeksu, ki ga v seznamu ni? V tem primeru seveda dobimo napako: >>> dolzine [len( dolzine )] IndexError : list index out of range Kot smo do zdaj ûe ve krat videli ima Python veliko lepih lastnosti. Ena izmed njih je tudi ta, da lahko uporabljamo negativno indeksiranje, pri emer indeks -1 predstavlja zadnji element, indeks -2 predzadnji in tako naprej. Dolûine skokov imajo torej tudi negativne indekse: indeksi -4 -3 -2 -1 dolzine = [121.4, 143.1, 105.2, 99.3] Prednost takega na ina indeksiranja je v tem, da lahko na zelo enostaven na in pridemo do zadnjega elementa seznama (brez funkcije len): >>> dolzine [ -1] 99.3 Mimogrede, podobno kot lahko indeksiramo elemente seznamov, lahko indeksiramo tudi elemente nizov. Prav tako lahko dolûino niza preverimo s funkcijo len. >>> niz = " banana " >>> niz [0] "b" >>> niz [ -1] "a" >>> len(niz) 6 5.4 Operatorji nad seznami Nad seznami lahko uporabimo razli ne operatorje, ki smo jih do zdaj uporabljali ûe npr. nad nizi. Nize smo npr. lahko med seboj seötevali (temu smo sicer rekli konkatenacija oziroma lepljenje). Med seboj lahko seötevamo tudi sezname: >>> [1 ,2 ,3] + [4 ,5 ,6] [1 ,2 ,3 ,4 ,5 ,6] Ne moremo pa seznamom priöteti ne esa, kar ni seznam, npr. ötevila: 48 Poglavje 5 Seznami in metode >>> [1 ,2 ,3]+4 TypeError : can only concatenate list (not "int") to list Lahko pa sezname mnoûimo s celimi ötevili: >>> [1 ,2 ,3]*4 [1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3] S im drugim jih ni smiselno mnoûiti, zato Python tega ne podpira: >>> [1 ,2 ,3]*[4 ,5 ,6] TypeError : can ’t multiply sequence by non -int of type ’list ’ Nad seznami lahko uporabimo tudi operatorja vsebovanosti in in not in, ki vrneta True ali False v odvisnosti od tega ali je nekaj v seznamu vsebovano ali ne: >>> 2 in [1 ,2 ,3] True >>> 4 in [1 ,2 ,3] False >>> 2 not in [1 ,2 ,3] False Sezname lahko primerjamo z drugimi seznami z uporabo primerjalnih operatorjev. Takole preverjamo enakost oziroma neenakost dveh seznamov: >>> [1 ,2 ,3] == [1 ,2 ,3] True >>> [1 ,2 ,3] != [1 ,2 ,3] False Lahko tudi ugotavljamo, e je prvi seznam manjöi od drugega (besedico manjöi bi lahko zamenjali tudi z ve ji, manjöi ali enak ter ve ji ali enak): >>> [1 ,2 ,3] < [1 ,2 ,3 ,4] True >>> [1 ,3 ,3] < [1 ,2 ,3] False Primerjalni operatorji nad seznami delujejo podobno kot nad nizi, in sicer gre za leksikografsko primerjanje. Leksikografsko primerjanje je npr. uporabljeno pri sortiranju besed v slovarju. Deluje tako, da med seboj primerjamo istoleûne elemente seznama, dokler ne pridemo do neenakosti oziroma do konca enega izmed obeh seznamov. V zgornjem primeru smo priöli do konca prvega seznama. Ker je nekaj kar ne obstaja na eloma manjöe kot nekaj kar obstaja, je Python vrnil, da je prvi seznam manjöi od drugega. V drugem primeru se je primerjanje ustavilo pri elementih na indeksu 1, saj sta elementa na tem indeksu razli na. Ker 3 ni manjöe od 2, je Python ugotovil, da prvi seznam ni manjöi od drugega in vrnil rezultat False. 5.5 Spreminjanje in brisanje elementov seznama 49 5.5 Spreminjanje in brisanje elementov seznama Videli smo ûe, da lahko do elementov seznama dostopamo preko indeksiranja. Preko indeksiranja pa lahko vrednosti v seznamih tudi spreminjamo. Kako? Tako, da indeksiranje seznama dopolnimo s prireditvenim stavkom: seznam [ indeks ] = nova_vrednost Tudi brisanje elementov iz seznama lahko izvajamo s pomo jo indeksiranje, le da tokrat pred indeksiranjem uporabimo besedico del: del seznam [ indeks ] 5.6 Vgrajene funkcije nad seznami Sre ali smo ûe funkcijo len, s pomo jo katere lahko ugotovimo kaköna je dolûina seznama. Nad seznami pogosto uporabljamo öe druge vgrajene funkcije, izmed katerih so pogosto uporabljene min, max in sum. Funkcija min vrne najmanjöi, funkcija max pa najve ji element v seznamu glede na relacijo <. Zdaj lahko kon no ugotovimo kaköna je bila dolûina najdaljöega skoka: >>> max( dolzine ) 143.1 Izra unamo lahko tudi povpre no doûino skoka >>> sum( dolzine )/ len( dolzine ) 117.25 Nad seznami lahko uporabimo öe druge vgrajene funkcije. Nekatere izmed njih bomo sre ali kasneje, druge pa boste zagotovo naöli, e se bo taköna potreba pokazala. 5.7 Metode Nad seznami lahko torej uporabljamo vgrajene funkcije, ki so v Pythonu na voljo. Te funkcije lahko sicer uporabimo na poljubnem podatku, ki ni nujno seznam. Obstaja poseben nabor funkcij, ki pa jih lahko uporabljamo samo nad seznami. Tem funkcijam pravimo metode seznamov. V sploönem se izraz metode uporablja za posebno druûino funkcij, ki pripadajo dolo enemu objektu. Kaj je objekt ne bomo podrobneje razlagali. Lahko pa povemo, da so seznami objekti (pravzaprav je skoraj vse v Pythonu objekt). Kakorkoli ûe, metode so tiste funkcije, ki pripadajo dolo enemu objektu. Do posamezne metode seznama lahko pridemo s spodnjim klicem: ime_seznama . ime_metode ( argumenti ) 50 Poglavje 5 Seznami in metode Klic metode je torej podoben klicu obi ajne funkcije, le da moramo pred imenom metode podati ime objekta, preko katerega oziroma nad katerim metodo kli emo, imeni pa lo imo s piko (.). e delamo v okolju IDLE ali v kakönem öe pametnejöem okolju, nam bo to po izpisu imena seznama in pike podalo nabor metod, ki jih imamo na razpolago. Ko v okolju IDLE npr. napiöemo >>> dolzine . se pojavijo imena metod, ki jih lahko nad seznamom uporabimo: append, copy, clear itd. Metode torej razöirjajo vgrajene funkcije okolja Python in so vezane na to no dolo en podatkovni tip, kateremu izbrani objekt pripada. e bi npr. enako stvar kot zgoraj poskusili z nizom, bi dobili drug seznam metod, ki jih lahko poûenemo nad nizom. Metodam kot argument za razliko od vgrajenih funkcij ni potrebno podati seznama (ali pa niza) nad katerim jih ûelimo izvesti, saj smo seznam (ali pa niz) podali ûe pred piko – ûe s samim klicem smo povedali nad im ûelimo metodo pognati. Metode vseeno velikokrat vsebujejo dolo ene argumente, ki dolo ijo kaj in kako naj metoda nad izbranim objektom naredi. Prav tako kot obstaja kar veliko vgrajenih funkcij, obstaja tudi veliko metod nad seznami. Natan neje si bomo v nadaljevanju tega poglavja pogledali tiste, ki jih uporabljamo pogosteje. 5.8 Dodajanje elementov Dodajanje elementov v seznam je pogosta operacija, zato jo lahko izvedemo na ve na inov. Enega smo pravzaprav ûe sre ali, saj lahko za dodajanje elementov v seznam uporabimo kar operator +, ki omogo a lepljenje seznamov. e ûelimo element seznamu dodati, bomo obstoje emu seznamu priöteli seznam, ki vsebuje ta element. Takole: seznam = seznam + [ element ] oziroma malo lepöe: seznam += [ element ] Tole dvoje sicer ni popolnoma enako, ampak zaenkrat recimo, da je bolje uporabiti spodnjo razli ico. Elemente lahko v sezname dodajamo tudi preko metode append in metode extend. Obe metodi bosta dodajali na koncu seznama. Razlika je v tem, da v primeru metode append dodajamo en element, zato ta metoda kot argument prejme element, ki ga bomo v seznam dodali. Dodajanje bi torej izvedli takole: seznam . append ( element ) 5.8 Dodajanje elementov 51 Metoda sicer ne bo ni esar vrnila, bo pa naö seznam spremenila. Primer uporabe je slede : >>> seznam = [1 ,2 ,3] >>> seznam . append (4) >>> seznam [1 ,2 ,3 ,4] Podobno lahko uporabimo metodo extend, ki v seznam dodaja drug seznam. Kot argument moramo torej metodi extend podati seznam, ki ga ûelimo v obstoje seznam dodati. Takole: seznam . extend ( seznam2 ) Oziroma na prejönjem zgledu takole: >>> seznam = [1 ,2 ,3] >>> seznam . extend ([4]) >>> seznam [1 ,2 ,3 ,4] Tokrat smo morali element, ki smo ga v seznam dodajali, zapakirati v drug seznam. Z metodo extend bi seveda lahko dodali tudi ve elementov naenkrat. Takole: >>> seznam = [1 ,2 ,3] >>> seznam . extend ([4 ,5 ,6]) >>> seznam [1 ,2 ,3 ,4 ,5 ,6] Povadimo dodajanje elementov v seznam na prakti nem zgledu. Zgled 13. Napiöi program, ki ga bo lahko uporabil sodnik smu arskih skokov. Program naj sodnika spraöuje po dolûini skoka. V primeru, da sodnik vnese ötevilo ve je od 0, naj program to ötevilo doda v seznam in sodnika vpraöa po novi dolûini. e sodnik vpiöe ötevilko 0, naj program izpiöe dolûino najdaljöega skoka in povpre no dolûino skoka. Reöitev 13. Sodnikova ötevila lahko beremo preko funkcije input, katere rezultat moramo pretvoriti v podatkovni tip float, saj so dolûine decimalna ötevila. Beremo dokler sodnik ne vnese ötevila 0, medtem pa dolûine dodajamo v seznam. Na koncu izra unamo povpre no dolûino skoka, poleg tega pa izpiöemo tudi najdaljöi skok. Program bo slede : 1 dolzine = [] # na za etku ni nobene dolûine 2 d = float(input("Vpiöi dolûino: ")) # prvo branje 3 while d > 0: # dokler je dolûina veljavna 4 dolzine . append (d) # dodaj dolûino 5 d = float(input("Vpiöi dolûino: ")) # ponovno branje 52 Poglavje 5 Seznami in metode 6 print(" Najdalj öi skok:", max( dolzine )) 7 print(" Povpre na dolûina:", sum( dolzine )/ len( dolzine )) — Prednost zgornjega programa je v tem, da deluje ne glede na to koliko skokov je v seznamu. Vse dokler sodnik ne vnese kaköne neumnosti. 5.9 Branje seznamov iz ukazne vrstice V asih bi si ûeleli celoten seznam prebrati z enim samim uporabnikovim vnosom. Torej bomo spet uporabili funkcijo input. Spomnimo se, da funkcija input uporabnikov vnos vedno zapiöe v niz oziroma v podatkovni tip str, ne glede na to kaj je uporabnik vnesel. Tak niz smo v prejönjih primerih s funkcijo int pretvorili v celo ötevilo ali pa s funkcijo float v decimalno, e smo ûeleli podan vnos v nadaljevanju obravnavati kot ötevilo. Kaj pa e bi ûeleli niz, ki ga je vnesel uporabnik, pretvoriti v seznam? Na prvo ûogo bi lahko rekli, da uporabimo funkcijo list. Poskusimo: >>> seznam = list(input(" Vnesi seznam : ")) Vnesi seznam : [1 ,2 ,3] >>> seznam [’[’, ’1’, ’,’, ’2’, ’,’, ’3’, ’]’] To ni ravno tisto, kar smo ûeleli. Dobili smo namre seznam vseh znakov, ki v podanem nizu nastopajo (vklju no z vejicami in oklepaji). Kaj bi pravzaprav radi dosegli? To, da se niz, ki ga uporabnik poda funkciji input obravnava na enak na in, kot e bi isti niz uporabnik vpisal v ukazno vrstico. Temu je namenjena funkcija eval, ki kot argument sprejme niz in ga izvede kot kodo v jeziku Python. Poskusimo öe to: >>> seznam = eval(input(" Vnesi seznam : ")) Vnesi seznam : [1 ,2 ,3] >>> seznam [1 ,2 ,3] V tem primeru stvar deluje, kot bi si ûeleli. Povadimo öe na zgledu. Zgled 14. Napiöi program, ki ga bo lahko uporabil sodnik smu arskih skokov. Programu naj sodnik poda seznam dolûin smu arskih skokov, program pa naj izpiöe dolûino najdaljöega skoka in povpre no dolûino skoka. Reöitev 14. Reöitev bo podobna kot prej, le da tokrat ne potrebujemo zanke. 5.10 Sortiranje seznamov 53 1 dolzine = eval(input(" Vnesi dolûine: ")) 2 print(" Najdalj öi skok:", max( dolzine )) 3 print(" Povpre na dolûina:", sum( dolzine )/ len( dolzine )) Slabost programa je ta, da mora sodnik zdaj vse dolûine vnesti naenkrat. — Uporaba funkcije eval je sicer lahko v dolo enih primerih nevarna ( e imamo zlobne uporabnike), saj bo slepo izvedla kodo, ki jo bo uporabnik preko vnosa podal. 5.10 Sortiranje seznamov Zaenkrat znamo dolo iti najdaljöi skok, ne znamo pa dolo iti najdaljöih treh skokov. Najdaljöe tri skoke bi lahko poiskali tako, da seznam uredimo (posortiramo), tako da recimo na manjöih indeksih vsebuje daljöe skoke oziroma, da so skoki urejeni po dolûini od najdaljöega do najkrajöega. e razpolagamo s tako urejenim seznamom, lahko za zmagovalne skoke izpiöemo skoke na indeksih 0, 1 in 2. Sortiranje seznamov lahko izvedemo z metodo sort: >>> dolzine . sort () >>> dolzine [99.3 , 105.2 , 121.4 , 143.1] Metoda sort torej sortira seznam, nad katerim smo jo poklicali, in ni esar ne vra a. Opazimo tudi, da je seznam sortirala od najmanjöe vrednosti do najve je. Najboljöe skoke bi torej lahko izpisali tako, da bi izpisali zadnje tri dolûine iz seznama. Lahko pa seznam sortiramo v obratnem vrstnem redu, tako da metodi sort nastavimo opcijski ( izbirni) argument reverse na vrednost True. Do dokumentacije metode sort lahko pridemo preko funkcije help: >>> help(list. sort ) Help on method_descriptor : sort (self , /, *, key=None , reverse = False ) Stable sort *IN PLACE *. Dokumentacija ni ni kaj preve iz rpna, vidimo pa lahko, da metoda sort sprejema tudi dva opcijska argumenta, in sicer key, ki je privzeto enak vrednosti None in reverse, ki je privzeto enak vrednosti False. Opcijski argumenti so tisti argumenti, ki imajo nastavljene privzete (angl. default) vrednosti. e ob klicu ne podamo druga nih vrednosti, bodo uporabljene privzete vrednosti. Privzete vrednosti pa lahko povozimo, tako da specificiramo druga ne vrednosti. Vrstni red urejanja bi lahko spremenili tako, da bi argument reverse nastavili na vrednost True. V naöem primeru takole: 54 Poglavje 5 Seznami in metode >>> dolzine . sort( reverse =True ) >>> dolzine [143.1 , 121.4 , 105.2 , 99.3]] Reöimo zdaj celoten zgled od za etka do konca. Zgled 15. Napiöi program, ki ga bo lahko uporabil sodnik smu arskih skokov. Programu naj sodnik poda seznam dolûin smu arskih skokov, program pa naj izpiöe najdaljöe tri skoke. Reöitev 15. Zdaj bo branju seznama sledilo sortiranje in izpis zmagovalnih dolûin. 1 dolzine = eval(input(" Vnesi dolûine: ")) 2 dolzine .sort( reverse =True) 3 print("1. mesto ", dolzine [0]) 4 print("2. mesto ", dolzine [1]) 5 print("3. mesto ", dolzine [2]) — V dokumentaciji metode sort smo videli, da ta sprejema tudi izbirni argument key. Temu argumentu lahko podamo ime funkcije, ki naj se nad posameznim elementom pokli e pred primerjanjem.2. e bi ûeleli npr. sortirati seznam po absolutnih vrednostih, bi argumentu key priredili funkcijo abs. Takole: >>> seznam = [ -100 , 10, -1, -5, 50] >>> seznam . sort(key=abs) >>> seznam [-1, -5, 10, 50, -100] Sezname (in öe kaj drugega) pa bi lahko sortirali tudi preko vgrajene funkcije sorted. Ta funkcija deluje na podoben na in kot metoda sort, le da podanega seznama ne sortira, ampak vrne sortiran seznam. Poglejmo si na zgledu: >>> seznam = [ -100 , 10, -1, -5, 50] >>> # kot argument podamo tisto kar ûelimo posortirati >>> sorted( seznam ) [-1, -5, 10, 50, -100] >>> seznam # podan seznam je ostal nespremenjen [ -100 , 10, -1, -5, 50] Funkcija torej vrne sortiran seznam, izhodiö ni seznam pa je ostal nespremenjen. Kako bi dosegli, da se ime spremenljivke, preko katerega smo funkcijo poklicali, spremeni, tako da vsebuje sortiran seznam? Tako, da bi rezultat funkcije sorted priredili spremenljivki: 2Sortiranje elementov temelji na primerjanju elementov med seboj glede na relacijo <. 5.11 Seznami seznamov 55 >>> seznam = [ -100 , 10, -1, -5, 50] >>> seznam = sorted( seznam ) >>> seznam [-1, -5, 10, 50, -100] 5.11 Seznami seznamov Vemo ûe veliko ve kot prej, öe vedno pa ne vemo kdo je sko il najve in komu moramo podeliti medaljo. Poleg dolûin bi si namre v ta namen morali beleûiti tudi imena tekmovalcev skakalcev. To lahko reöimo tako, da imamo dva seznama, tj. seznam dolûin in seznam tekmovalcev. Na istoleûnem indeksu imamo v obeh seznamih podatke o istem skakalcu. Takole: >>> dolzine = [121.4 , 143.1 , 105.2 , 99.3] >>> skakalci = [" Andrej ", " Bojan ", " Cene ", " Dejan "] Andrej je torej sko il 121.4 metra, Dejan pa zgolj 99.3 metra. Zmagovalne tri skoke öe vedno lahko dobimo tako, da sortiramo seznam dolûin: >>> dolzine . sort( reverse =True ) >>> dolzine [143.1 , 121.4 , 105.2 , 99.3] Do problema pa pride, ker zdaj ne vemo ve kateremu imenu pripada posamezna dolûina, saj smo indekse v seznamu dolûin s sortiranjem premeöali. Kaj lahko naredimo? Alternativen pristop bi bil, da za beleûenje podatkov o dolûinah in imenih uporabimo nov, ugnezden seznam. Torej naredimo seznam seznamov. Takole: >>> skoki = [[121.4 , " Andrej "], [143.1 , " Bojan "], [105.2 , " Cene "], [99.3 , " Dejan "]] Kasneje bomo za take primere sicer uporabljali malo druga no strukturo (zapis podatkov), ampak zaenkrat te öe ne poznamo. Naredili smo torej seznam seznamov. Kaj se nahaja v tem primeru na indeksu 0? >>> skoki [0] [121.4 , " Andrej "] Seznam, ki vsebuje podatke o ni tem skakalcu. Kako pa bi priöli do njegovega imena? Z uporabo veriûnega indeksiranja oziroma tako, da po indeksiranju zuna-njega seznama öe enkrat indeksiramo notranji seznam: >>> skoki [0][1] " Andrej " 56 Poglavje 5 Seznami in metode To bi lahko naredili tudi na nekoliko daljöi na in, tako da najprej dostopamo do ugnezdenega seznama (podseznama), ki vsebuje dolûino in ime, potem pa öe do imena. >>> podseznam = skoki [0] >>> podseznam [1] " Andrej " Kaj se bo zgodilo, e seznam takih podseznamov sortiramo? Nad podseznami oziroma ugnezdenimi seznami bo za sortiranje uporabljena relacija <, ki smo jo v tem poglavju v povezavi s primerjanjem seznamov ûe sre ali. Rekli smo, da relacija manjöe sezname med seboj primerja leksikografsko. Najprej primerja ni ti element prvega seznam z ni tim drugega. e sta enaka, primerja prvi element prvega seznama s prvim elementom drugega seznama in tako naprej. e bomo torej v ugnezdene sezname na ni to mesto dali dolûine na prvo mesto pa imena, bo sortiranje izvedeno po dolûinah. Po imenih bo sortiranje potekalo samo v primeru, e bosta dolûini pri dveh podseznamih enaki. Poskusimo: >>> skoki . sort ( reverse = True) >>> skoki [[143.1 , ’Bojan ’], [121.4 , ’Andrej ’], [105.2 , ’Cene ’], [99.3 , ’Dejan ’]] Ker smo zdaj sortirali dolûine skokov skupaj z imeni tekmovalcev, informacije o tem kdo je sko il koliko nismo izgubili in lahko izpiöemo zmagovalce, ki se nahajajo v prvih treh podseznamih na indeksu 1: >>> skoki [0 ,1] ’Bojan ’ >>> skoki [1 ,1] ’Andrej ’ >>> skoki [2 ,1] ’Cene ’ To so zmagovalci. Zapiöimo celoten zgled. Zgled 16. Napiöi program, ki ga bo lahko uporabil sodnik smu arskih skokov. Program naj sodnika spraöuje po dolûini skoka in imenu tekmovalca. V primeru, da sodnik za dolûino vnese ötevilo ve je od 0, naj program dolûino in ime doda v seznam. e sodnik vpiöe ötevilko 0, naj program izpiöe zmagovalce in dolûine njihovih skokov. Reöitev 16. Ponovno bomo brali dolûino po dolûino, le da bomo tokrat v primeru, ko bo vnesena dolûina ve ja kot 0, prebrali öe ime tekmovalca. Potem bomo oboje dodali v seznam skokov. Pomembno je, da na ni to mesto v podseznamu shranimo dolûino skoka, saj ûelimo podsezname sortirati po dolûini skokov. Na koncu skoke sortiramo in izpiöemo zmagovalce in dolûine zmagovalnih skokov. 5.12 Generiranje seznamov s funkcijo range 57 1 skoki = [] # na za etku ni nobenega skoka 2 d = float(input("Vpiöi dolûino: ")) # prvo branje 3 while d > 0: # dokler je dolûina veljavna 4 # branje imena 5 ime = input("Vpiöi ime tekmovalca : ") 6 # dodajanje podseznama 7 skoki . append ([d,ime ]) 8 # ponovno branje 9 d = float(input(" Vpisi dolûino: ")) 10 skoki .sort( reverse =True) # sortiranje 11 print("1. mesto :", skoki [0][1] , 12 ", dolûina skoka :", skoki [0][0]) 13 print("2. mesto :", skoki [1][1] , 14 ", dolûina skoka :", skoki [1][0]) 15 print("3. mesto :", skoki [2][1] , 16 ", dolûina skoka :", skoki [2][0]) — 5.12 Generiranje seznamov s funkcijo range Do zdaj smo sezname generirali oziroma dopolnjevali na podlagi vrednosti, ki jih je podal uporabnik. Taki seznami so lahko vsebovali poljubne elemente – tisto, kar je uporabnik vnesel. V dolo enih primerih ûelimo imeti sezname s ötevili v podanem razponu. Prakti no uporabo takih seznamov bomo podrobneje spoznali v naslednjem poglavju, zaenkrat pa si poglejmo, kako jih lahko generiramo. Generiranje seznamov v podanem razponu omogo a vgrajena funkcija range. Funkcijo range lahko pokli emo na tri razli ne na ine, in sicer preko podajanja slede ih argumentov: • start: dolo a za etek seznama (celo ötevilo), • stop: dolo a konec seznama (celo ötevilo), • step: dolo a korak (celo ötevilo). Pri prvem na inu klica funkciji range podamo zgolj argument stop. V tem primeru bomo dobili seznam vrednosti od 0 do argumenta stop–1 s korakom 1. Poskusimo: >>> razpon = range (10) >>> razpon range (0, 10) 58 Poglavje 5 Seznami in metode Tale izpis je malo uden. Poklicali smo funkcijo range in dobili range. Poglejmo si kaköen je podatkovni tip rezultata: >>> type( razpon ) < class ’range ’> Rezultat funkcije range torej pripada podatkovnemu tipu oziroma razredu range, ki nam vrednosti iz razpona vra a sproti, ko jih potrebujemo. Zakaj tako? Funkcija range je var na in ponavadi ni nobene potrebe po tem, da bi morali celoten razpon ustvariti naenkrat, ampak naenkrat potrebujemo samo en element iz razpona. S tem, ko funkcija range ustvari objekt, ki nam po potrebi vrne ûeleni element, var uje tako s procesorjevim asom (hitrost), saj je generiranje dolgih seznamov zamudno, kot tudi s pomnilniökim prostorom, saj dolgi seznami zasedejo veliko prostora. äe vedno pa lahko nad rezultatom funkcije range delamo podobne stvari, kot nad seznami. Lahko jih npr. indeksiramo: >>> razpon [2] 2 Ne moremo pa nad njimi klicati metod, ki so definirane nad obi ajnimi seznami: >>> razpon . sort () AttributeError : ’range ’ object has no attribute ’sort ’ Poleg tega funkcija print, kot smo videli ûe zgoraj, ne izpiöe vrednosti elementov v razponu. e bi ûeleli preko funkcije range dobiti obi ajen seznam, lahko uporabimo pretvorbo v seznam preko funkcije list: >>> razpon = range (10) >>> seznam = list( razpon ) >>> seznam [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> type( seznam ) < class ’list ’> Za to pa ponavadi ni potrebe. Primerjajmo ekonomi nost generiranja razpona ötevil z uporabo funkcije range in generiranja obi ajnega seznama. To lahko poskusimo tako, da s funkcijo range naredimo nek relativno velik razpon ötevil. Npr. od 0 do 108-1: >>> razpon = range (10**8) Tudi e imate po asen ra unalnik, bo generiranje razpona narejeno v trenutku. Zdaj pa poskusimo iz tega razpona narediti klasi en seznam: >>> seznam = list( razpon ) e vam Python ni javil napake MemoryError, je tole verjetno nekaj asa trajalo. e ni in vas nisem prepri al, poskusite stvar ponoviti z ve jim ötevilom, npr. 1010. 5.12 Generiranje seznamov s funkcijo range 59 Vrnimo se k osnovni uporabi funkcije range. Mogo e se spraöujete zakaj razpon ne vklju uje vrednosti stop. Razlogov za to je ve . Zaenkrat podajmo najbolj o itnega. Ker funkcija range za ne öteti z vrednostjo 0 (in ne z 1), bo razpon, ki ga bo vra ala, vseboval to no stop elementov. e bi funkciji range za argument stop podali dolûino nekega seznama, bi razpon vseboval vse indekse tega seznama (ena izmed moûnih uporab funkcije range se ûe po asi odkriva). S funkcijo range lahko generiramo razpon elementov, ki se ne za ne s ötevilom 0. V tem primeru bomo funkciji poleg argumenta stop podali öe argument start. Najprej seveda navedemo start, potem pa stop: >>> range(start , stop ) e bi npr. ûeleli generirati seznam v razponu od 5 do 10, bi napisali takole >>> razpon = range (5, 10) Poglejmo si seznam, ki ga s takim razponom dobimo: >>> list( razpon ) [5, 6, 7, 8, 9] Seznam torej vsebuje argument start, argumenta stop pa ne. Podobno kot prej. Razpon od 0 do 10 (brez ötevila 10) bi torej lahko dobili tudi takole: >>> razpon = range (0, 10) >>> list( razpon ) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Do zdaj je bil korak med sosednjima elementoma v razponu vedno enak. To lahko spremenimo tako, da podamo öe argument step. V tem primeru bomo funkcijo poklicali takole: >>> range(start , stop , step ) Argument step je opcijski, njegova privzeta vrednost pa je 1. Lahko ga nastavimo na kaj drugega, npr. na 2. e bi hoteli zgenerirati seznam lihih ötevil v razponu od 0 do 100, bi to lahko naredili takole: >>> razpon = range (1, 101 , 2) >>> list( razpon ) [1, 3, 5, ... , 97, 99] Zakaj smo argument start postavili na 1? e bi za eli öteti z 0, bi dobili seznam sodih ötevil. >>> razpon = range (0, 101 , 2) >>> list( razpon ) [0, 2, 4, ... , 98, 100] Korak lahko nastavimo tudi na negativno vrednost: 60 Poglavje 5 Seznami in metode >>> razpon = range (0, 101 , -2) >>> list( razpon ) [] Tokrat smo dobili prazen seznam. Zakaj? Negativen korak pomeni, da ötejemo navzdol. Torej mora imeti argument start ve jo vrednost kot argument stop: >>> razpon = range (101 , 0, -2) >>> list( razpon ) [101 , 99, 97 ,... , 3, 1] Spet smo dobili seznam lihih ötevil. Zakaj? Zato ker smo za eli öteti z lihim ötevilom. Poleg tega razpon zdaj vklju uje ötevilo 101, ker je argument start v razponu vklju en. Razpon sodih ötevil bi dobili takole >>> razpon = range (100 , 0, -2) >>> list( razpon ) [100 , 98, 96 ,... , 4, 2] ätevilo 0 tokrat v razponu ni vklju eno, ker razpon argumenta stop ne vklju uje. 5.13 Rezine V dolo enih primerih ûelimo namesto indeksiranja enega samega elementa izvesti indeksiranje razpona elementov v seznamu. Dobimo torej kos oziroma rezino (angl. slice) seznama. Razpon seznama podamo na zelo podoben na in, kot smo ga uporabljali pri funkciji range, in sicer preko za etka (start) rezine, konca (stop) rezine in koraka rezinjenja (step). Podamo lahko samo za etek rezine: seznam[start:]. V tem primeru bo rezina odrezana do konca seznama. e bi npr. radi dobili vse elemente seznama od vklju no petega indeksa naprej, bi napisali takole: >>> seznam = list(range (10)) >>> seznam [5:] [5, 6, 7, 8, 9] Izhodiö ni seznam je kot pri obi ajnem indeksiranju ostal nespremenjen: >>> seznam [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Podamo lahko samo konec rezine: seznam[:stop]. V tem primeru se bo za ela na za etku seznama in zaklju ila na indeksu stop – 1. Podobno kot pri funkciji range tudi pri rezinah stop ni vklju en v razpon. e bi npr. radi dobili vse elemente seznama od za etka do petega indeksa (pri tem peti indeks ne bo vklju en), bi napisali takole: 5.13 Rezine 61 >>> seznam = list(range (10)) >>> seznam [:5] [0, 1, 2, 3, 4] Z nevklju enostjo indeksa stop smo zopet priöli do to no stop vrednosti, saj se ötetje za ne z indeksom 0. Pri rezinjenju lahko podajamo tudi zgolj korak: seznam[::step]. V tem primeru bo rezina odrezana od za etka do konca seznama, pri emer bo uporabljen podan korak. e bi npr. hoteli dobiti vsak drugi element seznama, bi napisali takole: >>> seznam = list(range (10)) >>> seznam [::2] [0, 2, 4, 6, 8] Korak je lahko tudi negativen. e bi kot korak npr. napisali vrednost –1, bi s tem seznam obrnili. S tem smo namre povedali, da gremo ez cel seznam s korakom –1, torej od konca do za etka: >>> seznam = list(range (10)) >>> seznam [:: -1] [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] Vse zgoraj naötete kombinacije lahko seveda po mili volji kombiniramo. e bi npr. hoteli vzeti vsak tretji element seznama v razponu od 2 do 9, bi napisali takole: >>> seznam = list(range (10)) >>> seznam [2:9:3] [2, 5, 8] e je korak negativen, moramo zopet paziti na to, da ima za etek (start) ve jo vrednost od konca (stop) >>> seznam = list(range (10)) >>> seznam [2:9: -1] [] >>> seznam [9:2: -1] [9, 8, 7, 6, 5, 4, 3] e bi ûeleli iti od konca do nekega indeksa proti za etku, bi to lahko podali kot seznam[:stop:-1], npr. >>> seznam = list(range (10)) >>> seznam [:1: -1] [9, 8, 7, 6, 5, 4, 3, 2] stop tudi tokrat ni vklju en. Povadimo rezine öe na enem zgledu. 62 Poglavje 5 Seznami in metode Zgled 17. Napiöi program, ki ga bo lahko uporabil sodnik smu arskih skokov. Programu naj sodnik poda seznam dolûin smu arskih skokov, program pa naj izpiöe najdaljöe tri skoke. Reöitev 17. Podobno kot prej bomo seznam prebrali in uredili. Zmagovalce lahko zdaj izpiöemo v eni vrstici. Tokrat bo program za razliko od prej deloval tudi v primeru, e bo sodnik vnesel manj kot 3 skoke. Rezine reûejo dokler gre in primeru da razpon preseûe indekse seznama, napake ne javljajo. 1 dolzine = eval(input(" Vnesi dolûine: ")) 2 dolzine .sort( reverse =True) 3 print( dolzine [:3]) — 5.14 Indeksiranje nizov Kot smo ûe omenili lahko podobno kot sezname indeksiramo tudi nize. Prav tako lahko nad nizi izvajamo rezine. Povadimo najprej rezinjenje. Zgled 18. Napiöi program, ki bo od uporabnika prebral dve zaporedji nukleotidnih baz (zapisani kot niza) in med njima na sredini izvedel kriûanje, tako da bo sestavil dve novi zaporedji nukleotidnih baz in jih izpisal. Reöitev 18. Program bo torej od uporabnika prejel dva niza. Najprej bomo dolo ili indeksa, kjer bomo kriûanje naredili. To bo na polovici posameznega zaporedja. Dolûino posameznega zaporedja bomo delili z 2, pri emer bomo uporabili celoötevilsko deljenje (//), saj morajo biti indeksi cela ötevila. Potem bomo odrezali rezine in jih med seboj sestavili (z operatorjem lepljenja +) ter izpisali. 1 gen1 = input("Vpiöi prvo zaporedje : ") 2 gen2 = input("Vpiöi drugo zaporedje : ") 34 # kje prereûemo gen 1? 5 i1 = len(gen1 )//2 # celoötevilsko deljenje z 2 6 # kje prereûemo gen 2? 7 i2 = len(gen2 )//2 # celoötevilsko deljenje z 2 89 gen11 = gen1[:i1] # prva polovica gena 1 10 gen12 = gen1[i1 :] # druga polovica gena 1 11 gen21 = gen2 [: i2] # prva polovica gena 2 12 gen22 = gen2[i2 :] # druga polovica gena 2 13 5.14 Indeksiranje nizov 63 14 # lepljenje iz izpis 15 print( gen11 + gen22 ) 16 print( gen21 + gen12 ) — Iz zgornjega zgleda vidimo öe eno prednost tega, da stop v rezino ni vklju en. e prvo rezino reûemo do indeksa stop, drugo pa od istega indeksa naprej, bosta rezini neprese ni, kar pomeni, da element na indeksu stop ne bo podvojen. Povadimo zdaj öe obi ajno indeksiranje, ki ga bomo potem pohitrili z rezinami. Zgled 19. Palindrom je niz, ki se na enak na in bere naprej kot nazaj. Na-piöi program, ki od uporabnika prebere niz in izpiöe, e podani niz je oziroma ni palindrom. Reöitev 19. Prva reöitev bo temeljila na zanki while, s katero se bomo sprehajali od za etka proti koncu niza. Zanko while lahko torej ponavljamo, dokler z nekim ötevcem (npr. i) ne preötejemo do konca niza. Za eli bomo pa seveda na za etku, torej pri vrednosti 0 (i=0). V zanki while bomo primerjali enakost znaka na indeksu i z znakom na indeksu -i-1. e se bodo ti pari ujemali povsod, bomo lahko sklepali, da je niz palindrom. Takoj, ko bomo naöli en primer, kjer se par ne ujema (protiprimer), pa bomo lahko sklepali, da niz ni palindrom. 1 niz = input("Vpiöi niz: ") 2 i = 0 # za eli bomo na za etku niza 3 while i < len(niz ): # do konca niza 4 if niz[i] != niz[-i -1]: # protiprimer 5 print("Niz ni palindrom ") 6 break 7 i += 1 # gremo na naslednji par 8 else: # e smo prisli do konca brez break -a 9 print("Niz je palindrom ") Program bi sicer lahko nekoliko pohitrili, saj se nam ni treba premikati do konca niza, ampak je dovolj, da kon amo, ko ötevec i pride do polovice niza. Pogoj v zanki while bi torej lahko spremenili v i < len(niz)//2. Do bistveno lepöe reöitve pa pridemo, e uporabimo rezine. Niz je palindrom, e se bere naprej enako kot nazaj. Torej mora biti naprej prebrani niz (niz) enak nazaj prebranemu nizu (niz[::-1]). Program je torej slede : 1 niz = input("Vpiöi niz: ") 2 if niz == niz [:: -1]: 3 print("Niz je palindrom ") 4 else: 5 print("Niz ni palindrom ") 64 Poglavje 5 Seznami in metode — 5.15 Sprehajanje ez sezname Do zdaj smo se temu sicer izogibali, ampak pri delu s seznami je ena izmed najpogostejöi operacij sprehajanje ez sezname. Kako narediti tak sprehod? Zgoraj smo se z zanko while sprehajali ez indekse niza. Podoben sprehod bi lahko naredili tudi ez sezname. Posamezen element seznama bi lahko izpisali npr. takole: i = 0 while i < len( seznam ): print( seznam [i]) i += 1 Sprehajamo se torej po indeksih od za etka (0) do konca seznama (len(seznam)-1). Zgornja koda je sicer popolnoma pravilna, ni pa najlepöa, saj je sprehajanju ez sezname in ez seznamu podobnimi podatki v Pythonu namenjena posebna zanka, zanka for. 6 Zanka for 6.1 Sprehajanje ez sezname z zanko for Kot smo videli na koncu prejönjega poglavja, se lahko ez seznam (ali niz) sprehodimo z uporabo zanke while, pri emer sprehod vröimo preko indeksov seznama (ali niza). Preko indeksov lahko potem posredno pridemo tudi do vrednosti elementov seznama (ali niza). Veliko bolj elegantno pa se ez seznam (ali pa niz) sprehodimo z uporabo zanke for: for element in seznam : # telo zanke # spremenljivka element vsebuje trenuten element ... # nadaljevanje programa ... Potek izvedbe osnovne oblike zanke for ponazarja slika 6.1 Izvajanje zanke torej Slika 6.1 Potek izvedbe osnovne oblike zanke for. ponavljamo vse dokler je v seznamu (ali nizu) öe kaköen element, pri emer se spremenljivka element pomika od za etka proti koncu seznama (ali niza). e bi 65 66 Poglavje 6 Zanka for npr. ûeleli izpisati vse elemente v seznamu, pri emer bi vsak element izpisali v svoji vrstici, bi to lahko naredili takole: >>> seznam = [1 ,2 ,3] >>> for element in seznam : print( element ) 123 Na podoben na in bi se lahko sprehodili tudi ez niz: >>> niz = "ABC" >>> for znak in niz: print(znak) ABC V tem primeru se zanka for torej sprehaja ez znake niza. Povadimo sprehajanje öe na zgledu. Zgled 20. Napiöi program, ki od uporabnika preko funkcije eval prebere seznam in izpiöe najmanjöi element seznama (brez uporabe funkcije min). Reöitev 20. Najmanjöi element bomo naöli tako, da se bomo z zanko for sprehodili ez seznam in si zapomnili element, ki je najmanjöi. Kako pa vemo, da je nek element najmanjöi, e ostalih öe nismo pregledali? Teûko. Vemo pa, e je nek element manjöi od vseh elementov, ki smo jih pregledali preden smo do njega priöli. Nalogo lahko reöimo tako, da naredimo predpostavko, da je najmanjöi ni ti element v seznamu. Potem naredimo sprehod ez celoten seznam. e bomo naöli kaköen element, ki je manjöi od trenutno najmanjöega, bomo trenutno najmanjöi element postavili na tega, ki je o itno manjöi. To bomo nadaljevali, dokler ne pridemo do konca seznama. 1 seznam = eval(input(" Vnesi seznam : ")) 23 najmanjsi = seznam[0] # trenutno najmanjöi 45 for element in seznam: # sprehod ez elemente 6 if element < najmanjsi : # naöli manjöega? 7 najmanjsi = element # popravimo vrednost 89 print(najmanjsi) 6.2 Sprehajanje s funkcijo range in sprehajanje ez indekse 67 Program sicer deluje pravilno, ampak bi ga lahko öe nekoliko optimizirali. Trenutno namre ni ti element v seznamu pregledamo dvakrat. Sprehoda z zanko for nam torej ne bi bilo potrebno delati ez cel seznam, ampak bi ga lahko naredili ez rezino seznama, ki se za ne na indeksu 1. Torej bi zanko for lahko delali ez rezino seznam[1:]. — 6.2 Sprehajanje s funkcijo range in sprehajanje ez indekse Z zanko for se lahko sprehajamo tudi ez sezname, ki jih generira funkcija range. Na tak na in se lahko sprehajamo ez vrednosti elementov v dolo enem razponu. Vsa ötevila od 0 do vklju no ötevila, ki ga je vnesel uporabnik, bi torej lahko izpisali tudi takole: n = int(input(" Vnesi ö tevilo : ")) for i in range(n+1): print(i) Zanko for bi lahko na podoben na in uporabili za sprehajanje po indeksih seznama. Indekse in vrednosti v seznamu na posameznih indeksih bi lahko izpisali takole:1 for i in range(len( seznam )): print(i, seznam [i]) Tokrat funkciji range kot argument stop podamo dolûino seznama, kar pomeni, da bo funkcija zgenerirala razpon elementov v intervalu od 0 do len(seznam)-1, kar je ravno razpon indeksov seznama. Tudi zato torej argument stop v interval ni vklju en in zato funkcija range (tudi) deluje kakor deluje. 6.3 Sprehajanje ez elemente ali ez indekse? Zgornji program bo poleg indeksa izpisal öe vrednost elementa, ki se nahaja na posameznem indeksu. Ali bi lahko do indeksa elementov priöli tudi v primeru, ko se sprehajamo neposredno po elementih seznama? Teûko. Zato v primeru, ko informacijo o indeksu potrebujemo, izvajamo zanko ez indekse in ne ez elemente. Poglejmo si spodnji primer, kjer reöitev zahteva izvedbo sprehoda ez indekse seznama. Zgled 21. Napiöi program, ki od uporabnika preko funkcije eval prebere seznam in izpiöe najmanjöi element seznama ter njegov indeks. 1V tem primeru bi lahko uporabili tudi funkcijo enumerate, s katero se lahko hkrati sprehajamo ez indekse in vrednosti na teh indeksih. 68 Poglavje 6 Zanka for Reöitev 21. Najmanjöi element bomo naöli na podoben na in kot prej, le da si moramo tokrat zapomniti tudi njegov indeks. Ker preko direktnega sprehoda ez elemente seznama informacije o indeksih elementov nimamo, se bomo morali sprehoditi ez indekse seznama. 1 seznam = eval(input(" Vnesi seznam : ")) 23 najmanjsi = seznam[0] # trenutno najmanjöi element 4 najmanjsi_i = 0 # zapomnimo si tudi njegov indeks 56 for i in range(len(seznam)): # sprehod ez indekse 7 element = seznam [i] # preko indeksa do elementa 8 if element < najmanjsi : # naöli manjöega? 9 najmanjsi = element # popravimo vrednost 10 najmanjsi_i = i # popravimo indeks 11 12 print( najmanjsi ) 13 print( najmanjsi_i ) Spet bi lahko pri sprehodu prvi element seznama izpustili, tako da bi se sprehajali ez razpon indeksov range(1, len(seznam)). — Zgornja reöitev ima manjöo pomanjkljivost, in sicer ne upoöteva, da se lahko enako majhen element v seznamu pojavi ve krat. V tem primeru vrne zgolj indeks njegove prve pojavitve. Naprednejöo reöitev prikazuje spodnji zgled. Zgled 22. Napiöi program, ki od uporabnika preko funkcije eval prebere seznam in izpiöe najmanjöi element seznama ter vse indekse njegove pojavitve. Reöitev 22. Reöitev bo podobna kot prej, le da si bomo indekse pojavitve najmanj- öega elementa zabeleûili kar v seznam. V primeru, da bomo naöli manjöi element od trenutnega, bomo naredili nov seznam, ki bo vseboval samo en indeks (trenutni indeks). V primeru, da bomo naöli element, ki bo enak trenutno najmanjöemu, bomo v seznam indeksov dodali trenutni indeks. V prejönjih dveh zgledih smo na koncu omenili boljöo reöitev, ki pri sprehodu izpusti ni ti element seznama, saj smo tega upoötevali ûe pred zanko. Tokrat bo program brez te "optimizacije"deloval narobe. V primeru, da bo najmanjöi element na ni tem mestu, bo njegov indeks v seznamu najmanjöih indeksov namre nastopal dvakrat. 1 seznam = eval(input(" Vnesi seznam : ")) 23 najmanjsi = seznam[0] # trenutno najmanjöi element 4 najmanjsi_i = [0] # v seznam shranimo njegov indeks 6.4 Spreminjanje elementov seznama z zanko for 69 56 # sprehod ez indekse (ni ti element izpustimo) 7 for i in range (1, len( seznam )): 8 element = seznam [i] # preko indeksa do elementa 9 if element < najmanjsi : # naöli manjöega? 10 najmanjsi = element # popravimo vrednost 11 najmanjsi_i = [i] # resetiramo seznam indeksov 12 elif element == najmanjsi : # naöli enako majhnega 13 najmanjsi_i . append (i) # dodamo indeks 14 15 print( najmanjsi ) 16 print( najmanjsi_i ) — 6.4 Spreminjanje elementov seznama z zanko for Kaj pa v primeru da ûelimo seznam v zanki spremeniti, npr. da ûelimo vse negativne vrednosti seznama spremeniti v pozitivne (izra unati ûelimo absolutne vrednosti elementov seznama in seznam skladno s tem posodobiti). éal funkcije abs nad seznamom ne moremo direktno pognati (napaka)2, zato moramo izra unati absolutno vrednost vsakega elementa posebej, pri emer lahko to reöimo z uporabo zanke for. Poskusimo z obi ajnim sprehodom ez elemente seznama. >>> seznam = [-1, 10, -5, 15, 0, -3] >>> for element in seznam : element = abs( element ) print( element ) 11051503>>> print(seznam) [-1, 10, -5, 15, 0, -3] Elemente smo torej uspeöno postavili na njihove absolutne vrednosti, na kar nakazujejo izpisi, ki smo jih izvedli v telesu zanke. Kot pa vidimo iz izpisa, ki je sledil zanki, se seznam ni spremenil, saj öe vedno vsebuje negativne elemente. 2Dolo eno funkcijo lahko nad vsemi elementi seznama posredno poûenemo z uporabo funkcije eval. 70 Poglavje 6 Zanka for V konkretnem primeru torej samega seznama nismo spreminjali. e bi ûeleli spreminjati seznam, bi to lahko naredili preko indeksiranja: >>> seznam = [-1, 10, -5, 15, 0, -3] >>> for i in range(len( seznam )): seznam [i] = abs( seznam [i]) print( seznam [i]) 11051503>>> print(seznam) [1, 10, 5, 15, 0, 3] Zdaj se je seznam spremenil, saj smo absolutne vrednosti direktno prirejali seznamu na posameznem indeksu. 6.5 Zanka for ali zanka while? Vidimo, da so naöi programi z uporabo zanke for v dolo enih primerih veliko krajöi in lepöi kot v primeru uporabe zanke while. Poleg tega nam pri uporabi zanke for ni potrebno skrbeti, da bo program za vedno obti al v zanki (neskon na zanka). Zakaj bi torej sploh uporabljali zanko while? Izkaûe se, da je zanka while bolj sploöna kot zanka for in da lahko z njo reöimo dolo ene probleme, ki jih z zanko for ne moremo. Kako bi npr. z zanko for od sodnika smu arskih skokov brali dolûine skokov, dokler sodnik ne vnese ötevila 0? Koliko ponovitev bi morali narediti? Kako bi z zanko for odötevali manjöe ötevilo od ve jega, dokler ötevili ne bi postali enaki? Odgovor je enostaven. Teûko. Vpraöajmo se, kaj je skupnega primerom, kjer zanka for odpove. V obeh zgornjih primerih je ötevilo ponovitev, ki jih bo morala zanka narediti, vnaprej teûko predvidljivo. V sploönem velja, da zanko while uporabljamo, kadar ötevilo ponovitev zanke teûko podamo vnaprej, lahko pa oblikujemo pogoj, ki bo dolo il, do kdaj naj se zanka izvaja. V primeru, da je ötevilo ponovitev predvidljivo (npr. podan je razpon ötetja ali pa seznam s fiksno dolûino, ez katerega se sprehajajo) pa je kot nalaö zanka for. 6.6 Stavek break V kombinaciji z zanko for lahko prav tako kot pri zanki while uporabljamo stavek break. Ta izvajanje zanke prekine, kljub temu, da ta öe ni priöla do konca seznama 6.6 Stavek break 71 (ali esa drugega). Primer uporabe stavka break znotraj zanke for ponazarja spodnja koda: for element in seznam : # telo zanke ... if dodaten_pogoj : break # prekine izvajanje zanke # nadaljevanje programa ... Potek izvedbe kode iz primera prikazuje slika 6.2. Slika 6.2 Potek izvedbe zanke for v kombinaciji s stavkom break. Stavek break bi lahko uporabili pri iskanju dolo enega ötevila v seznamu celih ötevil ( e smo pozabili, da obstaja operator in). e element najdemo, lahko iskanje prekinemo s stavkom break: 1 st = int(input(" Vnesi ö tevilo : ")) 2 for el in sez: 3 if st == el: 4 print("ä tevilo najdeno !") 5 break 72 Poglavje 6 Zanka for S tem smo se izognili ve kratnemu izpisovanju, da smo ötevilo naöli ( e bi seznam vseboval ve pojavitev iskanega ötevila, bi brez uporabe stavka break, izpis podali za vsako pojavitev), poleg tega pa smo program nekoliko pohitrili, saj zanko izvaja samo dokler je to potrebno. Kdaj pa podali izpis, da ötevila ni v seznamu? 6.7 Veja else Podobno kot lahko vejo else kombiniramo z zanko while, jo lahko kombiniramo tudi z zanko for: for element in seznam : # telo zanke ... if dodaten_pogoj : break # prekini izvajanje zanke else: # samo e zanka ni bila prekinjena z break # konec seznama ... # nadaljevanje programa ... Veja else se bo kot pri zanki while izvedla samo v primeru, ko zanka ni bila prekinjena s stavkom break. Vrnimo se na primer z iskanjem ötevil v seznamu. e ötevilo v seznamu najdemo, lahko to izpiöemo in zanko prekinemo. V primeru, da smo priöli do konca seznama in ötevila nismo naöli, ötevila v seznamu ni. In to no v tem primeru se bo izvedla else veja zanke. Primer lahhko torej dopolnimo takole: 1 st = int(input(" Vnesi ö tevilo : ")) 2 for el in sez: 3 if st == el: 4 print("ä tevilo najdeno !") 5 break 6 else: 7 print("ä tevila ni v seznamu !") Demonstrirajmo uporabo stavka break in veje else öe na zgledu s tujimi ötevili. Zgled 23. Napiöi program, ki od uporabnika prebere dve celi ötevili in izpiöe, e sta ötevili tuji. ätevili sta tuji, e nimata nobenega skupnega delitelja, ki je ve ji od 1. Reöitev 23. Program bo strukturiran zelo podobno kot v primeru zanke while, le da bomo tokrat razpon ötevil, ez katera se sprehaja kandidat, ustvarili z uporabo funkcije range. 1 st1 = int(input(" Vnesi prvo ö tevilo : ")) 6.8 Gnezdenje zank 73 2 st2 = int(input(" Vnesi drugo ö tevilo : ")) 34 # sprehod od 2 do manjöega od obeh ötevil 5 # desni del intervala naj bo vklju en , 6 # zato priötejemo 1 7 for delitelj in range (2, min(st1 , st2 )+1): 8 # ali delitelj deli obe ötevili? 9 if st1 % delitelj == 0 and st2 % delitelj == 0: 10 print("ä tevili nista tuji") 11 break # lahko prenehamo z iskanjem 12 else: # ali se je zanka odvrtela do konca 13 # zanke nismo prekinili s stavkom break 14 print("ä tevili sta tuji") — 6.8 Gnezdenje zank Podobno kot smo gnezdili stavke if lahko gnezdimo tudi zanke. To pomeni, da bomo zanko izvajali znotraj druge zanke. Primer gnezdenja zanke for prikazuje spodnji izsek kode: >>> for i in range (5): for j in range (5): print(i,j) 0 0 0 1 0 2 0 3 0 4 1 0 1 1 ... 3 3 3 4 4 0 4 1 4 2 4 3 4 4 Notranja zanka for torej za vsako iteracijo zunanje zanke izvede enako ötevilo ponovitev. 74 Poglavje 6 Zanka for Potrenirajmo na zgledu. Zgled 24. Napiöi program, ki od uporabnika prebere celo ötevilo in izpiöe poötevanko ötevil od 1 do vklju no podanega ötevila. Reöitev 24. ätevila od 1 do podanega ötevila n bomo najprej mnoûili z 1, potem z 2, potem s 3 in tako naprej, dokler ne pridemo do ötevila n. To lahko enostavno reöimo z uporaba ugnezdene zanke. n = int(input(" Vnesi ö tevilo : ")) for i in range (1, n+1): # zunanja zanka for j in range (1, n+1): # notranja zanka print(i*j) # izpis produkta print () #prazna vrstica V primeru, da uporabnik vpiöe ötevilo 3, bo izpis slede : 123 246 369 Dopolnimo program, tako da bo pred izpisom produkta podal öe izra un: n = int(input(" Vnesi ö tevilo : ")) for i in range (1, n+1): # zunanja zanka for j in range (1, n+1): # notranja zanka print(str(i)+"*"+str(j)+"="+str(i*j)) print () #prazna vrstica V primeru, da uporabnik vpiöe ötevilo 3, bo zdaj izpis slede : 1*1=1 1*2=2 1*3=3 2*1=2 2*2=4 6.8 Gnezdenje zank 75 2*3=6 3*1=3 3*2=6 3*3=9 — V zgornjih primerih je bila notranja zanka neodvisna od tega, kako dale se je ûe odvila zunanja zanka. Ponavadi pa temu ni tako. Primer ugnezdene zanke, pri kateri je razpon notranje zanke odvisen od ötevila izvedenih iteracij zunanje zanke, prikazuje spodnji izsek kode: >>> for i in range (5): for j in range(i ,5): print(i,j) 0 0 0 1 0 2 0 3 0 4 1 1 1 2 1 3 1 4 2 2 2 3 2 4 3 3 3 4 4 4 V prvi iteraciji zunanje zanke, se torej notranja zanka izvede petkrat, v drugi ötirikrat, v tretji trikrat, v etrti dvakrat, v peti pa zgolj enkrat. Kasnejöa kot je iteracija zunanje zanke, manjöe je ötevilo ponovitev ugnezdene zanke. e bi v zgornjem primeru ötevili, ki ju izpisujemo, predstavljali oznako ekip, bi celoten izpis predstavljal vse pare ekip (brez ponovitev), ki lahko med seboj tekmujejo. Povadimo tako gnezdenje öe na primeru s tujimi ötevili. Zgled 25. Napiöi program, ki od uporabnika prebere celo ötevilo in izpiöe vsa ötevila, ki so podanemu ötevilu tuja in so od njega manjöa. Reöitev 25. Kandidati, ki jih moramo torej obravnavati, se gibljejo v razponu od ötevila 1 (ki je vsem ötevilom tuje ötevilo) do ötevila n-1, pri emer je n ötevilo, ki 76 Poglavje 6 Zanka for ga je vnesel uporabnik. Kako za posameznega kandidata preverimo, e je tuj ötevilu n? Podobno kot prej – tako da se sprehodimo od ötevila 2, do manjöega od obeh ötevil. e smo naöli kakönega delitelja, si ötevili o itno nista tuji. 1 n = int(input(" Vnesi ö tevilo : ")) 23 for kandidat in range(1, n): # razpon cez kandidate 4 # kandidat ne sme imeti nobenega skupnega delitelja 5 # ugnezdimo kodo iz prejönjih zgledov 6 st1 = n 7 st2 = kandidat 89 # sprehod od 2 do manjöega od obeh ötevil 10 # desni del intervala naj bo vklju en , zato priötejemo 1 11 for delitelj in range (2, min(st1 , st2 )+1): 12 # ali delitelj deli obe ötevili? 13 if st1 % delitelj == 0 and st2 % delitelj == 0: 14 break # lahko prekinemo ugnezdeno zanko 15 else: # ali se je ugnezdena zanka odvrtela do konca 16 # ugnezdene zanke nismo prekinili s stavkom break 17 print( kandidat ) — Ugnezdena zanka je v tem primeru odvisna od tega kako dale je naö program priöel z zunanjo zanko. Mimogrede, na podoben na in bi lahko gnezdili tudi zanko while. 6.9 Izbirni argumenti funkcij in izbirni argumenti funkcije print Tole sicer ni neposredno povezano z zanko for, bo pa sluûilo kot osnova za dopolnitev zgleda s poötevanko. Povedali smo ûe, da funkcije sprejemajo argumente, ki jih ob klicu podamo. V dolo enih primerih imajo funkcije tudi t.i. izbirne ali opcijske argumente, za katere velja da imajo (pred)nastavljeno privzeto vrednost. V primeru, da vrednosti teh argumentov eksplicitno ne podamo, bodo ti nastavljeni na njihove privzete vrednosti. V primeru, da vrednosti tem argumentom podamo, bomo s tem povozili privzete vrednosti in uporabljene bodo podane (naöe) vrednosti. Poglejmo si dva izbirna argumenta funkcije print in primer njune uporabe. Do dokumentacije funkcije print lahko pridemo preko funkcije help: 6.9 Izbirni argumenti funkcij in izbirni argumenti funkcije print 77 >>> help(print) Help on built -in function print in module builtins : print (...) print(value , ... , sep=’ ’, end=’\n’, file=sys.stdout , flush = False ) Prints the values to a stream , or to sys. stdout by default . Optional keyword arguments : file: a file -like object ( stream ); defaults to the current sys. stdout . sep: string inserted between values , default a space . end: string appended after the last value , default a newline . flush : whether to forcibly flush the stream . Zaenkrat nas bosta zanimala predvsem argumenta sep in end. Funkcija print deluje tako, da sprejme poljubno ötevilo ötevilk, nizov in öe esa drugega, to med seboj zdruûi in izpiöe na zaslon. Pri tem argument sep dolo a, s im naj podane ötevilke, nize in öe kaj drugega med seboj zdruûi. Privzeto je ta argument postavljen na vrednost ’ ’, kar vidimo iz zgleda klica funkcije (sep=’ ’). To pomeni, da bo izpis narejen tako, da bodo med podanimi argumenti za izpis vstavljeni presledki. Povadimo: >>> print (1 ,2 ,3) # privzeta vrednost argumenta 1 2 3 >>> print (1,2,3, sep=’’) # brez presledka 123 >>> print (1,2,3, sep=’+++ ’) # poljuben niz kot lo ilo 1+++2+++3 Izbirni argument end podaja niz, ki naj se vstavi na koncu izpisa. Privzeto je argument end nastavljen na znak ’\n’ (end=’\n’), ki predstavlja znak za novo vrstico (angl. line feed). Tudi tega lahko postavimo na kaköno drugo vrednost. Povadimo nastavljanje opcijskih argumentov na zgledu v kombinaciji z ugnezdeno zanko for. Zgled 26. Napiöi program, ki od uporabnika prebere celo ötevilo in izpiöe poötevanko ötevil od 1 do vklju no podanega ötevila. Pri tem naj bo poötevanka s posameznim ötevilom podana v svoji vrstici, ötevila pa naj bodo lo ena s presledki. 78 Poglavje 6 Zanka for Reöitev 26. Reöitev bo podobna kot prej, le da se tokrat ne bomo pomikali v novo vrstico po vsakem izpisu. To lahko naredimo tako, da opcijski argument end nastavimo na znak ’ ’. n = int(input(" Vnesi ö tevilo : ")) for i in range (1, n+1): # zunanja zanka for j in range (1, n+1): # notranja zanka print(i*j, end = ’ ’) # izpis produkta brez nove vrstice print () #nova vrstica V primeru, da uporabnik vpiöe ötevilo 3, bo tokrat izpis slede : 1 2 3 2 4 6 3 6 9 — 7 Uporaba in pisanje funkcij 7.1 Kaj so funkcije in zakaj so uporabne? Kot ûe vemo, funkcije predstavljajo del kode, ki jo lahko izvedemo tako, da funkcijo preprosto pokli emo. Uporaba funkcij ima veliko prednosti. Govorili smo ûe o tem, da je glavno vodilo programiranja razdelitev problemov na obvladljive podprobleme. Dolo anje algoritma, ki ga potem samo öe prenesemo v programsko kodo, je podobno dolo-anju recepta, ki ga potem prenesemo v okusno jed. Prav tako, kot se moramo pri kuhanju zavedati sestavin, ki jih imamo na razpolago, se moramo tudi pri programiranju zavedati gradnikov programskega jezika, ki jih lahko pri pisanju algoritma uporabimo. Funkcije nam omogo ajo, da osnovne korake za reöevanje programa vgradimo v enostavnejöe funkcije, enostavnejöe funkcije v kompleksnejöe in tako naprej. Podobno, kot e bi pri peki torte lahko uporabili ûe vnparej pripravljeno testo, preliv in kar se pri torti öe uporabi, namesto da moramo torto sestaviti iz enostavnejöih (niûjenivojskih) sestavin, kot so jajca, mleko in sladkor. Tako, kot bi lahko tudi pri peki seveda öli v drug ekstrem in se lotili reje kokoöi, bi na veliko niûji nivo lahko öli tudi pri programiranju, ampak pustimo to za kdaj drugi . S pisanjem svojih funkcij se lahko torej najprej lotimo enostavnejöih korakov, ki predstavljajo del reöitve izbranega problema. Potem lahko vmesne reöitve (velikokrat na enostaven na in) zdruûimo v kon no reöitev. e bi npr. ûeleli najti vsa praötevila v dolo enem razponu ötevil, bi lahko najprej napisali funkcijo, ki za podano ötevilo preveri, e je praötevilo. Vse kar bi morali narediti potem bi bil zgolj klic te funkcije za vsako ötevilo z intervala. Zgled s praötevili pa nam je posredno razodel öe eno veliko prednost uporabe funkcij. Isto kodo, tj. preverjanje ali je neko ötevilo praötevilo, bomo poklicali ve krat, vsaki seveda z drugim argumentom, tj. ötevilom, ki je kandidat za praötevilo. Funkcije nam torej omogo ajo tudi to, da lahko isti kos kode ve krat pokli emo brez tega, da bi jo vklju evali v zanke ali pa kopirali v vse dele programa, kjer jo potrebujemo. To kodo bi lahko delili tudi z drugimi programerji. e smo npr. napisali zelo dobro funkcijo za iskanje praötevil in smo nad njo nadvse navduöeni, hkrati pa vemo, da bi bila lahko koristna tudi za druge iskalce praötevil, lahko funkcijo enostavno 79 80 Poglavje 7 Uporaba in pisanje funkcij zapakiramo v t.i. modul, ki ga objavimo na internetu. 7.2 Kako definiramo funkcijo? Vsaki funkciji, ki jo ûelimo v naöih programih ponovno uporabiti, moramo dati seveda neko ime, preko katerega jo bomo lahko po potrebi poklicali. Skupaj s seznamom argumentov, ki jih bo naöa funkcija sprejela, to podamo v definiciji funkcije. Definicijo funkcije za nemo z rezervirano besedo def in kon amo z dvopi jem: def ime_funkcije ( argument_1 , argument_2 ,... , argument_n ): Definiciji funkcije sledi njena vsebina. Stavke, ki so v funkciji vsebovani tudi tokrat dolo imo z zamikanjem na za etku vrstice (podobno kot pri pogojnemu stavku in zankah). Ko ûelimo Pythonu sporo iti, da koda ni ve del funkcije, enostavno nehamo zamikati. Spodnji primer predstavlja definicijo enostavne funkcije, ki seöteje vrednosti dveh spremenljivk (a in b) v novo spremenljivko (c) in rezultat seötevanja izpiöe. 1 def sestej (a, b): 2 c = a + b 3 print(c) 4 # tale komentar je öe del funkcije 5 # tale komentar ni ve del funkcije Kaj pa se zgodi, ko program s tole definicijo poûenemo. Navidez se ne zgodi ni , e pa v ukazno vrstico napiöemo ime pravkar definirane funkcije, bi moral Python izpisati nekaj podobnega temu: Kaj to pomeni? To pomeni, da se je v naöem imenskem prostoru (pojem bomo razloûili v kratkem) pojavilo ime sestej, ki ima v ozadju funkcijo, ta pa je shranjena nekje v pommnilniku (natan neje na pomnilniökem naslovu 0x000001C24E1481E0). Ko smo izvedli zgornjo kodo, smo torej dobili definicijo funkcije sestej, ki jo zdaj lahko pokli emo. Do zdaj smo funkcije vedno klicali tako, da so imenu funkcije sledili oklepaji, znotraj katerih smo naöteli vrednosti argumentov, nad katerimi smo ûeleli funkcijo poklicati. In seveda je tako tudi v primeru funkcij, ki jih definiramo sami. e bi torej ûeleli izpisati vsoto ötevil 5 in 7, bi lahko izvedli klic >>>sestej (5 ,7) 12 Ko smo funkcijo definirali se torej koda znotraj funkcije sploh ni izvedla. Izvedla se je zgolj njena definicija, ki nam je njeno ime umestila v imenski prostor (podobno, kot 7.3 Globalni imenski prostor 81 e smo nekemu imenu – sprememnljivki, priredili neko vrednost). Dejanska izvedba stavkov znotraj funkcije pa se je izvröila öele, ko smo funkcijo poklicali. Mimogrede, e bi v funkciji imeli napako, kot je npr. uporaba nedefinirane spremenljivke, bi jo Python naöel öele ob klicu funkcije. 7.3 Globalni imenski prostor Vsaki , ko v Pythonu definiramo novo spremenljivko, se ime, preko katerega bomo dostopali do vrednosti te spremenljivke, shrani v t.i. imenski prostor. Podobno se zgodi ob definiciji funkcije, le da se v tem primeru za imenom funkcije skriva vsebina funkcije, ki se bo izvedla, ko jo bomo poklicali. Ko npr. definiramo spremenljivki x in y z uporabo kode >>> x = 5 >>> y = 7 se v imenskem prostoru pojavita imeni x in y, za katerimi se skrivata podani vrednosti, kot prikazuje slika 7.1. Preko imen x in y lahko zdaj dostopamo do globalni imenski prostor vrednosti x 5 y 7 Slika 7.1 Imena v imenskem prostoru kaûejo na konkretne vrednosti v pomnilniku. vrednosti, ki se skrivajo v ozadju, ne da bi se morali zavedati kje konkretno v pomnilniku so te vrednosti shranjene, kar nam bistveno olajöa ûivljenje. Definicije novih imen, pa naj gre za imena spremenljivk ali funkcij, ki jih ustvarimo izven funkcij, se shranijo v t.i. globalni imenski prostor. Zato tem imenom pogosto re emo kar globalna imena, spremenljivkam pa globalne spremenljivke. e obstaja globalni imenski prostor pa bo verjetno obstajal tudi lokalni. Poglejmo si, kaj se zgodi, ko funkcijo pokli emo. 7.4 Kaj se zgodi ob klicu funkcije in lokalni imenski prostor Kot smo ûe omenili, se ob definiciji funkcije v globalnem imenskem prostoru ustvari novo ime, ki je enako imenu funkcije. To kaûe na samo funkcijo, tako da bomo lahko le-to kasneje preko imena tudi poklicali. Situacijo po definiciji funkcije sestej prikazuje slika 7.2. Dopolnimo program, v katerem smo napisali funkcijo sestej, öe z njenim klicem. 82 Poglavje 7 Uporaba in pisanje funkcij globalni imenski prostor vrednosti sestej function sestej(a,b) Slika 7.2 Ob definiciji funkcije v imenskem prostoru dobimo novo ime, ki je enako imenu funkcije. Za tem imenom se skriva naöa funkcija. 1 def sestej (a, b): # definicija funkcije 2 c = a + b 3 print(c) 4 x = 5 5 y = 7 6 sestej (x,y) # klic funkcije Vrstice programa od 1, 4 in 5 bi morale biti zdaj ûe popolnoma jasne. Kaj pa se zgodi, ko program pride do vrstice 6? Ustvari se lokalni imenski prostor funkcije sestej, znotraj katerega bo funkcija ustvarila svoje lokalne spremenljivke. V lokalnem imenskem prostoru se najprej ustvarita lokalni spremenljivki z imeni a in b, ki predstavljata plitvi kopiji spremenljivk x in y, tj. spremenljivk, s katerimi smo funkcijo poklicali. Enako posledico bi imela prireditev >>> a = x >>> b = y s to razliko, da bi se imeni a in b ustvarili v globalnem imenskem prostoru. Plitva kopija pomeni, da vrednost, ki je shranjena v pomnilniku dobi dodatno ime, brez da bi se dejansko kopirala (to bi bila globoka kopija), s imer smo s pomnilniökim prostorom veliko bolj var ni. O tem bomo öe govorili, zaenkrat pa se vrnimo k naöi funkciji in njenem lokalnem imenskem prostoru. Situacijo ob klicu funkcije prikazuje slika 7.3. Vsa imena, ki jih bomo v nadaljevanju definirali znotraj funkcije, bodo ustvarjena v lokalnem imenskem prostoru funkcije. Ko naö program na primer izvede vrstico 2 (ta se je ob definiciji funkcije presko ila in se izvede öele ob njenem klicu), bo priölo do situacije, kot jo prikazuje slika 7.4 Kaj pa bi se zgodilo, e bi znotraj funkcije definirali ime, ki obstaja ûe v globalnem imenskem prostoru. Ni posebnega. Spremenljivka s tem imenom bi se ustvarila v lokalnem imenskem prostoru funkcije in to na globalno spremenljivko ne bi vplivalo. Zgodilo bi se nekaj takega kot prikazuje slika 7.5. Zakaj je tak na in delovanja dober? e bi morali znotraj funkcij paziti, da ne uporabljamo enakih imen, kot so ûe definirana izven funkcij, potem bi morali ûe vnaprej predvideti kaköna imena bodo pri programiranju uporabljali vsi bodo i uporabniki naöih funkcij. Prav tako bi morali biti zelo pazljivi, ko bi obstoje e funkcije uporabljali mi. Vedeti bi morali katere spremenljivke za izpis ne esa na 7.4 Kaj se zgodi ob klicu funkcije in lokalni imenski prostor 83 globalni imenski prostor vrednosti sestej function sestej(a,b) x 5 y 7 imenski prostor funkcije sestej a b Slika 7.3 Ob klicu funkcije se ustvari njen lokalni imenski prostor znotraj katerega se dodatno ustvarijo plitve kopije vrednosti, s katerimi smo funkcijo poklicali. globalni imenski prostor vrednosti sestej function sestej(a,b) x 5 y 7 imenski prostor funkcije sestej a b c 12 Slika 7.4 Vsa imena, ki jih definiramo znotraj funkcije, se ustvarijo zgolj v lokalnem imenskem prostoru te funkcije. 84 Poglavje 7 Uporaba in pisanje funkcij globalni imenski prostor vrednosti sestej function sestej(a,b) x 5 y 7 imenski prostor funkcije sestej a b x 12 Slika 7.5 Znotraj funkcije lahko uporabljamo enaka imena spremenljivk kot izven funkcije in s tem ne vplivamo na globalne spremenljivke. zaslon na primer uporablja funkcija print. Tem imenom bi se morali izogibati, kar pa bi bilo skrajno nerodno in nesmiselno. Vpraöanje, na katerega moramo öe odgovoriti je, kaj se zgodi, ko se funkcija izvede do konca. V naöem primeru se funkcija kon a po izpisu vrednosti spremenljivke c (vrstica 3). Ko se funkcija kon a, njenega lokalnega imenskega prostora ne potrebujemo ve . e bomo funkcijo öe enkrat poklicali, bo Python ustvaril nov lokalen imenski prostor. Iz tega razloga po kon anju izvedbe funkcije lokalni imenski prostor funkcije izgine. V naöem konkretnem primeru torej imena a, b in c izginejo. Kaj pa vrednosti? Do vrednosti 12 ne moremo ve dostopati preko nobene spremenljivke, zato se lahko izbriöe tudi ta. Vrednosti 5 in 7 po drugi strani ostaneta, saj nanju öe vedno kaûeta imeni x in y. To prikazuje slika 7.6. Iz globalnega imenskega prostora do lokalnih imenskih prostorov uporabljenih funkcij torej ne moremo dostopati, saj se po zaklju ku izvajanja funkcij (ko izvedba programa preide spet v globalni imenski prostor), lokalni imenski prostori izbriöejo. Kaj pa obratno? Iz lokalnega imenskega prostora funkcije, lahko dostopamo do globalnega (tudi zato se mu re e globalni), kar pomeni, da lahko dostopamo do vrednosti globalnih spremenljivk. äe pomembneje pa je to, da lahko iz lokalnega imenskega prostora funkcij, dostopamo do imen globalno definiranih funkcij. To pomeni, da lahko iz posamezne funkcije pokli emo druge funkcije (gnezdenje funkcij) ali pa tudi samo sebe. Slednjemu se re e rekurzija, ampak pustimo to za kdaj drugi . 7.4 Kaj se zgodi ob klicu funkcije in lokalni imenski prostor 85 globalni imenski prostor vrednosti sestej function sestej(a,b) x 5 y 7 imenski prostor funkcije sestej a b c 12 Slika 7.6 Po izvedbi klica funkcije, se njen imenski prostor izbriöe. Napiöimo malo razöirjen program, ki bo seötel vrednosti dveh seznamov. Pri tem si bomo pomagali z definicijo dveh funkcij. 1 def sestej (a, b): # seötej in izpiöi 2 c = a + b 3 print(c) 45 def sestej_seznama(a,b): # seötej istoleûne elemente 6 for i in range(len(a)): 7 sestej (a[i], b[i]) 89 sestej_seznama([1,2,3],[4,5,6]) # klic funkcije Iz funkcije sestej_seznama torej kli emo funkcijo sestej. Ali je to dovoljeno? Seveda. Imeni sestej in sestej_seznama bomo po izvedbi vrstic 1 in 5 imeli v globalnem imenskem prostoru, kot prikazuje slika 7.7. Ker je globalni imenski prostor viden tudi iz lokalnih imenskih prostorov posameznih funkcij, jih lahko od tam tudi pokli emo. V sled temu je zgornji program popolnoma pravilen. e program pogledamo podrobneje, lahko vidimo, da obe funkciji uporabljata enaka imena spremenljivk. Tudi to ne bo povzro alo nobenih teûav, saj bo vsaka funkcija dobila svoj lasten lokalni imenski prostor. Ko bomo poklicali funkcijo sestej_seznama, bo ta dobila lokalen imenski prostor. Ko bomo iz te funkcije poklicali funkcijo sestej, bo ta dobila svoj imenski prostor, ki se s prostorom 86 Poglavje 7 Uporaba in pisanje funkcij globalni imenski prostor vrednosti sestej function sestej(a,b) sestej_seznama function sestej_seznama(a,b) Slika 7.7 Imeni definiranih funkcij sta shranjeni v globalnem imenskem prostoru, zato jih lahko pokli emo od kjerkoli. funkcije sestej_seznama ne bo prekrival. Lokalne imenske prostore si torej lahko predstavljamo kot lo ene mehur ke, ki se med seboj ne prekrivajo. Stanje naöega programa ob prvi izvedbi funkcije sestej do vklju no vrstice 2 prikazuje slika 7.8. globalni imenski prostor vrednosti sestej function sestej(a,b) sestej_seznama function sestej_seznama(a,b) imenski prostor funkcije sestej_seznama a [1,2,3] b [4,5,6] i 0 imenski prostor funkcije sestej a b c 5 Slika 7.8 Lokalni imenski prostori funkcij so med seboj lo eni. Vpraöanje za razmislek – zakaj se znotraj funkcije sestej ne ustvarita novi vrednosti, na kateri bosta kazali imeni a in b? 7.5 Vsaka funkcija vra a rezultat 87 7.5 Vsaka funkcija vra a rezultat V sploönem pri programiranju lo imo dva tipa funkcij, in sicer tiste, ki nekaj uporabnega vrnejo in tiste, ki nekaj uporabnega naredijo (vrnejo pa ni ). V dolo enih programskih jezikih ti dve skupini nosijo celo posebna imena in sta tudi druga e definirani. Kaj pa v jeziku Python? V skupino funkcij, ki nekaj uporabnega vra ajo bi lahko uvrstili npr. funkcijo input, ki prebere uporabnikov vnos in tega vrne kot podatkovni tip str. V skupino funkcijo, ki ne vra ajo ni kaj preve uporabnega, je pa uporabno tisto, kar naredijo, pa spada funkcija print. Dejstvo je, da v Pythonu vsaka funkcija nekaj vrne pa tudi, e to ni isto ni uporabnega. Poglejmo si kaj vrne funkcija print. Kako? Rezultat funkcije print bomo shranili v spremenljivko in vrednost te spremenljivke izpisali. >>>a = print(" testni izpis ") testni izpis >>> print(a) None Kaj se torej skriva v rezultatu funkcije print? Dobesedno ni oziroma None. Funkcija nekaj vrne, in sicer vrne ni . Preverimo lahko tudi njegov podatkovni tip. >>> print(type(a)) < class ’NoneType ’> Ni oziroma None je torej poseben podatek, ki pripada podatkovnemu tipu ni oziroma NoneType. Ni sicer veliko, ampak nekaj pa je. Enak rezultat vra ajo funkcije, ki smo jih definirali v prejönjem razdelku. Lahko preverite sami. Kaj pa e bi ûeleli, da naöa funkcija vrne nekaj uporabnega? V tem primeru moramo od nje to eksplicitno zahtevati, in sicer s stavkom return. Spremenimo funkcijo sestej, tako da bo vsoto dveh ötevil vra ala in ne izpisovala. 1 def sestej (a, b): # seötej in vrni 2 c = a + b 3 return c Z uporabo stavka return smo torej povedali, da ûelimo, da naöa funkcija vrne vrednost spremenljivke c. Ali nismo tega naredili ûe prej? Ne. V prejönji razli ici je funkcija vrednost spremenjivke c zgolj izpisovala. Ko se je funkcija kon ala, je njen lokalni imenski prostor izginil in z njim tudi vrednost spremenljivke c. Pogosto pa ûelimo rezultate funkcij uporabiti tudi v drugih delih naöih programov (npr. ko uporabljamo funkcijo input ûelimo z uporabnikovim vnosom ponavadi nekaj uporabnega narediti in ga ne zgolj izpisati na zaslon). To lahko doseûemo s stavkom return. Kaj se zgodi, e funkcijo v naöem programu zdaj öe pokli emo. Razöirimo program na slede na in. 1 def sestej (a, b): # seötej in vrni 88 Poglavje 7 Uporaba in pisanje funkcij 2 c = a + b 3 return c 4 sestej (4 ,5) Program tokrat ne izpiöe ni esar. Zakaj ne? Ker tega od njega nismo nikjer zahtevali. Kaj torej naredi klic funkcije sestej. V konkretnem primeru ni uporabnega, saj izra una vsoto ötevil 4 in 5, rezultat shrani v spremenljivko c in ko se funkcija zaklju i, le-ta izgine, saj nanj ne kaûe nobeno ime ve . Kako pa bi lahko dobljeno vrednost uporabili öe kje druge v naöem programu? Podobno kot pri uporabi funkcije input – tako, da bi rezultat funkcije priredili spremenljivki. 1 def sestej (a, b): # seötej in vrni 2 c = a + b 3 return c 4 rezultat = sestej (4 ,5) 5 print( rezultat ) V zgornjem primeru bomo rezultat izpisali, lahko pa bi z njim naredili tudi karkoli drugega. Stavek return ima dvojno vlogo. Ob njegovem klicu funkcija vrne rezultat, poleg tega pa se njeno izvajanje prekine (podobno, kot e uporabimo stavek break v kombinaciji z zanko while ali for). Povadimo zdaj to na iskanju praötevil. Najprej poskusimo napisati funkcijo, ki uporabniku informacijo o tem, ali ötevilo je praötevilo ali ne, zgolj izpiöe. Zgled 27. Napiöi funkcijo, ki kot argument prejme celo ötevilo in izpiöe, e je podano ötevilo praötevilo ali ne. Reöitev 27. ätevilo je praötevilo, e tega ne deli nobeno od njega manjöe naravno ötevilo, ki je ve je od 1. Sprehoditi se moramo torej ez interval od 2 do naöega ötevila – 1 in za vsako ötevilo iz intervala preveriti, e deli naöe ötevilo. e na intervalu najdemo vsaj enega delitelja, ötevilo ni praötevilo in lahko iskanje takoj prekinemo. e delitelja na celotnem intervalu ne najdemo, lahko sklepamo, da je ötevilo praötevilo. 1 def prastevilo ( stevilo ): 2 for i in range (2, stevilo ): # razpon preiskovanja 3 if stevilo % i == 0: # delitelj? 4 print(stevilo , "ni praö tevilo ") 5 break # dovolj je , da najdemo enega delitelja 6 else: # e se je zanka odvrtela do konca 7 print(stevilo , "je praö tevilo ") — 7.5 Vsaka funkcija vra a rezultat 89 Poskusimo zdaj funkcijo spremeniti, tako da ne bo ni esar izpisovala, ampak bo uporabniku podala povratno informacijo o tem, e je ötevilo praötevilo ali ne. Zgled 28. Napiöi funkcijo, ki kot argument prejme celo ötevilo in vrne vrednost True, e je to ötevilo praötevilo, sicer pa vrne vrednost False. Reöitev 28. 1 def prastevilo ( stevilo ): 2 for i in range (2, stevilo ): # razpon preiskovanja 3 if stevilo % i == 0: 4 return False # prekine funkcijo in vrne False 5 return True # for se je odvrtel do konca — Ta reöitev je bistveno lepöa in enostavnejöa. Iz nje vidimo dodatno prednost stavka return, ki poleg vra anja rezultata prekine izvajanje funkcije. Ko smo v zanki for naöli prvega delitelja, smo prekinili izvajanje funkcije in vrnili rezultat False. S prekinitvijo izvajanja funkcije se je prekinila tudi zanka, zato break ni ve potreben. e je program priöel do vrstice ötevilka 5, zagotovo nismo naöli nobenega delitelja, saj bi sicer funkcija ûe vrnila False in se nehala izvajati. Zato lahko v vrstici 5 brezpogojno vrnemo vrednost True. Tako tukaj ne potrebujemo niti stavka if. Dodatna prednost te reöitve je tudi to, da lahko zdaj rezultat preverjanja uporabimo tudi kje drugje. Lahko na primer napiöemo funkcijo, ki izpiöe vsa praötevila v dolo enem razponu. Zgled 29. Napiöi funkcijo, ki kot argument prejme celo ötevilo in izpiöe vsa pra- ötevila do vklju no podanega ötevila. Reöitev 29. Vse kar je potrebno narediti, je sprehod ez interval ötevil od 2 do podanega ötevila, klic in preverjanje rezultata klica funkcije, ki smo jo definirali zgoraj. 1 def prastevila ( stevilo ): 2 for kandidat in range (2, stevilo +1): # kandidati 3 if prastevilo ( kandidat ): # ali je praötevilo 4 print( kandidat ) — Reöitev je izjemno enostavna. Sprehodili smo se ez vse moûne kandidate za praötevila in za vsakega preverili, e je praötevilo. Kako? Tako, da smo poklicali funkcijo, ki vrne True, e je podano ötevilo praötevilo. Klic te funkcije smo vstavili v stavek if, ki je izpisal ötevilo v primeru izpolnjenosti pogoja. 90 Poglavje 7 Uporaba in pisanje funkcij 7.6 Izbirni argumenti V asih ûelimo, da imajo dolo eni argumenti funkcije svoje vrednosti ûe vnaprej dolo ene (privzete vrednosti), razen v primeru, da ûeli uporabnik za te argumente uporabiti druge vrednosti. e torej uporabnik vrednosti argumentov ne bo podal, bodo uporabljene njihove privzete vrednosti. V nasprotnem primeru bodo uporabljene uporabnikove vrednosti. Tak primer uporabe funkcij smo sre ali ûe pri funkciji print, ki ima kar nekaj izbirnih argumentov. Privzeto gre funkcija print po vsakem klicu v novo vrstico (argument end je privzeto enak znaku za novo vrstico – \n), v primeru ve podanih vrednosti pa te izpiöejo tako, da se med njih vrine presledke (argument sep je privzeto enak presledku). Njune privzete vrednosti lahko povozimo, tako da jih specificiramo ob klicu, npr. kot >>> print (1,2,3, sep=’+’,end=’ ’) 1+2+3 Podobno lahko specificiramo izbirne argumente in njihove vrednosti pri definiciji svojih funkcij. def ime_funkcije (arg1 , arg2 ,... , opc1=v1 , opc2=v2 ,...): Paziti moramo samo na to, da so tisti argumenti, ki nimajo privzetih vrednosti vedno podani pred tistimi, ki privzete vrednosti imajo. Povadimo to na malo bolj sploönem Fibonaccijevem zaporedju. Najprej poskusimo brez uporabe opcijskih argumentov. Zgled 30. Napiöi funkcijo, ki vrne Fibonaccijevo zaporedje ötevil, pri emer naj uporabik poda dolûino zaporedja in prvi dve ötevili v zaporedju. Reöitev 30. Fibonaccijevo zaporedje je zaporedje ötevil, v katerem sta prva dva lena enaka ötevilu 1, vsak nadaljnji len pa je enak vsoti prejönjih dveh lenov. V funkciji ûelimo generirati bolj sploöno Fibonaccijevo zaporedje, ki se za ne s poljubnima öteviloma, npr. a in b. Znotraj funkcije bomo najprej preverili, e je ûeljena dolûina zaporedja n enaka 1 (v tem primeru vrnemo seznam, ki vsebuje zgolj prvo podano ötevilo [a]). Sicer bomo naredili seznam z obema podanima öteviloma ([a,b]). Potem bomo öe n-2-krat izvedli zanko, v kateri bomo v seznam vsaki dodali nov element, ki bo predstavljal vsotot trenutno zadnjih dveh elementov seznama. 1 def fibonacci (n, a, b): 2 if n == 1: 3 return [a] 4 f = [a,b] 5 for i in range (3,n+1): 6 f. append (f[ -1]+f[ -2]) 7 return f 7.6 Izbirni argumenti 91 — V primeru, da uporabnik ûeli imeti zaporedje dolûine 1, funkcija vrne zaporedje z enim elementom, in sicer a. V nasprotnem primeru naredi za etno zaporedje z elementoma a in b in potem v zanki doda ustrezno ötevilo dodatnih elementov, ki vsaki predstavljajo vsoto zadnjih dveh elementov zaporedja. S to reöitvijo sicer ni ni narobe, je pa dejstvo to, da si kot Fibonaccijevo zaporedje ponavadi predstavljamo zaporedje ötevil, ki se za ne z vrednostma 1, 1. Smiselno bi torej bilo, da se privzeto naöe zaporedje za ne s ötevili 1, 1, razen e uporabnik tega ne specificira druga e. Zgled 31. Napiöi funkcijo, vrne Fibonaccijevo zaporedje ötevil, pri emer naj uporabnik poda dolûino zaporedja. Uporabnik lahko poda tudi prvi dve ötevili v zaporedju, ki sta privzeto enaki 1. Reöitev 31. Funkcijo bomo dopolnili tako, da sta argumenta a in b opcijska in da sta privzeto postavljena na vrednost 1. 1 def fibonacci (n, a=1, b=1): 2 if n == 1: 3 return [a] 4 f = [a,b] 5 for i in range (3,n+1): 6 f. append (f[ -1]+f[ -2]) 7 return f — Zgornjo funkcijo lahko torej pokli emo tudi tako, da podamo samo dolûino zaporedja. V tem primeru bosta prvi dve ötevili v zaporedju enaki 1, 1. V primeru, da jo bomo poklicali tako, da podamo öe vrednosti za argumenta a in b pa bosta za za etna elementa uporabljeni ti vrednosti. 8 Uporaba in pisanje modulov 8.1 Kaj so moduli? Moduli predstavljajo Pythonove datoteke, ki vsebujejo implementacijo dolo enih funkcij, spremenljivk in razredov (angl. classes). Module lahko vklju imo v svoje programe in na ta na in razöirimo osnovne funkcionalnosti jezika Python. Primeri ûe vgrajenih modulov, ki jih ni potrebno posebej namestiti, so modul math, v katerem so definirane dolo ene matemati ne funkcije in konstante, modul time za vra anje podatkov o asu in tvorjenje zakasnitev ter modul random za delo s (psevdo)naklju nimi ötevili. 8.2 Uporaba modulov Module lahko v svoje programe vklju imo na razli ne na ine, v vseh primerih pa uporabljamo rezervirano besedo import. e ûelimo npr. v naö program uvoziti celoten modul math, lahko to naredimo s slede o vrstico: import math oziroma v sploönem import ime_modula Najprej lahko preverimo kaj uvoûen modul dejansko ponuja. e je pisec modula bil priden in napisal tudi dokumentacijo (v obliki komentarjev), bomo za to lahko uporabili funkcijo help help( ime_modula ) Funkcija nam bo izpisala nekaj osnovnih informacij o modulu, tako da bo uporaba laûja. Seveda pa lahko informacije o modulu poiö emo tudi na internetu, ki pa v asih ni na voljo (npr. v asu pisanja kolokvijev in izpitov), zato se je dobro navaditi tudi uporabe zgoraj omenjene funkcije. e bi zdaj ûeleli dostopati do posamezen funkcije, ki je v uvoûenem modulu definirana, bi to naredili na slede na in ime_modula . ime_funkcije ( argumenti ) 93 94 Poglavje 8 Uporaba in pisanje modulov e bi npr. ûeleli izra unati sinus ötevila shranjenega v spremenljivki x in rezultat shraniti v spremenljivko y, bi za to uporabil funkcijo sin, ki je vsebovana v modulu math. Poklicali bi jo takole y= math.sin(x) V asih imajo moduli zelo dolga in teûko berljiva imena. e ûelimo modul uvoziti pod druga nim imenom (lahko bi rekli psevdonimom), uvoz dopolnimo z as stavkom: import dolgo_ime_modula as psevdonim Tako lahko pri klicanju funkcij (ali pa esarkoli ûe) modula podajamo le kratko ime modula. V prejönjem primeru bi kodo lahko spremenili na slede na in: import math as m y=m.sin(x) V dolo enih primerih pa ûelimo iz modula uvoziti le dolo eno funkcijo (spremenljivko, razred). Takrat lahko uporabimo rezervirano besedo from, in sicer takole from ime_modula import ime_funkcije S takim na inom uvaûanja smo uvozili le tisto kar potrebujemo, poleg tega pa zdaj pri klicu funkcije imena modula ni potrebno ve podajati. Primer sinusa bi se spremenil v slede o kodo from math import sin y=sin(x) Zdaj smo iz modula uvozili zgolj funkcijo sin – e bi ûeleli imeti öe kaköno drugo funkcijo, npr. kosinus, bi jo morali uvoziti lo eno oziroma hkrati s funkcijo sin. To bi naredili takole: from math import sin ,cos Lahko pa naredimo öe nekaj, kar ponavadi ni priporo ljivo. Uvozimo lahko vse, kar je v modulu definirano, in sicer namesto imena funkcije podamo *, ki se v ra unalniötvu velikokrat uporablja kot simbol za vse. Rekli bomo torej iz modula uvozi vse: from ime_modula import * V primeru modula math bomo zapisali takole: from math import * Zakaj tak na in uvaûanja ni priporo ljiv? Vse funkcije, spremenljivke in razrede modula smo zdaj dobili v naö globalni imenski prostor. Pri tem je velika verjetnost, da smo si s tem povozili kaköno od spremenljivk, ki jo tam ûe uporabljamo in ima enako ime kot kaköna izmed funkcij ali spremenljivk definiranih v modulu. Zato se takemu na in uvaûanja modulov izogibamo. 8.3 Definicija in uporaba lastnih modulov 95 8.3 Definicija in uporaba lastnih modulov Vsi programi, ki smo jih do zdaj napisali, predstavljajo module, ki jih lahko uvozimo v druge programe. To pomeni, da pri reöevanju nekega (bolj kompleksnega) problema ni potrebno vse kode napisati v isti datoteki, ampak lahko datoteko razdelimo po smiselnih modulih, pri emer lahko funkcije prvega modula uporabljamo v drugem in obratno. Pri tem lahko uporabimo kodo opisano v prejönjem razdelku. Paziti moramo le na to, da se modul, ki ga uvaûamo nahaja v isti mapi kot modul, v katerega kodo uvaûamo. V nasprotnem primeru moramo pri uvaûanju modula do drugega modula podati öe pot do njega. e se modul, ki ga ûelimo uvoziti, npr. nahaja v podmapi mapi podmapa, ga bomo uvozili na slede na in: import podmapa . ime_modula e v tem primeru ne ûelimo, da modul vsaki posebej kli emo z imenom podmapa.ime_modula, ga je smiselno uvoziti pod krajöim imenom takole import podmapa . ime_modula as ime_modula Mogo e se spraöujete zakaj poti ni bilo potrebno podajati pri uvaûanju modula math. Dejstvo je, da so dolo eni moduli v Python ûe vgrajeni, sicer pa Python module, poleg v trenutni delovni mapi, iö e tudi v mapi lib\site-packages, kamor se shranijo namestitve vseh modulov, ki jih bomo v prihodnosti potencialno öe namestili. 8.4 Nameö anje novih modulov Na spletu obstaja veliko modulov, ki so jih razvili programerji pred nami. Te lahko uporabimo, kadar ûelimo pri reöevanju dolo enega problema uporabiti viöjenivojske sestavine. e ûelimo npr. narisati graf povpre ne mese ne pla e v Sloveniji, nam ni potrebno ötudirati, kako se lotili kakrönegakoli risanja v jeziku Python, ampak enostavno uporabimo knjiûnico (knjiûnica ni ni drugega kot zbirka modulov) matplotlib in njene funkcije za risanje grafov. Problem, s katerim se sre amo, je, da tovrstne knjiûnice oziroma paketi v osnovni razli ici Pythona öe niso nameö eni (razen, e si nismo namestili distribucije Anaconda 1) Pred uporabo jih moramo torej namestiti. Problem nameö anja tovrstnih knjiûnic in paketov je, poleg v asih mukotrpnega procesa iskanja ustreznih namestitvenih datotek in ro ne namestitve, tudi v tem, da za svoje delovanje ve ina knjiûnic in paketov uporablja druge knjiûnice in pakete, ti spet druge in tako naprej. Temu re emo odvisnost med paketi (angl. package dependency). Da pa se s tem obi ajnemu uporabniku Pythona ni potrebno ukvarjati, Python okolje vsebuje orodje pip (angl. pip installs packages), ki preko 1Anaconda je distribucija Pythona za znanstveno ra unanje, ki ima nameö enih ûe ve ino knjiûnic, ki jih za tako ra unanje potrebujemo. Dostopna je na povezavi https://www.anaconda. com/. 96 Poglavje 8 Uporaba in pisanje modulov repozitorija PyPI (angl. Python package index) poiö e in namesti ustrezne pakete avtomatsko. Nameö en je ûe skupaj z osnovno distribucijo Pythona. Vse kar poleg tega potrebujemo je öe internetna povezava in ime knjiûnice oziroma paketa, ki ga ûelimo namestiti. Orodje pip bomo pognali iz sistemske ukazne vrstice (v operacijskem sistemu Widnows jo zaûenemo tako, da v start meni vpiöemo cmd). V primeru, da smo ob namestitvi Pythona obkljukali opcijo Add Python to path, lahko orodje pip poûenemo iz poljubne lokacije. V nasprotnem primeru se moramo premakniti v mapo, kjer pip nameö en (podmapa Scripts mape, kjer je nameö en Python). Paket z imenom ime_paketa zdaj namestimo s slede im ukazom > pip install ime_paketa in pip bo poskrbel za vse ostalo. 9 Spremenljivost podatkovnih tipov in terke 9.1 Kaj je spremenljivost? Dolo eni podatkovni tipi v Pythonu so spremenljivi (angl. mutable), dolo eni pa ne. Kaj to pomeni? e je nek podatek nespremenljiv (angl. immutable), to pomeni, da ga po tistem, ko je enkrat ustvarjen, ne moremo ve spreminjati. Lahko pa naredimo nov podatek, ki odraûa spremembo, ki jo ûelimo nad podatkom narediti. Primeri nespremenljivih podatkovnih tipov so ötevila tipa int in float, niz oziroma str in bool (spremenljivost osnovnih podatkovnih tipov v jeziku Python prikazuje tabela 9.1). To so torej skoraj vsi podatkovni tipi, ki smo jih do sedaj spoznali. e je dolo en podatek spremenljiv, potem ga lahko spreminjamo tudi kasneje. Primer spremenljivega podatkovnega tipa je seznam oziroma list. Spremenljivost podatkovnih tipov na videz izgleda kot nekaj, s imer se nam pri osnovah programiranja niti ne bi bilo potrebno ukvarjati. éal pa ima veliko posledic, ki jih brez razumevanja spremenljivosti teûko razumemo, zato je smiselno, da si celoten koncept podrobneje pogledamo. Tabela 9.1 Spremenljivost osnovnih podatkovnih tipov v jeziku Python podatkovni tip opis spremenljiv bool boolean (True, False) Ne int integer (celo ötevilo) Ne float floating-point (decimalno ötevilo) Ne str string (niz) Ne list list (seznam) Da tuple tuple (terka) Ne dict dictionary (slovar) Da set set (mnoûica) Da frozenset frozenset (nespremenljiva mnoûica) Ne 97 98 Poglavje 9 Spremenljivost podatkovnih tipov in terke 9.2 Kaj se zgodi ob prirejanju spremenljivk? Kaj se zgodi, ko spremenljivki priredimo neko vrednost ûe vemo. V imenskem prostoru, kjer spremenljivko definiramo, se pojavijo imena, ki smo jih dodelili spremenljivkam, v pomnilniku pa se ustvarijo vrednosti, na katere ta imena kaûejo. Zaporedje prireditvenih stavkov >>> i = 1 >>> niz = ’ABC ’ >>> seznam = [1 ,2 ,3] lahko ponazorimo s sliko 9.1. Kaj pa se zgodi, e spremenljivko priredimo drugi imenski prostor vrednosti i 5 niz 'ABC' seznam [1,2,3] Slika 9.1 Ob prireditvi se imenu spremenljivke priredi podana vrednost. spremenljivki, na primer takole: >>> i = 1 >>> j = i >>> niz1 = ’ABC ’ >>> niz2 = niz1 >>> seznam1 = [1 ,2 ,3] >>> seznam2 = seznam1 Nekaj podobnega smo sre ali ûe pri klicu funkcije. Spomnimo se, da Python v takem primeru naredi t.i. plitvo kopijo spremenljivke. To pomeni, da vrednost v pomnilniku dobi novo ime. Do dejanskega ( globokega) kopiranja vrednosti v tem primeru ne pride. To lahko ponazorimo s sliko 9.2. Na tak na in je delovanje tako s asovnega staliö a (hitrost) kot tudi s prostorskega staliö a (poraba pomnilnika) bolj var no. 9.3 Kaj se zgodi ob spreminjanju vrednosti spremenljivk? Kaj pa se zgodi, e vrednost nove (ali pa stare) spremenljivke spremenimo? Vse skupaj zavisi od tega ali je podatek, ki ga spreminjamo spremenljiv ali ne. Spo- 9.3 Kaj se zgodi ob spreminjanju vrednosti spremenljivk? 99 imenski prostor vrednosti i 5 j niz1 'ABC' niz2 seznam1 [1,2,3] seznam2 Slika 9.2 Ob prireditvi spremenljivke drugi spremenljivki se ustvari plitva kopija spremenljivke. mnimo se. Spremenljiv podatek lahko spreminjamo, ko pa pokusimo spremeniti nespremenljiv podatek, se ustvari njegova kopija (globoka), ki odraûa narejeno spremembo. e npr. uporabimo operator +=, bo v primeru spremenljivega podatka spremenjen obstoje podatek, v primeru nespremenljivega podatka pa bo ustvarjen nov podatek, ki bo odraûal narejeno spremembo. Kaj pa se zgodi v primeru, da na podatek kaûe ve imen, kot v scenariju zgoraj. Ali se bo po izvedbi spodnje kode sprememba odraûala tudi preko drugih imen podatka? Poglejmo si spodnjo kodo. >>> i = 1 >>> j = i >>> j += 1 >>> niz1 = ’ABC ’ >>> niz2 = niz1 >>> niz2 += ’D’ >>> seznam1 = [1 ,2 ,3] >>> seznam2 = seznam1 >>> seznam2 += [4] Zanima nas ali se po spreminjanju spremenljivk j, niz2 in seznam2 spremembe odraûajo tudi na spremenljivkah i, niz1 in seznam1. Odgovor ni enostaven da ali ne. Odgovor je namre odvisen od spremenljivosti podatka, ki ga spreminjamo. 100 Poglavje 9 Spremenljivost podatkovnih tipov in terke Situacijo po spreminjanju podatka z operatorjem += prikazuje slika 9.3. V primeru, imenski prostor vrednosti i 5 j 6 niz1 'ABC' niz2 'ABCD' seznam1 [1,2,3,4] seznam2 Slika 9.3 Ob spreminjanju spremenljivih podatkov se spremenijo vse plitve kopije podatka. da je podatek spremenljiv, se torej sprememba odraûa na vseh spremenljivkah, ki predstavljajo plitve kopije tega podatka. S tem ko v zgornjem zgledu spreminjamo spremenljivko seznam2, spreminjamo tudi spremenljivko seznam1. Po drugi strani spreminjanje spremenljivk j in niz2 ustvari globoko kopijo spremenljivk j in niz2, ki odraûa narejeno spremembo. Globoka kopija predstavlja nov podatek, tj. podatek ki se razlikuje od tistega, na katerega kaûeta imeni i in niz1. Posledica tega je, da spreminjanje vrednosti spremenljivk j in niz2 na vrednostih spremenljivk i in niz1 ne vplivajo, saj pripadajo nespremenljivim podatkovnim tipom. 9.4 Ali funkcije spreminjajo vrednosti svojim argumentom? Spomnimo se, da se ob klicu funkcije ustvari lokalni imenski prostor funkcije. V lokalnem imenskem prostoru se ob klicu spremenljivkam, ki nastopajo kot argumenti funkcije, priredijo vrednosti, s katerimi smo funkcijo poklicali. V primeru, da smo funkcijo poklicali z globalnimi spremenljivkami, se argumentom funkcije priredi plitva kopija teh spremenljivk. Vpraöanje pa je ali se bodo ob spreminjanju argumentov funkcije spremembe odraûale tudi izven funkcije, torej po tem, ko se bo funkcija ûe kon ala. Vpraöanje lahko ponazorimo s spodnjim zgledom. 9.4 Ali funkcije spreminjajo vrednosti svojim argumentom? 101 Zgled 32. Kaköna je vrednost spremenljivk st1, niz1 in seznam1 po izvedbi spodnje kode in kaköen bo izpis programa? 1 def spremeni (a, b): 2 a += b 34 i = 1 5 j = 2 6 spremeni (i,j) 7 print(i) 89 niz1 = "ABC" 10 niz2 = "D" 11 spremeni (niz1 ,niz2) 12 print(niz1) 13 14 seznam1 = [1 ,2 ,3] 15 seznam2 = [4 ,5 ,6] 16 spremeni (seznam1 , seznam2 ) 17 print( seznam1 ) Reöitev 32. Ob klicu funkcije spremenljivki, s katerima funkcijo pokli emo, dobita plitvi kopiji z imeni a in b v lokalnem imenskem prostoru funkcije. Znotraj funkcije plitvo kopijo z imenom a spreminjamo. V primeru, da je spremenljivka spremenljivega podatkovnega tipa (npr. seznam) se spreminja obstoje podatek, na katerega kaûe tudi globalna spremenljivka, kar pomeni, da se bo sprememba odraûala tudi izven funkcije. V primeru, da je spremenljivka nespremenljivega podatkovnega tipa, se ustvari globoka kopija podatka, ki bo odraûala narejeno spremembo. Spremeni se torej zgolj spremenljivka, ki je definirana znotraj funkcije, ta sprememba pa izven funkcije ne bo vidna. Spremenljivki i in niz1 se torej po klicu funkcije ne bosta spremenili, spremenljivka seznam1 pa se bo spremenila. Po izvedbi programa bo izpis slede : 1 # nespremenjena vrednost ABC # nespremenjena vrednost [1 ,2 ,3 ,4 ,5 ,6] # spremenjena vrednost — Funkcije torej lahko spreminjajo vrednosti svojim argumentom, tako da so spremembe vidne tudi izven funkcij, ampak samo v primeru, ko so podani argumenti spremenljivega podatkovnega tipa. 102 Poglavje 9 Spremenljivost podatkovnih tipov in terke 9.5 Terke Zdaj, ko vemo, kaj je to spremenljivost, lahko razloûimo tudi, kaj so terke (angl. tuples). Terka oziroma tuple predstavlja sekven en podatkovni tip, ki je nespremenljiv. Ker terke zelo spominjajo na sezname, bi jim lahko rekli tudi nespremenljivi seznami. Na eloma bi lahko pri programiranju shajali tudi brez njih (tako kot bi lahko shajali tudi brez zanke for), ampak njihova uporaba v veliko primerih naredi naöe programe lepöe in boljöe (tako kot uporaba zanke for). 9.6 Uporaba terk Terko definiramo z navadnimi oklepaji, tj. ( in ), znotraj katerih naötejemo elemente. Terko treh elementov, bi lahko definirali na primer takole: >>> terka =(" Janez ", 1.8 , 75) Tudi, e bi elemente naöteli brez oklepajev, bi dobili terko. Takole: >>> terka =" Janez ", 1.8 , 75 >>> terka (" Janez ", 1.8 , 75) >>> type( terka ) < class ’tuple ’> Python je ob naötevanju elementov z vejicami ugotovil, da ûelimo imeti terko in jo naredil. Python ima nekoliko teûav, ko ûelimo narediti terko dolûine 1, saj si v tem primeru oklepaje razlaga kot operator, ki dolo a prioriteto. V primeru, da znotraj oklepajev damo zgolj eno npr. celo ötevilo, bomo torej dobili podatek, ki pripada podatkovnemu tipu int in ne tuple: >>> terka =(1) >>> terka 1>>> type(terka) < class ’int ’> Ena izmed lastnosti terk je naötevanje elementov, ki jih lo imo z vejicami. Kako v primeru enega elementa povemo, da gre za naötevanje? Tako, da za njim napiöemo vejico: tuple: >>> terka =(1 ,) >>> terka (1 ,) >>> type( terka ) < class ’tuple ’> Gre pa seveda tudi brez oklepajev: tuple: 9.7 Seznami terk in razpakiranje elementov terk 103 >>> terka =1, >>> terka (1 ,) >>> type( terka ) < class ’tuple ’> Kaj lahko s terkami po nemo? Podobno kot sezname lahko elemente terke indeksiramo, lahko delamo rezine, lahko preverjamo vsebovanost elementov, z zanko for se lahko ez elemente terke sprehajamo itd. Z njimi lahko delamo torej skoraj vse, kar smo delali s seznami. Skoraj vse? Ker so terke nespremenljive, jih seveda ne moremo spreminjati, tako kot lahko spreminjamo sezname. Poskusimo: >>> terka = (" Janez ", 1.8 , 75) >>> terka [0] = " Marko " TypeError : ’tuple ’ object does not support item assignment O itno res ne gre. Seveda ne, saj so nespremenljive. Zakaj bi terke potem sploh uporabljali? Nekaj primerov, pri katerih je uporaba terk smiselna, je podanih v nadaljevanju poglavja. 9.7 Seznami terk in razpakiranje elementov terk Nenapisano pravilo (ki ga seveda lahko kröimo) je, da v sezname shranjujemo homogene podatke, torej podatke, ki se nanaöajo npr. na isto spremenljivko. To pomeni, da vsak element seznama obravnavamo na enak na in, saj se nanaöa na isto koli ino. Terke se pogosto uporabljajo za shranjevanje heterogenih podatkov, tj. podatkov razli nih tipov, ki pa pripadajo isti entiteti, kot je npr. oseba ali meritev. e si torej ûelimo pri dolo eni entiteti zabeleûiti ve podatkov, lahko uporabimo seznam terk. Na primer, e imamo vzorec oseb, pri emer za vsako osebo beleûimo ime, viöino in telesno maso, potem lahko uporabimo seznam terk, pri emer vsaka izmed terk vsebuje ime, viöino in telesno maso doti ne osebe. Primer takega seznama bi bil meritve = [(" Janez ", 1.8 , 75) , ("Ana", 1.65 , 60) , (" Nika", 1.66 , 55)] Elementi seznama so torej homogeni, kar pomeni, da bomo vsakega obravnavali enako. Elementi seznama so namre terke, ki imajo vsaki enako obliko. Na 0-tem indeksu je shranjeno ime osebe, na indeksu 1 viöina v metrih in na indeksu 2 telesna masa osebe v kilogramih. Elementi posamezne terke pa o itno pripadajo razli nim spremenljivkam. Tak na in predstavitve podatkov bomo sre ali velikokrat. Kako pa lahko tako shranjene podatke uporabimo pri nadaljnji analizi. Na primer pri izra unu in 104 Poglavje 9 Spremenljivost podatkovnih tipov in terke izpisu indeksa telesnih mas posamezne osebe v seznamu. Tako, da se ez seznam sprehodimo z zanko for in v vsaki iteraciji zanke teko o terko razpakiramo in tako pridemo do konkretnih vrednosti. To lahko naredimo na slede na in: for meritev in meritve : ime = meritev [0] visina = meritev [1] masa = meritev [2] itm = masa / visina **2 print("ITM osebe ", ime , "je", itm) Do posameznih elementov terke smo torej priöli z njihovim indeksiranjem. Terke pa lahko razpakiramo veliko hitreje, in sicer tako, da terko priredimo drugi terki, ki vsebuje imena spremenljivk, v katere ûelimo vrednosti shraniti oziroma razpakirati. Takole: ( spremenljivka1 , spremenljivka2 ,...) = terka Paziti moramo le na to, da terka na levi strani vsebuje enako ötevilo elementov kot terka na desni strani prireditvenega stavka. Kaj smo v zgornjem stavku pravzaprav naredili? Naredili smo terko spremenljivk, ki smo ji priredili terko na desni strani. Ker terki spremenljivk nismo dali nobenega imena, je v imenski prostor nismo shranili in zato tudi ni shranjena nikjer v pomnilniku. So pa v pomnilniku ostale spremenljivke, v katere smo razpakirali terko na desni. Kot smo videli ûe zgoraj pa lahko oklepaje pri definiciji tudi izpustimo. Torej lahko napiöemo tudi nekaj takega spremenljivka1 , spremenljivka2 ,... = terka Mimogrede, tako razpakiranje elementov bi delovalo tudi, e bi imeli na desni strani prireditvenega stavka seznam. Tak na in razpakiranja elementov lahko uporabimo v naöem zgledu z ra unanjem indeksa telesnih mas, s imer se koda ob utna skrajöa: for meritev in meritve : ime , visina , masa = meritev itm = masa / visina **2 print("ITM osebe ", ime , "je", itm) Kodo lahko öe dodatno skrajöamo, e razpakiranje naredimo kar v glavi zanke for: for ime , visina , masa in meritve : itm = masa / visina **2 print("ITM osebe ", ime , "je", itm) 9.8 Pakiranje seznamov v sezname terk Seznami terk, kot smo jih sre ali zgoraj, torej predstavljajo lep na in zapisovanja podatkov, ko ûelimo pri posamezni entiteti imeti ve podatkov. Dejstvo pa je, da 9.8 Pakiranje seznamov v sezname terk 105 velikokrat podatkov ne dobimo v taki obliki, ampak dobimo za vsako koli ino svoj seznam. Pri tem so seznami med seboj poravnani, kar pomeni, da istoleûni elementi v vseh seznamih pripadajo isti entiteti. Elementi na indeksu 0 torej pripadajo entiteti 0, elementi na indeksu 1 entiteti 1 itd. V primeru imen, viöin in mas, bi torej imeli tri sezname v obliki imena = [" Janez ", "Ana", " Nika"] visine = [1.8 , 1.65 , 1.66] mase = [75 , 60, 55] Elementi vseh treh seznamov torej na indeksu 0 pripadajo Janezu, na indeksu 1 Ani in na indeksu 2 Niki. Kaj imajo ti seznami skupnega? Indekse! ez take podatke bi se torej lahko sprehodili tako, da se sprehajamo po indeksih in ne direktno po elementih. Naredimo torej sprehod z zanko for od indeksa 0 do dolûine seznama - 1. Dolûine katerega seznama? Ni vaûno, saj so vsi enako dolgi (oziroma vsaj smiselno bi bilo, da so). To bi lahko naredili takole: for i in range(len( imena )): ime = imena [i] visina = visine [i] masa = mase[i] itm = masa / visina **2 print("ITM osebe ", ime , "je", itm) Kako pa bi lahko iz treh seznamov naredili seznam terk, s katerim smo delali zgoraj. Izkaûe se, da se s takim problemom sre amo relativno pogosto, zato nam Python za zapakiranje ve seznamov v seznam terk ponuja vgrajeno funkcijo zip. Funkcija zip iz zgornjih treh seznamov naredi to no to, kar bi si ûeleli: >>> meritve = zip(imena , visine , mase) >>> meritve < zip object at 0 x0000019A2A865D48 > Tale izpis je malo uden, ampak ni z njim ni narobe. Funkcija zip je t.i. iterator, ki dejanski element seznama terk vrne, öele ko ga potrebujemo, oziroma posamezne elemente seznama vra a sproti. e bi ûeleli imeti lepöi izpis, bi lahko do njega priöli tako, da rezultat funkcije zip eksplicitno pretvorimo v seznam s funkcijo list: >>> meritve = list(zip(imena , visine , mase )) >>> meritve [( ’Janez ’, 1.8 , 75) , (’Ana ’, 1.65 , 60) , (’Nika ’, 1.66 , 55)] Sprehod ez sezname lahko torej naredimo na podoben na in kot v prejönjem poglavju, le da prej sezname zapakiramo v seznam terk: for ime , visina , masa in zip(imena , visine , mase ): itm = masa / visina **2 print("ITM osebe ", ime , "je", itm) 106 Poglavje 9 Spremenljivost podatkovnih tipov in terke 9.9 Zahteva po nespremenljivosti V dolo enih primerih Python zahteva uporabo nespremenljivih podatkovnih tipov. Nespremenljive podatkovne tipe moramo uporabiti, kadar ûelimo podatke shranje-vati v mnoûico (set) in kadar ûelimo nek podatek uporabiti kot klju (key) slovarja (dict). e ûelimo v takem primeru uporabiti ve elementov, moramo namesto po seznamu pose i po terki. V teh primerih je torej uporaba terk obvezna. Ve o mnoûicah in slovarjih bomo izvedeli prav kmalu. Drug primer, v katerem bi nespremenljivost bila zaûelena (ne pa obvezna), je, ko ne ûelimo, da funkcija spremeni vrednosti podanega argumenta. Kot smo videli lahko funkcije spreminjajo vrednosti svojih argumentov, tako da bodo spremembe vidne tudi izven funkcij. e bi radi zagotovilo, da se podan argument izven funkcije zagotovo ne bo spremenil, namesto spremenljivega seznama enostavno uporabimo nespremenljivo terko. V tem kontekstu si poglejmo spodnji zgled. Zgled 33. Kaköna je vrednost spremenljivk seznam1 in terka1 po izvedbi spodnje kode in kaköen bo izpis programa? 1 def spremeni (a, b): 2 a += b 34 seznam1 = [1,2,3] 5 seznam2 = [4 ,5 ,6] 6 spremeni (seznam1 , seznam2 ) 7 print( seznam1 ) 89 terka1 = (1,2,3) 10 terka2 = (4 ,5 ,6) 11 spremeni (terka1 , terka2 ) 12 print( terka1 ) Reöitev 33. Kot smo videli ûe prej, se sprememba, ki smo jo nad plitvo kopijo spremenljivke seznam1 naredili znotraj funkcije, odraûa tudi izven funkcije, saj je seznam spremenljiv podatkovni tip. Ko torej spreminjamo njegovo plitvo kopijo, s tem spreminjamo vse spremenljivke, ki nanj kaûejo. Kaj pa se zgodi, ko funkcijo pokli emo s terko. Najprej se ustvari plitva kopija terke v lokalnem imenskem prostoru funkcije (spremenljivka a). Ker je terka nespremenljiv podatkovni tip, je ne moremo spreminjati. Ob njenem spreminjanju se zato ustvari globoka kopija, torej nov podatek v pomnilniku, ki odraûa narejeno spremembo. Na ta podatek pa kaûe zgolj lokalna spremenljivka a. Ko se funkcija zaklju i, njen lokalni imenski prostor skupaj z lokalno spremenljivko a izgine (tako kot tudi spremenjena terka – globoka kopija terke, s katero smo funkcijo poklicali). V sled temu sprememba, ki smo jo naredili znotraj funkcije, izven funkcije ni vidna. 9.9 Zahteva po nespremenljivosti 107 Po izvedbi funkcije spremeni ima spremenljivka seznam1 spremenjeno vrednost ([1,2,3,4,5,6]), spremenljivka terka1 pa ostane taka, kot je bila pred klicem funkcije ((1,2,3)). Izpis programa je torej [1 ,2 ,3 ,4 ,5 ,6] # spremenjena vrednost (1 ,2 ,3) # nespremenjena vrednost — Funkcijam, ki kot argumente sprejemajo spremenljive podatkovne tipe, spremenje-nih vhodnih argumentov ni potrebno vra ati, saj se bodo spremembe odraûale tudi izven funkcije. Funkcije, ki kot argumente sprejemajo nespremenljive podatkovne tipe, morajo spremenjene vhodne argumente eksplicitno vrniti, saj so spremenjene vrednosti sicer za vedno izgubljene. Oba na ina si poglejmo v spodnjih zgledih. Najprej si poglejmo kako se lotiti pisanja in uporabe funkcije, ki sprejema nespremenljive podatke. Zgled 34. Napiöi funkcijo dodaj_AT_niz, ki sprejme dve nukleotidni zaporedji zapisani kot niza in v prvo zaporedje doda vse ponovitve baz A in T v enakem zaporedju kot nastopajo v drugem nizu. Funkcijo uporabi na zaporedjih ’ATCG’ in ’AATGGAATGG’, tako da bo prvo zaporedje po njeni izvedbi spremenjeno. Reöitev 34. Funkcija sprejema in spreminja podatke tipa str, ki je nespremenljiv podatkovni tip. e bomo vhodne argumente spreminjali znotraj funkcije, se te spremembe izven funkcije ne bodo odraûale, kar pomeni, da mora funkcija vra ati spremenjen niz. Napiöimo jo. def dodaj_AT_niz ( zaporedje1 , zaporedje2 ): for baza in zaporedje2 : if baza in ’AT ’: zaporedje1 += baza return zaporedje1 Na koncu torej vrnemo spremenjeno zaporedje1. Kljub temu, da smo znotraj funkcije to spremenljivko spreminjali, spremembe izven funkcije ne bodo vidne. Klic funkcije moramo izvesti na tak na in, da bo spremenila vrednost prve spremenljive, s katero funkcijo kli emo. Kako to dose i? Enostavno tako, da rezultat funkcije priredimo tej spremenljivki. Takole: >>> zaporedje1 = ’ATCG ’ >>> zaporedje2 = ’AATGGAATGG ’ >>> zaporedje1 = dodaj_AT_niz ( zaporedje1 , zaporedje2 ) Na tak na in smo vrednost spremenljivke zaporedje1 spremenili. — 108 Poglavje 9 Spremenljivost podatkovnih tipov in terke Poglejmo si öe kaköne so razlike pri delu s spremenljivimi podatki. Zgled 35. Napiöi funkcijo dodaj_AT_seznam, ki sprejme dve nukleotidni zaporedji zapisani kot seznama enoznakovnih nizov (baz) in v prvo zaporedje doda vse ponovitve baz A in T v enakem zaporedju kot nastopajo v drugem nizu. Funkcijo uporabi na zaporedjih [’A’,’T’,’C’,’G’] in ’A’,’A’,’T’,’G’,’G’,’A’,’A’,’T’,’G’,’G’, tako da bo prvo zaporedje po njeni izvedbi spremenjeno. Reöitev 35. Navodilo naloge je prakti no enako kot prej, le da tokrat namesto nespremenljivih podatkovnih tipov uporabljamo spremenljive. To pomeni, da ni potrebe po tem, da funkcija vra a spremenjen rezultat, saj se bodo spremembe odraûale ûe preko podanega argumenta. Koda je torej slede a: def dodaj_AT_seznam ( zaporedje1 , zaporedje2 ): for baza in zaporedje2 : if baza in ’AT ’: zaporedje1 . append ( baza ) Tokrat funkcija ne vra a ni esar uporabnega, zato njenega rezultata nima smisla ni emur prirejati. Vse kar potrebujemo je klic funkcije z ustreznimi argumenti: >>> zaporedje1 = [’A’,’T’,’C’,’G’] >>> zaporedje2 = [’A’,’A’,’T’,’G’,’G’,’A’,’A’,’T’,’G’,’G’] >>> dodaj_AT_seznam ( zaporedje1 , zaporedje2 ) Prepri ajmo se, e je vrednost spremenljivke zaporedje1 res spremenjena: >>> zaporedje1 [’A’, ’T’, ’C’, ’G’, ’A’, ’A’, ’T’, ’A’, ’A’, ’T’] — 10 Slovarji 10.1 Zakaj slovarji? Zdaj smo se ûe pobliûje spoznali z razli nimi sekven nimi podatkovnimi tipi, med katere smo uvrstili nize, sezname in terke. V spremenljivke, ki so pripadale tem tipom, smo lahko shranili ve podatkov, pri emer je bil podatek vedno povezan z dolo enim indeksom. e se osredoto imo na sezname (kar bomo povedali velja sicer tudi za nize in terke), lahko re emo, da so podatki v njih na nek na in urejeni, ko smo v sezname dodajali nove podatke, smo te ponavadi dodajali na konec seznama, iskanje pa je potekalo tako, da smo se morali z zanko for sprehoditi ez celoten seznam in iskati dokler elementa nismo naöli. Sezname smo lahko tudi sortirali po nekem klju u (recimo glede na relacijo <). Pri tem je bil rezultat sortiranja tak, da smo na manjöih indeksih dobili elemente, ki so imeli manjöo vrednost od tistih na ve jem indeksu, pri emer je bila upoötevana uporabljena relacija. V dolo enih primerih pa je bolj priro no, e lahko do vrednosti v spremenljivki dostopamo öe preko esa drugega kot njenega indeksa. Pomislimo npr. na telefonski imenik ötevil (npr. v naöem mobilnem telefonu), kjer lahko do telefonske ötevilke neke osebe, dostopamo preko imena te osebe. Rekli bi lahko, da je ime osebe klju preko katerega dostopamo do telefonske ötevilke oziroma vrednosti. Na podoben na in iö emo gesla v Slovarju slovenskega knjiûnega jezika (ali pa kakönem drugem slovarju), kjer kot klju nastopa iskana beseda oziroma geslo, kot vrednost pa razlaga iskane besede. Za razlago dolo ene besede nam torej ni potrebno preiskati celotnega slovarja, ampak njeno razlago poiö emo preko gesla, ki ga je v slovarju na eloma enostavno (in predvsem hitro) najti. V takih primerih lahko uporabimo v Python ûe vgrajen podatkovni tip dict (angl. dictionary) oziroma po slovensko kar slovar. V primeru slovarjev posamezna vrednost (angl. value) ni vezana na dolo en indeks, ampak je povezana na klju (angl. key). Elemente slovarja torej vedno podajamo kot pare klju :vrednost. Kaköna je prednost takega na ina shranjevanja podatkov? Uporabno je takrat, ko klju e, po katerih shranjujemo in kasneje iö emo vrednosti, poznamo. V tem primeru je iskanje zelo hitro, saj nam ni potrebo preiskati celotnega slovarja, ampak slovarju zgolj podamo klju in do vrednosti pridemo takoj. Operacija iskanja je torej zelo hitra v primerjavi s seznami ali terkami. 109 110 Poglavje 10 Slovarji 10.2 Kako uporabljamo slovarje? Slovarje zapisujemo z zavitimi oklepaji, torej { in }, pri emer elemente naötejemo kot pare klju in vrednost lo ene z dvopi jem (:). Prazen slovar bi naredili takole >>> prazen_slovar = {} Slovar, ki vsebuje enostaven telefonski imenik bi lahko zapisali kot >>> imenik = {" Janez ":" 083455544 ", "Ana":" 084566732 ", " Nika ":" 099563123 "} V tem primeru so klju i slovarja nizi, ki predstavljajo imena oseb, preko katerih lahko pridemo do vrednosti, ki v tem primeru predstavljajo telefonske ötevilke zapisane v obliki nizov. Mimogrede, slovar je spremenljiv podatkovni tip, kot smo zapisali ûe v tabeli 9.1. 10.3 Iskanje vrednosti Telefonsko ötevilko osebe lahko torej najdemo tako, da podamo njeno ime, kar je smiselno, saj imena svojih prijateljev ponavadi poznamo na pamet, njihovih telefonskih ötevilk pa ne. Telefonsko ötevilko od Janeza lahko npr. dobimo tako, da slovar indeksiramo po klju u "Janez": >>> imenik [" Janez "] 083455544 Indeksiranje se torej v primeru slovarjev namesto po indeksih izvaja po klju ih. Kaj pa e bi ûeleli poiskati telefonsko ötevilko od Dejana. Potem bi slovar indeksirali po klju u "Dejan", kar pa nam v konkretnem primeru vrne napako: >>> imenik [" Dejan "] KeyError : ’Dejan ’ Problem je v tem, da klju a "Dejan" v naöem slovarju (öe) ni, zato preko njega slovarja ne moremo indeksirati (tako kot nismo mogli indeksirati seznam z indeksom, ki ga v seznamu ni bilo). Obstoj klju a v slovarju lahko preverimo z operatorjem in: >>> " Dejan " in imenik False >>> " Nika " in imenik True Preden do dolo enega klju a v slovarju dostopamo je torej smiselno, da preverimo, e klju v slovarju sploh obstaja. 10.4 Dodajanje in spreminjanje vrednosti 111 Zgled 36. Napiöi funkcijo poisci, ki kot argument sprejme imenik in ime osebe. e imena ni v imeniku, naj funkcija vrne vrednost False, sicer pa njeno telefonsko ötevilko. Reöitev 36. Reöitev mora pred indeksiranjem po podanem klju u preveriti, e klju v slovarju obstaja. V nasprotnem primeru vrne vrednost False. def poisci (imenik , ime ): if ime in imenik : return imenik [ime] else:return False — 10.4 Dodajanje in spreminjanje vrednosti Indeksiranje po klju ih, ki jih v slovarju ni, pa je v dolo enih primerih dovoljeno, in sicer takrat, ko ûelimo v slovar dodati nov par klju , vrednost. Dodajanje novega elementa namre izvedemo tako, da slovar indeksiramo po novem (neobstoje em) klju u in takemu indeksiranju priredimo vrednost, ki jo ûelimo s tem klju em povezati. e bi npr. ûeleli v slovar dodati Dejana in z njim povezati neko telefonsko ötevilko, npr. 089543678, bi to naredili takole: >>> imenik [" Dejan "] = " 089543678 " V tem primeru napake nismo dobili, v slovarju pa se je ponavil nov par klju , vrednost. >>> imenik {’Janez ’: ’083455544 ’, ’Ana ’: ’084566732 ’, ’Nika ’: ’099563123 ’, ’Dejan ’: ’089543678 ’} Kaj pa se zgodi, e poskusimo v slovar dodati öe enega Dejana? Ker v slovarju do vrednosti dostopamo preko klju ev, morajo biti klju i enoli ni, kar pomeni, da se posamezen klju lahko v slovarju pojavi najve enkrat. e torej naredimo prireditev vrednosti preko klju a, ki v slovarju ûe obstaja, bomo s tem izvedli spreminjanje vrednosti, ki je povezana s tem klju em. Prireditev >>> imenik [" Dejan "] = " 000000000 " bo torej spremenila telefonsko ötevilko Dejana na 000000000. >>> imenik {’Janez ’: ’083455544 ’, ’Ana ’: ’084566732 ’, ’Nika ’: ’099563123 ’, ’Dejan ’: ’000000000 ’} 112 Poglavje 10 Slovarji e bi se ûeleli torej omejiti samo na dodajanje elementov, bi lahko prej preverili, e klju preko katerega dodajamo v slovarju ûe obstaja in dodajanje izvedli le, e takega klju a öe ni. Zgled 37. Napiöi funkcijo dodaj, ki kot argumente prejme imenik, ime osebe in telefonsko ötevilko osebe. Osebo in njeno telefonsko ötevilko naj v slovar doda samo v primeru, e te osebe öe ni v slovarju. Sicer naj izpiöe, da ta oseba ûe obstaja. Reöitev 37. V funkciji bomo tokrat izvedli prirejanje vrednosti, samo e podanega klju a öe ni v slovarju. Poraja pa se dodatno vpraöanje. Ali mora funkcija spremenjen slovar vra ati? Odgovor je ne, saj je slovar spremenljiv podatkovni tip in spremembe slovarja, ki jih bomo v funkciji naredili in ki ga je funkcija prejela kot argument, se bodo odraûale tudi izven funkcije. def dodaj (imenik , ime , stevilka ): if ime not in imenik : imenik [ime] = stevilka else:print("Ta oseba ûe obstaja") — e bi se ûeleli omejiti samo na spreminjanje elementov, bi morali spremeniti pogoj v stavku if, tako da ötevilko prirejamo samo v primeru, e je klju v slovarju ûe vsebovan. Zgled 38. Napiöi funkcijo spremeni, ki kot argumente prejme imenik, ime osebe in telefonsko ötevilko osebe. Telefonsko ötevilko naj spremeni samo v primeru, e je oseba v imeniku ûe vsebovana. Sicer naj izpiöe, da te osebe v imeniku ni. Reöitev 38. Tokrat bomo v funkciji izvedli prirejanje vrednosti samo v primeru, e podan klju v slovarju je. V nasprotnem primeru ne bi izvajali spreminjanja vrednosti za klju em, ampak bi izvajali dodajanje novega para klju , vrednost. def spremeni (imenik , ime , stevilka ): if ime in imenik : imenik [ime] = stevilka else:print("Te osebe ni v imeniku") — V dolo enih primerih, npr. pri ötetju pojavitev ne esa, moramo v slovar dodati nov klju , e tega klju a v slovarju öe ni, ali spreminjati nanj vezano vrednost, e ta klju v slovarju ûe obstaja. Poglejmo si slede primer: 10.5 Brisanje vrednosti 113 Zgled 39. Napiöi funkcijo prestej_baze, ki kot argument prejme nukleotidno zaporedje baz zapisano kot niz. Funkcija naj vrne slovar, ki za posamezno bazo vsebuje ötevilo ponovitev. Reöitev 39. Za razliko od prej bo funkcija slovar vra ala, saj ji ga kot argument nismo podali. V programu bi lahko predpostavljali, da delamo samo z bazami A, T, C in G. Tako bi si na za etku naredili slovar, v katerem kot klju i nastopajo oznake baz, nanje pa so vezane vrednosti 0. Takole: baze = {’A’:0, ’T’:0, ’C’:0, ’G’:0} Slabost takega pristopa je ta, da smo se omejili samo na klju e A, T, C in G. Kaj pa e kot vhod dobimo RNA zaporedje? Ali pa e se v zaporedju pojavi öe kaköna oznaka, ki je nismo predvideli? Boljöi pristop bi bil, da na za etku naredimo prazen slovar in ko v nizu najdemo bazo, ki je v slovarju öe ni, nanjo veûemo vrednost 1 ( e smo jo naöli v nizu prvi , potem to pomeni, da smo jo naöli enkrat). V primeru, da v nizu najdemo bazo, ki v slovarju ûe obstaja, ötevilo njenih pojavitev pove amo za 1. def prestej_baze ( zaporedje ): baze = {} # prazen slovar for baza in zaporedje : if baza not in baze: # baza v slovarju? baze [ baza ] = 1 # ne else:baze[baza] += 1 # da return baze — 10.5 Brisanje vrednosti V dolo enih primerih ûelimo elemente iz slovarjev tudi brisati. To lahko naredimo z besedico del, ki smo jo sre ali ûe pri brisanju elementov iz seznama. Podobno kot pri seznamih tudi iz slovarjev briöemo z indeksiranjem vrednosti, ki jo ûelimo izbrisati: del slovar [ kljuc ] Tudi tokrat je smiselno, da pred brisanjem preverimo, e podan klju v slovarju obstaja (brisanje po neobstoje em klju u bo spet vrnilo napako). Zgled 40. Napiöi funkcijo izbrisi, ki kot argumente prejme imenik in ime osebe, ki jo ûelimo iz imenika izbrisati. Brisanje naj se izvede samo v primeru, ko oseba v imeniku obstaja. Sicer naj funkcija izpiöe, da te osebe v imeniku ni. 114 Poglavje 10 Slovarji Reöitev 40. Spet bomo najprej preverili obstoj klju a, nato pa izvedli brisanje. def izbrisi (imenik , ime ): if ime in imenik : del imenik [ime] else:print("Te osebe ni v imeniku") — 10.6 Klju i in vrednosti Posamezen klju se lahko v slovarju pojavi najve enkrat. Klju i so torej enoli ni identifikatorji, preko katerih pridemo do posamezne vrednosti. Za klju e pa velja tudi to, da morajo biti nespremenljivi. Zakaj? Vrednost, ki je vezana na posamezen klju , se zapiöe na lokacijo v pomnilniku, ki je dolo ena s preslikavo klju a v pomnilniöko lokacijo (angl. hash function). e klju spreminjamo, se bo spremenila tudi vrednost preslikave. Za pravilno delovanje torej klju ev ne smemo spreminjati (ko so enkrat shranjeni v slovarju). Zato je smiselno, da so klju i nespremenljivi podatki. Za vrednosti ni nobene omejitve – uporabimo lahko poljuben podatkovni tip vklju no z drugim, ugnezdenim, slovarjem. Do vseh klju ev v slovarju lahko pridemo z uporabo metode keys, do vseh vrednosti z uporabo metode values, do vseh parov pa z uporabo metode items. >>> imenik . keys () dict_keys ([ ’Janez ’, ’Ana ’, ’Nika ’, ’Dejan ’]) >>> imenik . values () dict_values ([ ’083455544 ’, ’084566732 ’, ’099563123 ’, ’089543678 ’]) >>> imenik . items () dict_items ([( ’Janez ’, ’083455544 ’), (’Ana ’, ’084566732 ’), (’Nika ’, ’099563123 ’), (’Dejan ’, ’089543678 ’)]) Te metode vra ajo nekaj kar je zelo podobno seznamom. Pri metodi keys tako dobimo seznam klju ev, pri metodi values seznam vrednosti, pri metodi items pa seznam terk. Nad rezultatom, ki ga posamezna metoda vrne, se lahko sprehajamo z zanko for. Zanko for pa lahko izvedemo tudi direktno nad slovarjem, pri emer se bomo tako sprehajali ez klju e slovarja: >>> for k in imenik () print(k) Janez Ana Nika 10.6 Klju i in vrednosti 115 Dejan Preko klju ev seveda lahko dostopamo tudi do vrednosti. Zgled 41. Napiöi funkcijo izpisi_imenik, ki kot argument prejme imenik. Funkcija naj izpiöe vsebino imenika, tako da se vsak vnos nahaja v svoji vrstici, ime in telefonska ötevilka pa naj bosta lo ena z dvopi jem. Reöitev 41. V zanki for se lahko sprehajamo direktno ez slovar (sprehod ez klju e), ez klju e preko metode keys ali pa ez pare preko metode items. Uporabimo zadnjo moûnost. def izpisi_imenik ( imenik ): for ime , stevilka in slovar . items (): print(ime , ":", stevilka ) — Podoben sprehod bi lahko naredili tudi kadar npr. iö emo najve jo vrednost v slovarju. Zgled 42. Napiöi funkcijo naj_baza, ki kot argument sprejme zaporedje baz, vrne pa ime baze, ki se v zaporedju pojavi najve krat. Pri tem si pomagaj s funkcijo prestej_baze. Reöitev 42. Najprej bomo poklicali funkcijo prestej_baze, ki bo iz niza naredila slovar pojavitev baz. V naslednjem koraku moramo najti bazo, ki ima najve pojavitev. To bomo naredili na podoben na in, kot smo izvedli iskanje najve jega (ali najmanjöega) elementa v seznamu – s sprehodom z uporabo zanke for. def naj_baza ( zaporedje ): baze = prestej_baze ( zaporedje ) naj_B = "" # naj baza M = 0 # ötevilo pojavitev for baza in baze: pojavitev = baze [baza ] if pojavitev > M: naj_B = baza M = pojavitev return naj_B — 116 Poglavje 10 Slovarji 10.7 Imenski prostor in slovarji Poleg tega, da slovarje lahko navadni smrtniki uporabljamo za reöevanje svojih programerskih problemov, slovarje za svoje delovanja uporablja tudi Python sam. Omenili smo ûe, da Python v svojem imenskem prostoru vsebuje vsa imena, preko katerih lahko dostopamo do vrednosti spremenljivk, funkcij in öe esa drugega. Do imenskega prostora lahko pridemo s funkcijo globals. Poglejmo si, kaj ta funkcija vrne: >>> globals () {’__name__ ’: ’__main__ ’, ’__doc__ ’: None , ’__package__ ’: None , ’__loader__ ’: < class ’_frozen_importlib . BuiltinImporter ’>, ’__spec__ ’: None , ’__annotations__ ’: {}, ’__builtins__ ’: } Funkcija vrne imena v imenskem prostoru in vrednosti, ki se za imeni skrivajo. Definirajmo novo spremenljivko in pokli imo gornjo funkcijo öe enkrat. >>> x = 1 >>> globals () {’__name__ ’: ’__main__ ’, ’__doc__ ’: None , ’__package__ ’: None , ’__loader__ ’: < class ’_frozen_importlib . BuiltinImporter ’>, ’__spec__ ’: None , ’__annotations__ ’: {}, ’__builtins__ ’: , ’x’: 1} V imenskem prostoru se je zdaj o itno pojavilo ime x, za katerem se skriva vrednost 1. e pogledamo bolj natan no, lahko vidimo, da je funkcija globals vrnila slovar. >>> type(globals ()) < class ’dict ’> V tem slovarju so klju i imena spremenljivk, funkcij itd., vrednosti pa tisto, kar se za imeni skriva. Ko v ukazno vrstico torej napiöem x bo Python v svojem slovarju, ki predstavlja imenski prostor, pogledal, e tam obstaja ime x in vrnil vrednost, ki se za tem imenom skriva. To bi lahko naredili tudi takole: >>> globals ()[ ’x’] 1 Vsaki , ko definiram novo funkcijo ali spremenljivko, se njeno ime in vrednost doda v Pythonov slovar, podobno kot smo prej klju e in vrednosti v slovarje dodajali mi. Kaj se zgodi, ko dolo eni spremenljivki vrednost povozimo - vrednost za klju em z imenom te spremenljivke se enostavno povozi, saj lahko posamezen klju v slovarju nastopa najve enkrat. 11 Mnoûice 11.1 In öe mnoûice Zadnji izmed vgrajenih podatkovnih tipov, ki si ga moramo pogledati, so mnoûice. Mnoûice za predstavitev podatkov uporabljamo takrat, ko ûelimo, da posamezen podatek obravnavamo najve enkrat. Ker nam Python poleg tega nad mnoûicami omogo a izvedbo osnovnih operacij, kot so unija, presek in razlika, mnoûice zelo spominjajo na matemati ne mnoûice. 11.2 Uporaba mnoûic Mnoûice podobno kot slovarje zapisujemo v zavite oklepaje, znotraj katerih naötejemo elemente. Mnoûico elementov 1, 2 in 3, bi torej naredili takole: >>> mnozica = {1 ,2 ,3} >>> mnozica {1, 2, 3} >>> type( mnozica ) < class ’set ’> Kljub temu, da mnoûice uporabljajo podoben zapis kot slovarji, ju Python med seboj brez problemov lo i, saj slovarji za razliko od mnoûic vsebujejo pare klju : vrednost. Do teûave pride le takrat, ko je mnoûica oziroma slovar prazen. Do praznega slovarja pridemo tako, da podamo zavite oklepaje brez elementov: >>> slovar = {} >>> slovar {} >>> type( slovar ) < class ’dict ’> Do prazne mnoûice pridemo z uporabo funkcije set, ki jo pokli emo brez argumentov: >>> mnozica = set () >>> mnozica 117 118 Poglavje 11 Mnoûice set () >>> type( mnozica ) < class ’set ’> 11.3 Omejitve pri uporabi mnoûic Elementi mnoûic imajo zelo podobne omejitve kot klju i slovarjev, za katere prav tako velja, da lahko posamezno vrednost vsebujejo najve enkrat. Prav tako kot za klju e slovarjev tudi za mnoûice velja, da lahko vsebujejo le nespremenljive podatkovne tipe. Mnoûice predstavljajo neurejeno strukturo, kar pomeni, da vrstni red elementov mnoûice ni pomemben in tudi ni dolo en. Elementi torej niso vezani na indekse, zato mnoûic ne moremo indeksirati in nad njimi delati rezine: >>> mnozica = {1 ,2 ,3} >>> mnozica [0] TypeError : ’set ’ object is not subscriptable Prav tako nad mnoûicami ne moremo izvajati aritmeti nih operacij, kot smo jih npr. lahko izvajali nad nizi: >>> {1 ,2 ,3}*3 TypeError : unsupported operand type(s) for *: ’set ’ and ’int ’ >>> {1 ,2 ,3} + {4 ,5 ,6} TypeError : unsupported operand type(s) for +: ’set ’ and ’set ’ 11.4 Osnovne operacije nad mnoûicami Kaj pa pravzaprav potem z mnoûicami sploh lahko po nemo. Ko mnoûico enkrat imamo se lahko ez njo sprehajamo z zanko for: >>> mnozica = {1 ,2 ,3} >>> for element in mnozica : print( element ) 123 Poleg tega lahko preverjamo ali mnoûica dolo en element vsebuje (ali ne) z opera-torjema vsebovanosti: >>> mnozica = {1 ,2 ,3} >>> 1 in mnozica True 11.5 Presek, unija in razlika 119 >>> 2 not in mnozica False >>> 4 in mnozica False ätevilu elementov v mnoûici pravimo tudi mo mnoûice, do katere pridemo s klicem vgrajene funkcije len >>> len ({1 ,2 ,3}) 3 Zgled 43. Napiöi funkcijo razlicni, ki kot argument sprejme niz, kot rezultat pa vrne ötevilo razli nih znakov, ki v nizu nastopajo Reöitev 43. Najprej bomo niz pretvorili v mnoûico s funkcijo set. Ker mnoûica posamezen element vsebuje najve enkrat, se bomo s tem znebili vseh potencialnih ponovitev znakov. Potem samo öe izra unamo in vrnemo mo mnoûice. 1 def razlicni (niz ): 2 mnozica = set(niz) # odstranitev duplikatov 3 return len( mnozica ) # mo mnoûice — Tudi primerjalni operatorji so zdaj prilagojeni matemati ni interpretaciji mnoûic. Ali je prva mnoûica podmnoûica druge, lahko npr. ugotovimo z operatorjem <=: >>> {1 ,2 ,3} <= {1 ,2 ,3 ,4} True 11.5 Presek, unija in razlika Primeri najbolj tipi nih operacij, ki jih prikazujejo diagrami na sliki 11.1, so seveda presek (A & B), unija (A | B) in razlika (A - B). Primer uporabe zgornjih operacij je slede : >>> {1 ,2 ,3} & {3 ,4 ,5} # presek {3} >>> {1 ,2 ,3} | {3 ,4 ,5} # unija {1 ,2 ,3 ,4 ,5} >>> {1 ,2 ,3} - {3 ,4 ,5} # razlika {1 ,2} 120 Poglavje 11 Mnoûice Slika 11.1 Vennova diagrama, ki ponazarjata osnovne operacije nad mnoûicami. Slika na levi prikazuje razliko (A-B in B-A) in presek (A & B) med mnoûicama A in B. Slika na desni prikazuje unijo (A | B) med mnoûicama A in B 11.6 Metode mnoûic: dodajanje in brisanje elementov Dodajanje elementa v mnoûico lahko izvedemo z uporabo metode add, ki kot argument sprejme element, ki ga ûelimo dodati: >>> mnozica = {1 ,2 ,3} >>> mnozica .add (4) >>> mnozica {1, 2, 3, 4} V primeru, da dodajamo element, ki v mnoûici ûe obstaja, metoda ne naredi ni esar. Dodajanje bi lahko izvedli tudi z operatorjem |=, ki naredi unijo mnoûice z neko drugo mnoûico. V tem primeru lahko dodamo ve elementov naenkrat: >>> mnozica = {1 ,2 ,3} >>> mnozica |= {4 ,5} >>> mnozica {1, 2, 3, 4, 5} Brisanje elementov iz mnoûice vröimo z metodo remove. Ta v primeru neobstoja elementa vrne napako. Uporabimo lahko tudi metodo discard, ki v primeru neobstoja napake ne vrne, sicer pa deluje enako kot metode remove: >>> mnozica = {1 ,2 ,3} >>> mnozica . remove (3) >>> mnozica {1, 2} >>> mnozica . remove (3) KeyError : 3 >>> mnozica . discard (2) >>> mnozica {1} 11.7 Zgled uporabe mnoûic 121 >>> mnozica . discard (2) Podobno kot prej, lahko uporabimo tudi operator -=, ki naredi razliko med mnoûico in neko drugo mnoûico in to priredi izhodiö ni mnoûici: >>> mnozica = {1 ,2 ,3} >>> mnozica -= {2 ,3} >>> mnozica {1} 11.7 Zgled uporabe mnoûic Ker so bili sprotni zgledi v tem poglavju relativno skopi, bomo to nadoknadili z malo daljöim zgledom, s katerim bomo povadili tudi slovarje in mogo e öe kaj. Zamislimo si, da bi radi opazovali omreûje prijateljev oziroma povezanost ljudi preko relacije prijateljstvo, bodisi v resni nem ali pa virtualnem ûivljenju. Tako omreûje lahko predstavimo z neusmerjenim grafom, v katerem vozliö a predstavljajo imena oseb, povezave med vozliö i pa prijateljstva med osebami. Te povezave so neusmerjene, saj prijateljstvo deluje v obe smeri: e je oseba 1 prijatelj osebe 2, je tudi oseba 2 prijatelj osebe 1. Primer grafa prijateljstev prikazuje slika 11.2. V Ana Janez Peter Nejc Polona Slika 11.2 Primer grafa omreûja prijateljstev. 122 Poglavje 11 Mnoûice grafu na sliki je npr. Ana prijateljica od Polone, saj sta med seboj povezani, Nejc pa ni prijatelj od Ane, saj med njima povezave ni. Da se bomo lahko lotili nadaljnjih analiz omreûja prijateljstev, moramo tega najprej prestaviti v obliko, ki jo bomo lahko zapisali v Pythonu. V nadaljevanju bomo napisali par funkcij, ki jih bomo lahko uporabili pri posodabljanju omreûja in njegovi analizi. Zgled 44. Izberi si podatkovno strukturo, ki jo boö lahko uporabil pri predstavitvi in analizi omreûja prijateljstev ter z njo predstavi omreûje na sliki 11.2. Pri izbiri strukture upoötevaj to, da ûeliö nad omreûjem vröiti funkcije, kot so dodajanje prijateljev, brisanje prijateljev, iskanje skupnih prijateljev, iskanje osebe z najve prijatelji ipd. Reöitev 44. Omreûje bi lahko predstavili na razli ne na ine, a vendar teûimo k tisti predstavitvi, ki nam bo pri nadaljnjih operacijah nad omreûjem prihranila najve dela, hkrati pa bodo operacije nad omreûjem potekale kar se da hitro. Za nimo z mogo e najbolj intuitivno, a ne preve dobro predstavitvijo, in sicer s seznamom terk. V tem primeru vsaka terka v seznamu vsebuje ime osebe in seznam njenih prijateljev. Omreûje s slike 11.2 bi na tak na in zapisali kot: prijatelji = [( ’Ana ’, [’Janez ’, ’Peter ’, ’Polona ’]), (’Janez ’, [’Ana ’, ’Nejc ’, ’Peter ’, ’Polona ’]), (’Nejc ’, [’Janez ’, ’Polona ’]), (’Peter ’, [’Ana ’, ’Janez ’]), (’Polona ’, [’Ana ’, ’Janez ’, ’Nejc ’])] Zakaj predstavitev ni najboljöa? Zatakne se nam ûe pri izpisovanju vseh prijateljev podane osebe, saj ûe ta zahteva iskanje podane osebe s sprehodom ez (v najslaböem primeru) celoten seznam prijateljev. e je ta seznam dolg, je lahko ta operacija asovno zelo potratna. Stvar bi lahko izboljöali, e bi namesto seznama terk uporabili kar slovar, v katerem so klju i imena oseb, vrednosti pa seznami prijateljev. Takole: prijatelji = {’Ana ’: [’Janez ’, ’Peter ’, ’Polona ’], ’Janez ’: [’Ana ’, ’Nejc ’, ’Peter ’, ’Polona ’], ’Nejc ’: [’Janez ’, ’Polona ’], ’Peter ’: [’Ana ’, ’Janez ’], ’Polona ’: [’Ana ’, ’Janez ’, ’Nejc ’]} S tem smo reöili problem iskanja prijateljev podane osebe, saj do teh pridemo z enostavnim indeksiranjem po imenu osebe, ki nas zanima. äe vedno pa je proble-mati no npr. iskanje skupnih prijateljev dveh oseb, saj zahteva ugnezdeno zanko po seznamih prijateljev teh dveh oseb. Spet je ta operacija lahko asovno potratna, e so ti seznami dolgi, kar v socialnih omreûjih zagotovo ni izklju eno. Temu problemu bi se lahko izognili tako, da prijatelje podane osebe predstavimo z mnoûicami, nad katerimi so operacije kot je presek bolj u inkovite. Poleg tega glede na podan opis 11.7 Zgled uporabe mnoûic 123 problema vrstni red prijateljev ni pomemben, posamezna oseba pa kot prijatelj druge osebe nastopa najve enkrat. Predstavitev bi bila torej slede a: prijatelji = {’Ana ’: {’Janez ’, ’Peter ’, ’Polona ’}, ’Janez ’: {’Ana ’, ’Nejc ’, ’Peter ’, ’Polona ’}, ’Nejc ’: {’Janez ’, ’Polona ’}, ’Peter ’: {’Ana ’, ’Janez ’}, ’Polona ’: {’Ana ’, ’Janez ’, ’Nejc ’}} — Zgled 45. Napiöi funkcijo prijatelji_od, ki kot argument sprejme omreûje prijateljstev in ime osebe in vrne vse prijatelje podane osebe. V primeru, da podane osebe ni v omreûju, naj funkcija to izpiöe. Reöitev 45. Kot smo videli ûe prej, je zaradi izbrane predstavitve omreûja, iskanje prijateljev podane osebe enostavno. Preveriti moramo samo, e oseba v omreûju obstaja. 1 def prijatelji_od ( prijatelji , oseba ): 2 if oseba not in prijatelji : # ali je oseba v omreûju? 3 print("Ta oseba ne obstaja !") 4 else: 5 return prijatelji [ oseba ] — Zgled 46. Napiöi funkcijo dodaj_osebo, ki kot argument sprejme omreûje prijateljstev in ime osebe ter podano osebo doda v omreûje prijateljstev s prazno mnoûico prijateljev. Reöitev 46. V omreûje bomo torej dodali novo osebo (nov klju ), na katerega bomo vezali prazno mnoûico. Smiselno je tudi, da preverimo, e oseba v omreûju ûe obstaja. Ali mora funkcija vra ati spremenjen slovar? Odgovor je seveda ne, saj je slovar spremenljiv podatkovni tip in se bo spreminjanje tega znotraj funkcije odraûalo tudi izven funkcije ( e bomo seveda slovar podali funkciji kot argument). 1 def dodaj_osebo ( prijatelji , oseba ): 2 if oseba not in prijatelji : # dodaj samo , e je öe ni 3 prijatelji [ oseba ] = set () # prazna mnoûica — 124 Poglavje 11 Mnoûice Zgled 47. Napiöi funkcijo spoprijatelji, ki kot argument sprejme omreûje prijateljstev in imena dveh oseb, ki sta se spoprijateljili. e sta osebi ûe v prijateljstvu, naj funkcija to izpiöe. e katerekoli izmed oseb öe ni v omreûju, naj to osebo doda preko funkcije dodaj_osebo. Reöitev 47. Spoprijateljevanje bomo naredili tako, da bomo prvo osebo dodali v mnoûico prijateljev druge osebe in obratno. Pri tem lahko uporabimo metodo add. äe prej bomo po potrebi posamezno osebo dodali v omreûje, e je tam öe ni. 1 def spoprijatelji ( prijatelji , oseba1 , oseba2 ): 2 if oseba1 not in prijatelji : 3 dodaj_osebo ( prijatelji , oseba1 ) 4 if oseba2 not in prijatelji : 5 dodaj_osebo ( prijatelji , oseba2 ) 67 # spoprijatelji samo , e öe nista prijatelja 8 if oseba2 not in prijatelji [ oseba1 ]: 9 # dodaj oseba2 med prijatelje oseba1 10 prijatelji [ oseba1 ]. add( oseba2 ) 11 # dodaj oseba1 med prijatelje oseba2 12 prijatelji [ oseba2 ]. add( oseba1 ) 13 else: 14 print(" Osebi sta ûe v prijateljstvu ") Primer klica: >>> spoprijatelji ( prijatelji , ’Ana ’, ’Nejc ’) >>> prijatelji {’Ana ’: {’Janez ’, ’Peter ’, ’Nejc ’, ’Polona ’}, ’Janez ’: {’Peter ’, ’Ana ’, ’Nejc ’, ’Polona ’}, ’Nejc ’: {’Janez ’, ’Ana ’, ’Polona ’}, ’Peter ’: {’Janez ’, ’Ana ’, ’Polona ’}, ’Polona ’: {’Janez ’, ’Peter ’, ’Ana ’, ’Nejc ’}} >>> spoprijatelji ( prijatelji , ’Ana ’, ’Nejc ’) Osebi sta ûe v prijateljstvu — Zgled 48. Napiöi funkcijo skregaj, ki kot argument sprejme omreûje prijateljstev in imena dveh oseb, ki sta se skregali. e osebi nista v prijateljstvu, naj funkcija to izpiöe. e katerekoli izmed oseb öe ni v omreûju, naj funkcija to izpiöe. Reöitev 48. Pri skreganju bomo prvo osebo odstranili iz mnoûice prijateljev druge osebe in obratno. Pri tem lahko uporabimo metodo remove. äe prej bomo preverili, e osebi sploh sta v omreûju in e sta v relaciji prijateljstva. 11.7 Zgled uporabe mnoûic 125 1 def skregaj ( prijatelji , oseba1 , oseba2 ): 2 if oseba1 not in prijatelji or oseba2 not in prijatelji : 3 print("Ene izmed oseb ni v omreûju!") 4 return # kon aj in ne vrni ni 5 # ce oseba1 ni prijatelj oseba2 , velja tudi obratno 6 if oseba1 not in prijatelji [ oseba2 ]: 7 print(" Osebi nista prijatelja !") 8 return # kon aj in ne vrni ni 9 10 # odstrani prijateljstvo 11 prijatelji [ oseba1 ]. remove ( oseba2 ) 12 prijatelji [ oseba2 ]. remove ( oseba1 ) Primer klica: >>> skregaj ( prijatelji , ’Ana ’, ’Nejc ’) {’Ana ’: {’Janez ’, ’Peter ’, ’Polona ’}, ’Janez ’: {’Peter ’, ’Ana ’, ’Nejc ’, ’Polona ’}, ’Nejc ’: {’Janez ’, ’Polona ’}, ’Peter ’: {’Janez ’, ’Ana ’, ’Polona ’}, ’Polona ’: {’Janez ’, ’Peter ’, ’Ana ’, ’Nejc ’}} >>> skregaj ( prijatelji , ’Ana ’, ’Nejc ’) Osebi nista prijatelja ! — Zdaj pa se lotimo öe analize omreûja. Zgled 49. Napiöi funkcijo najbolj_popularni, ki kot argument sprejme omreûje prijateljstev in vrne ime osebe, ki ima najve prijateljev. Reöitev 49. Spet reöujemo nalogo z iskanjem najve jega elementa v slovarju, tokrat glede na dolûino mnoûice, ki se skriva za posameznim klju em. Reöitev se ne bo dosti razlikovala od programa, ki je iskal oznako nukleotida, ki se v nukleotidnem zaporedju pojavi najve krat. 1 def najbolj_popularni ( prijatelji ): 2 naj_oseba = "" 3 naj_prijateljev = 0 45 for oseba in prijatelji : # sprehod ez klju e 6 # ötevilo prijateljev 7 prijateljev = len( prijatelji [ oseba ]) 8 126 Poglavje 11 Mnoûice 9 if prijateljev > naj_prijateljev : 10 naj_oseba = oseba 11 naj_prijateljev = prijateljev 12 return naj_oseba Primer klica: >>> najbolj_popularni ( prijatelji ) ’Janez ’ Reöitev predpostavlja, da je enako najbolj popularna samo ena oseba, kar lahko reöimo z manjöo dopolnitvijo, tako da si najbolj popularne osebe shranimo v seznam. 1 def najbolj_popularni ( prijatelji ): 2 naj_osebe = [] 3 naj_prijateljev = 0 45 for oseba in prijatelji : # sprehod ez klju e 6 # ötevilo prijateljev 7 prijateljev = len( prijatelji [ oseba ]) 89 if prijateljev > naj_prijateljev : 10 naj_osebe = [ oseba ] 11 naj_prijateljev = prijateljev 12 elif prijateljev == naj_prijateljev : 13 naj_osebe . append ( oseba ) 14 return naj_osebe Primer klica: >>> spoprijatelji ( prijatelji , ’Polona ’,’Peter ’) >>> najbolj_popularni ( prijatelji ) [’Janez ’, ’Polona ’] — Zgled 50. Napiöi funkcije skupni_prijatelji, vsaj_od_enega in brez_njegovih. Vse tri funkcije naj prejmejo omreûje prijateljstev in dve osebi ter vrnejo njune skupne prijatelje, prijatelje vsaj od ene izmed podanih oseb ter tiste, ki so prijatelji prve osebe ne pa prijatelji druge. V primeru, da v omreûju ni obeh podanih oseb, funkcije izpiöejo, da vsaj ene podane osebe v omreûju ni. Reöitev 50. V tej reöitvi bomo povadili uporabo preseka (skupni prijatelji), unije (vsaj od enega) ter razlike (brez njegovih). 11.7 Zgled uporabe mnoûic 127 1 def skupni ( prijatelji , oseba1 , oseba2 ): 2 if oseba1 not in prijatelji or oseba2 not in prijatelji : 3 print("Vsaj ene osebe ni v omreûju!") 4 else: 5 # presek 6 return prijatelji [ oseba1 ] & prijatelji [ oseba2 ] 78 def vsaj_od_enega(prijatelji, oseba1, oseba2): 9 if oseba1 not in prijatelji or oseba2 not in prijatelji : 10 print("Vsaj ene osebe ni v omreûju!") 11 else: 12 # unija 13 return prijatelji [ oseba1 ] | prijatelji [ oseba2 ] 14 15 def brez_njegovih ( prijatelji , oseba1 , oseba2 ): 16 if oseba1 not in prijatelji or oseba2 not in prijatelji : 17 print("Vsaj ene osebe ni v omreûju!") 18 else: 19 # razlika 20 return prijatelji [ oseba1 ] - prijatelji [ oseba2 ] Primer klica: >>> skupni ( prijatelji , ’Ana ’, ’Janez ’) {’Peter ’, ’Polona ’} >>> skupni ( prijatelji , ’Ana ’, ’Jan ’) Vsaj ene osebe ni v omre ûju! >>> vsaj_od_enega ( prijatelji , ’Ana ’, ’Janez ’) {’Janez ’, ’Peter ’, ’Ana ’, ’Nejc ’, ’Polona ’} >>> brez_njegovih ( prijatelji , ’Ana ’, ’Janez ’) {’Janez ’} — Zgled 51. Napiöi funkcijo prijatelji_prijateljev, ki kot argumenta sprejme omreûje prijateljev in ime osebe ter vrne prijatelje prijateljev podane osebe, pri emer naj izpusti prijatelje podane osebe ter podano osebo samo. Reöitev 51. Tokrat se bomo sprehodili ez prijatelje podane osebe z uporabo zanke for ter s prijatelji prijateljev dopolnili na za etku prazno mnoûico. Potem bomo iz te mnoûice izlo ili prijatelje podane osebe in osebo samo. 1 def prijatelji_prijateljev ( prijatelji , oseba ): 2 # prijatelji prijateljev je na za etku prazna 128 Poglavje 11 Mnoûice 3 vsi_pp = set () 45 # sprehod ez prijatelje osebe 6 for prijatelj in prijatelji [ oseba ]: 7 pp = prijatelji [ prijatelj ] 8 # z unijo dodam prijatelje prijatelja 9 vsi_pp |= pp 10 # odstranim osebo 11 vsi_pp . remove ( oseba ) 12 # odstranim neposredne prijatelje osebe 13 vsi_pp -= prijatelji [ oseba ] 14 15 return vsi_pp Primer klica: >>> prijatelji_prijateljev ( prijatelji , ’Nejc ’) {’Ana ’, ’Peter ’} — 12 Oblikovanje nizov 12.1 Delo z nizi Nize smo do zdaj ûe dodobra spoznali. Preden se lotimo dela z datotekami pa si moramo pogledati öe nekaj metod, ki jih lahko nad nizi uporabimo. Ponovimo najprej osnovne operacije, ki smo jih do zdaj ûe izvajali nad nizi. Nad nizi lahko izvajamo zanko for, pri emer v vsaki iteraciji zanke, dostopamo do enega znaka niza. Takole: >>> niz = " abeceda " >>> for znak in niz: print(znak) abeced Nize lahko med sabo seötevamo, emur smo rekli lepljenje ali konkatenacija, poleg tega pa jih lahko mnoûimo s celimi ötevili: >>> "abc" + " " + "def" ’abc def ’ >>> "abc" * 3 ’abcabcabc ’ Njihove elemente lahko tudi indeksiramo in delamo rezine. Ne moremo pa jih spreminjati. Odstranjevanje podniza z besedico del torej ne bo ölo skozi: >>> niz = " abeceda " >>> niz [2:5] ’ece ’ >>> del niz [2:5] TypeError : ’str ’ object does not support item deletion Zakaj ne? Ker so nizi nespremenljivi. Alternativa je seveda ta, da naredimo nov niz, ki odraûa ûeljeno spremembo. V zgornjem primeru bi torej lahko naredili nov 129 130 Poglavje 12 Oblikovanje nizov niz z lepljenjem dveh rezin. Eno pred podnizom, ki ga ûelimo odstraniti, in drugo za podnizom, ki ga ûelimo odstraniti. Takole: >>> niz = " abeceda " >>> niz [:2] + niz [5:] ’abda ’ Iz niza smo tako odstranili podniz "ece". e ûelimo, da se sprememba odraûa tudi preko spremenljivke z imenom niz, bomo nov (spremenjen) niz priredili temu imenu: >>> niz = " abeceda " >>> niz = niz [:2] + niz [5:] >>> niz ’abda ’ Zapakirajmo vse skupaj v funkcijo. Zgled 52. Napiöi funkcijo odstrani, ki sprejme dva niza in iz prvega niza odstrani prvo pojavitev drugega niza ter spremenjen niz vrne. e se drugi podniz v prvem ne pojavi, naj funkcija vrne nespremenjen niz. Reöitev 52. V funkciji moramo najprej preveriti, e in kje se v nizu nahaja podniz. To lahko naredimo tako, da se sprehajamo od za etka do konca niza in reûemo rezine dolûine podniza. Da lahko delamo rezine, se moramo seveda sprehajati po indeksih niza. To bi izgledalo nekako takole: >>> niz = " abeceda " >>> pondiz = "ece" >>> for i in range(len(niz )): niz[i:i+len( podniz )] ’abe ’ ’bec ’ ’ece ’ ’ced ’ ’eda ’ ’da ’ ’a’ Znotraj zanke bomo preverjali, e je odrezana rezina enaka podnizu (zgoraj je to bilo v 3. iteraciji zanke). V tem primeru ga bomo odstranili (podobno kot prej) in vrnili rezultat. Napiöimo celotno funkcijo. 1 def odstrani (niz , podniz ): 2 for i in range(len(niz )): 3 rezina = niz[i:i+len( podniz )] 4 if rezina == podniz : 12.2 Iskanje podnizov 131 5 zacetek = i # za etek rezanja 6 konec = i + len( podniz ) # konec rezanja 7 return niz [: zacetek ] + niz[ konec :] 8 return niz # nismo naöli - vrni nespremenjen niz Vpraöanje je öe ali mora funkcija res vra ati spremenjen niz ali je dovolj, e niz, ki ga funkcija sprejme kot argument, spreminjamo znotraj funkcije (kot smo to delali pri seznamih, slovarjih in mnoûicah). Spremenjen niz moramo seveda eksplicitno vrniti, saj je niz nespremenljiv. Spremembe, ki jih vröimo nad imenom, za katerim se skriva niz, se izven funkcije ne bodo ohranile, zato je potrebno nov (spremenjen) niz vrniti s stavkom return. — Tole je bilo öe kar zakomplicirano. Izkaûe pa se, da nam te in podobnih funkcij ni treba pisati, saj ve inoma ûe obstajajo. Najdemo jih med metodami nizov. Izbrane (tj. tiste, ki jih bomo uporabljali pogosteje) si bomo podrobneje pogledali v nadaljevanju poglavja. 12.2 Iskanje podnizov V prejönjem zgledu se lahko uporabi rezin deloma izognemo z uporabo metode startswith, ki vrne vrednost True, e se niz, preko katerega smo metodo poklicali za ne s podanim podnizom. Reöitev bi torej lahko nekoliko poenostavili: 1 def odstrani (niz , podniz ): 2 for i in range(len(niz )): 3 if niz[i:]. startswith ( podniz ): 4 zacetek = i # za etek rezanja 5 konec = i + len( podniz ) # konec rezanja 6 return niz [: zacetek ] + niz[ konec :] 7 return niz # nismo naöli - vrni nespremenjen niz Poleg metode startswith lahko pri iskanju uporabimo öe metodo endswith, ki preverja, e se niz kon a s podanim podnizom, metodo count, ki preöteje ötevilo pojavitev podniza in metodo find, ki vrne lokacijo podniza oziroma –1, e podniza v nizu ni. Povadimo öe uporabo metode find: 1 def odstrani (niz , podniz ): 2 i = niz.find ( podniz ) 3 if i >= 0: # ali je podniz v nizu 4 zacetek = i # za etek rezanja 5 konec = i + len( podniz ) # konec rezanja 6 return niz [: zacetek ] + niz[ konec :] 7 return niz 132 Poglavje 12 Oblikovanje nizov 12.3 Odstranjevanje in spreminjanje (pod)nizov Podnize pa lahko odstranimo zgolj s klicem metode replace, ki vse pojavitve prvega argumenta zamenja z drugim argumentom. Poskusimo >>> niz = " abeceda " >>> niz. replace ("a","e") ’ebecede ’ Metoda podanega niza ne spreminja. Seveda, saj ga ne more, ker so nizi nespremenljivi. Metoda torej niza ne more spremeniti, lahko pa vrne nov niz, ki odraûa spremembo. e ûelimo s klicem metode spremeniti vrednost, ki stoji za imenom spremenljivke, bomo rezultat klica priredili spremenljivki. Takole: >>> niz = " abeceda " >>> niz = niz. replace ("a","e") >>> niz ’ebecede ’ Kako lahko uporabimo metodo pri odstranjevanju podnizov? Tako, da kot drugi argument podamo prazen niz. >>> niz = " abeceda " >>> niz. replace ("a","") ’beced ’ V navodilu prejönje naloge je bilo zahtevno, da odstranimo samo prvo pojavitev podniza. Tudi to lahko reöimo z dodatnim argumentom, ki ga metoda replace sprejema. Poglejmo si izpis funkcije help: >>> help("". replace ) Help on built -in function replace : replace (old , new , count =-1, /) method of builtins . str instance Return a copy with all occurrences of substring old replaced by new. count Maximum number of occurrences to replace . -1 (the default value ) means replace all occurrences . If the optional argument count is given , only the first count occurrences are replaced . Podamo lahko tudi tretji argument count, ki pove koliko zamenjav naj metoda naredi (privzeto naredi vse zamenjave). Zdaj lahko naöo funkcijo poenostavimo do konca: 12.4 Razdruûevanje in zdruûevanje nizov 133 1 def odstrani (niz , podniz ): 2 return niz. replace (podniz , "", 1) Pogosto uporabljeni metodi za spreminjanje nizov sta öe metodi upper in lower, ki spremenita vse rke niza v velike oziroma male. Ker Python lo i med velikimi in malimi rkami, lahko ti dve metodi uporabimo, kadar te lo itve ne ûelimo imeti in vse pa spremenimo bodisi v velike bodisi v male rke. Povadimo na naslednjem zgledu. Zgled 53. Napiöi funkcijo palindrom, ki kot argument sprejme niz in vrne True, e je niz palindrom in False, e ni. Pri tem naj funkcija ne lo i med malimi in velikimi rkami. Reöitev 53. Kako preveriti, e je niz palindrom, ûe znamo. Tokrat bomo dodali öe to, da preverjanje ne lo i med malimi in velikimi rkami. To bomo naredili tako, da bomo vse rke v nizu pred preverjanjem pretvorili v velike (ali pa v male). 1 def palindrom (niz ): 2 niz = niz. upper () 3 return niz == niz [:: -1] — 12.4 Razdruûevanje in zdruûevanje nizov Funkcija input uporabnikov vnos vedno vrne zapisan kot niz. Prav tako bomo vsebino datotek (v naslednjem poglavju) vedno dobili najprej zapisano kot niz. V primeru, da uporabnik vnese eno ötevilo, lahko ötevilo zapisano kot niz enostavno pretvorimo preko funkcij int oziroma float. V primeru, da uporabnik naenkrat vnese ve ötevil ali pa je vnos sestavljen iz ötevil in drugih znakov, ki jih mogo e ûelimo pretvoriti v kaj drugega pa ta pristop ni ve mogo . Deloma nas sicer lahko reöi funkcija eval, katere uporaba zaradi varnosti v sploönem ni priporo ljiva, poleg tega pa tudi ta slej ko prej odpove. V tem primeru moramo niz raz leniti (angl. parse) sami. Za ta namen lahko uporabimo metodo split, ki ji kot argument podamo lo ilo (angl. separator), preko katerega naj niz lo i v seznam nizov. Poglejmo si primer: >>> niz = "jabolka ,hruöke , slive " >>> niz. split (",") [’jabolka ’, ’hruöke ’, ’slive ’] Povadimo öe na zgledu: Zgled 54. Napiöi program, ki na podlagi uporabnikovega vnosa izra una in izpiöe vsoto podanih ötevil. Uporabnik bo ötevila vnesel v eni vrstici in jih med seboj lo il s podpi ji. 134 Poglavje 12 Oblikovanje nizov Reöitev 54. Program bo uporabnikov vnos (niz) lo il po podpi jih. Sledil bo sprehod ez dobljeni seznam, pretvorba nizov v ötevila in seötevanje. 1 vnos = input(" Vnesi ö tevila lo ena s podpi ji: ") 2 seznam = vnos. split (";") 3 s = 0 # za etna vsota 4 for st in seznam : 5 s += float(st) # pretvorba niza in priötevanje 6 print(s) Zgled izvedbe programa: Vnesi ö tevila lo ena s podpi ji: 20;40.5;60 120.5 — Metodo split lahko pokli emo tudi brez podanega lo ila, pri emer bo privzeto za lo ilo uporabljen prazen prostor (angl. white space). Prazen prostor predstavlja znake, ki na zaslonu predstavljajo to no to – prazen prostor. Sem uvrstimo presledek, tabulator (\t) in novo vrstico (\n) (opomba: kombinacija znakov \t in \n pravzaprav predstavlja en sam znak). Dodatna prednost uporabe metode split brez argumenta je v tem, da bo ve zaporednih ponovitev znakov za prazen prostor obravnaval kot eno samo. Poskusimo: >>> niz = "1 2 \n 3\ t4" >>> niz. split (" ") [’1’, ’’, ’’, ’’, ’2’, ’\n’, ’3\ t4 ’] >>> niz. split () [’1’, ’2’, ’3’, ’4’] Klic metode z argumentom praznega prostora o itno ni odstranil v celoti, klic metode brez argumentov iz niza uspeöno pobral samo relevantne vrednosti. Podobno, kot lahko podan niz glede na podano lo ilo razbijemo na seznam nizov, lahko seznam nizov, glede na podano lo ilo zdruûimo v en niz. V tem primeru uporabimo metodo join, ki jo pokli emo nad nizom, ki predstavlja naöe lo ilo, kot argument pa ji podamo seznam nizov, ki ga ûelimo zdruûiti. Takole: >>> seznam = [" jabolka ", " slive ", " avokado "] >>> ", ".join ( seznam ) ’jabolka , slive , avokado ’ 12.5 Odstranjevanje praznega prostora Dopolnimo najprej naöe iskanje palindromov. 12.5 Odstranjevanje praznega prostora 135 Zgled 55. Napiöi funkcijo palindrom, ki kot argument sprejme niz in vrne True, e je niz palindrom in False, e ni. Pri tem naj funkcija ne lo i med malimi in velikimi rkami, poleg tega pa naj ignorira presledke, znake za nove vrstice in tabulatorje. Reöitev 55. éelimo torej, da bi reöitev delovala tudi nad ne im takim kot je niz "perica reûe raci rep". Niz se prebere enako naprej kot nazaj, ampak samo v primeru, ko presledkov ne upoötevamo. Prva reöitev bo temeljila na tem, da vse bele prostore zamenjamo za prazne nize. 1 def palindrom (niz ): 2 niz = niz. upper () 3 niz = niz. replace (" ","") 4 niz = niz. replace ("\n","") 5 niz = niz. replace ("\t","") 6 return niz == niz [:: -1] Alternativa je, da najprej nad nizom uporabimo metodo split brez podanega argumenta, ki bo niz lo ila v seznam nizov, pri emer bo odstranila ves prazen prostor. Zatem uporabimo metodo join, pri emer kot lo ilo uporabimo prazen niz. Takole: 1 >>> niz = " perica \n reûe \t raci rep" 2 >>> sez = niz. split () 3 >>> sez 4 [’perica ’, ’reûe’, ’raci ’, ’rep ’] 5 >>> niz = "".join(sez) 6 >>> niz 7 ’pericare û eracirep ’ Druga reöitev je torej slede a: 1 def palindrom (niz ): 2 niz = niz. upper () 3 sez = niz. split () 4 niz = "". join (sez) 5 return niz == niz [:: -1] Prve tri vrstice lahko zdruûimo v eno: 1 def palindrom (niz ): 2 niz = "". join (niz. upper (). split ()) 3 return niz == niz [:: -1] — 136 Poglavje 12 Oblikovanje nizov Beli prostor lahko torej odstranjujemo na razli ne na ine. V dolo enih primerih pa ûelimo beli prostor odstraniti samo pred za etkom in po koncu prave vsebine, vmes pa ne. V tem primeru lahko uporabimo metodo strip >>> niz = "\n \t Danes je lep dan! \n" >>> niz. strip () ’Danes je lep dan!’ 12.6 Prilagajanje izpisa in formatiranje nizov Funkcija print podane argumente zdruûi v niz, ki ga izpiöe na zaslon. Pri tem med argumente vstavi presledke (sep = ), na koncu izpisa pa gre v novo vrstico (end = "\n"). Privzeto delovanje lahko spremenimo, tako da nastavimo (povozimo) privzete vrednosti izbirnima argumentom sep in end. V asih pa to ni dovolj in bi bili radi z izpisovanjem nekoliko bolj ustvarjalni. V tem primeru lahko uporabimo metodo format. Metodo format pokli emo nad nizom, ki vsebuje t.i. fiksne dele in dele, ki jih bo metoda zamenjala z argumenti, ki jih bomo podali ob klicu. Slednje znotraj niza ozna imo z zavitimi oklepaji ({ in }). Osnovni primer uporabe je slede : >>> m = 1 >>> "{} meter /ov/a je {} centimeter /ov/a". format(m, m *100) ’1 meter /ov/a je 100 centimeter /ov/a’ Prvo pojavitev zavitega oklepaja je metoda format torej zamenjala s prvim argumentom, drugo pa z drugim. Znotraj zavitih oklepajev lahko tudi eksplicitno navedemo, kateri argument naj se uporabi pri zamenjavi. Takole: >>> m = 1 >>> "{0} m = {1} cm , torej je {1} cm = {0} m". format(m, m *100) ’1 m = 100 cm , torej je 100 cm = 1 m’ Do malo gröega izpisa pride, kadar imamo veliko decimalk: >>> cm = 12.673 >>> "{} cm = {} m". format(cm , cm /100) ’12.673 cm = 0.12673 m’ Rezultate lahko sicer zaokroûujemo z vgrajeno funkcijo round, ki ji kot argument podamo ötevilo decimalk. Alternativa je, da zaokroûevanje podamo samo pri izpisu, tako da znotraj zavitih oklepajev malo bolj natan no povemo kako naj se izpis formatira. Takole: >>> cm = 12.673 >>> " {:5.1 f} cm = {:5.2 f} m". format(cm , cm /100) ’ 12.7 cm = 0.13 m’ 12.7 Formatiranje nizov in f-Strings 137 Kaj smo s tem povedali? Z dvopi jem povemo, da ûelimo izpis malo oblikovati. ätevilo 5 podaja (najmanjöe) ötevilo mest, ki naj jih izpis zasede. Iz izpisa je vidno, da je metoda format pred posamezno ötevilo vstavila presledek, saj je izpisano ötevilo dolgo 4 znake, mi pa smo povedali, da naj bo izpis dolg 5 znakov. e bi bilo naöe ötevilo daljöe od 5 znakov, rezanja ne bi bilo, ampak bi format izpisal vse znake. Za piko smo podali ötevilo decimalk, ki naj bodo v izpisu. Pri centimetrih smo uporabili 1, pri metrih pa 2 decimalki. Oznaka f pomeni, da formatiramo decimalno ötevilo (angl. float). Oblikovanje celih ötevil in nizov je enostavneje. V tem primeru podamo ötevilo mest, ki naj jih ötevilo (minimalno) zavzame. >>> " {:3}: {:15}...{:5.2 f} ¶ C". format (1, " Ljubljana ", 25.3) ’ 1: Ljubljana ...25.30 ¶ C’ Povemo lahko öe ali naj se posamezen izpis poravna levo (<), desno (>) ali sredinsko (ˆ): >>> "{: <3}: {: <15}...{: >5.2 f} ¶ C". format (1, " Ljubljana ", 25.3) ’1 : Ljubljana ...25.30 ¶ C’ Presledke lahko zamenjamo tudi za kaj drugega, npr. za pike. >>> "{: <3}: {:. <15}...{: >5.2 f} ¶ C". format (1, " Ljubljana ", 25.3) ’1 : Ljubljana .........25.30 ¶ C’ Zdaj lahko uporabo formatiranja demonstriramo na celotnem zgledu: >>> stevilke = [1 ,2 ,3] >>> kraji = [" Ljubljana ", " Maribor ", " Nova Gorica "] >>> temperature = [25.3 , 21.32322 , 26.433333] >>> for st , kraj , temp in zip( stevilke , kraji , temperature ): print("{: <3}: {:. <15}...{: >5.2 f} ¶ C". format(st , kraj , temp )) 1 : Ljubljana .........25.30 ¶ C 2 : Maribor ...........21.32 ¶ C 3 : Nova Gorica .......26.43 ¶ C 12.7 Formatiranje nizov in f-Strings Novejöa in hitrejöa alternativa metodi format je formatiranje nizov zapisanih v obliki, ki ji re emo f-niz oziroma f-Strings. Tovrstne nize zapisujemo tako, da pred za etkom niza (pred navednice) zapiöemo rtko f. Python bo to razumel kot niz, ki ga mora öe dodatno oblikovati. V osnovi tak niz zapiöemo takole: >>> f’niz ’ ’niz ’ V f-niz lahko v zavite oklepaje vstavimo spremenljivke, ki jih bo Python pri izpisu zamenjal za njihove vrednosti, podobno kot pri metodi format: 138 Poglavje 12 Oblikovanje nizov >>> m = 1 >>> f"{m} meter /ov/a je {m *100} centimeter /ov/a" ’1 meter /ov/a je 100 centimeter /ov/a’ Tak na in oblikovanja je nekoliko hitrejöi kot oblikovanje z metodo format, predvsem pa je tak na in oblikovanja bolj pregleden. Podobno kot pri metodi format lahko v zavite oklepaje podamo oblikovanje izpisa posamezne spremenljivke, le da je v tem primeru vrstni red malenkost druga en, saj spremenljivko podamo kar v zavite oklepaje pred na inom njenega oblikovanja: f"{ spremenljivka : oblikovanje }" Poskusimo na zgledu: >>> cm = 12.673 >>> f"{cm :5.1 f} cm = {cm /100:5.2 f} m" ’ 12.6 cm = 0.13 m’ Ostalo je zelo podobno oziroma enako kot pri uporabi metode format. Poskusimo öe na zadnjem zgledu iz prejönjega razdelka: >>> stevilke = [1 ,2 ,3] >>> kraji = [" Ljubljana ", " Maribor ", " Nova Gorica "] >>> temperature = [25.3 , 21.32322 , 26.433333] >>> for st , kraj , temp in zip( stevilke , kraji , temperature ): print(f"{st : <3}: {kraj :. <15}...{ temp : >5.2f} ¶ C") 1 : Ljubljana .........25.30 ¶ C 2 : Maribor ...........21.32 ¶ C 3 : Nova Gorica .......26.43 ¶ C Rezultat je torej enak kot v primeru uporabo metode format, je pa koda postala nekoliko krajöa in bolj pregledna. 13 Delo z datotekami 13.1 Zakaj pisati v datoteke in brati iz njih? Programi, ki smo jih napisali do zdaj, so podatke hranili le za as svojega izvajanja v t.i. delovnem pomnilniku. Ko se je nek program kon al, so ti podatki izginili in ko smo program ponovno zagnali, smo morali te podatke ponovno zgenerirati, npr. tako, da smo jih prebrali od uporabnika ( e smo bolj natan ni, so podatki izginili, ko smo resetirali okolje IDLE npr. z zagonom drugega ali istega programa). Ko smo torej dopolnjevali telefonski imenik z novimi vnosi, se je imenik ob kon anju programa izbrisal in dodani vnosi so izginili. To obi ajno predstavlja problem zaradi ve razlogov: • Ko se program neha izvajati, podatki izginejo. V primeru imenika se ta torej ob vsakem ponovnem zagonu programa resetira na za etno, npr. prazno, vsebino. • Nimamo varnostnih kopij podatkov. e se npr. telefon, ki uporablja naö imenik, ugasne, moramo celotno vsebino imenika ustvariti od za etka. • Podatkov, ki smo jih ustvarili v enem programu, ne moremo oziroma teûko uporabljamo v drugih programih. Na sre o lahko podatke v svojih programih iz delovnega pomnilnika oziroma iz vsebine spremenljivk kadarkoli shranimo v t.i. trajni pomnilnik oziroma po doma e na disk. S tem omogo imo njihovo trajno hrambo, kar pomeni, da lahko do teh podatkov pridemo tudi po zaklju ku izvajanja programa, na disku podatki ostanejo tudi po izklopu ra unalnika, do njih pa lahko pridemo tudi iz drugih programov. Kako pa podatke shranimo na disk? Podobno, kot smo do zdaj na disk shranjevali naöe programe v obliki datotek s kon nico py, lahko v (druge) datoteke shranjujemo tudi podatke, s katerimi delamo. Tako shranjene datoteke lahko kasneje v svojih programih preberemo in s tem obno-vimo stanje svojega delovnega pomnilnika oziroma dolo imo vrednosti spremenljivk na podlagi vsebine datoteke. Dodatna prednost branja podatkov iz datotek je to, da se nam zdaj ni treba ve zanaöati na vnos podatkov s strani uporabnika, ampak lahko podatke, ki jih ûelimo obdelati, v svoje programe preberemo kar iz datotek. 139 140 Poglavje 13 Delo z datotekami e bi npr. ûeleli pregledati podatke o povpre nih mese nih pla ah v Sloveniji, najpogostejöih imenih ali inflaciji, bi te lahko prenesli iz spletne strani Statisti nega urada republike Slovenije (SURS, https://www.stat.si/statweb), jih v svojem programu prebrali in ustrezno obdelali. V tem poglavju si bomo pogledali kako podatke iz datoteke prebrati in kako v datoteko podatke zapisati. 13.2 Kaj je datoteka? Datoteke predstavljajo osnovni na in zapisovanja podatkov na disku (ali na kakönem drugem mediju). Datoteke predstavljajo trajno hrambo podatkov, omogo ajo prenaöanje podatkov med razli nimi uporabniki in lahko vsebujejo razli ne tipe podatkov od teksta, besedil, slik in filmov, do datotek, ki jih operacijskih sistem uporablja za svoje delovanje. Ponavadi tip datoteke ozna uje njena kon nica. Programi v Pythonu na primer uporabljajo kon nico py, slike pa kon nice kot so jpg, png ali gif. V grobem lahko datoteke lo imo v dve skupini, in sicer na tekstovne datoteke in binarne datoteke. V obeh primerih vsebino datoteke predstavlja zaporedje ötevil, ki si jih lahko v primeru tekstovnih datotek interpretiramo kot zaporedje znakov oziroma nek (neoblikovan) tekst. Primer tekstovne datoteke je recimo datoteka s kon nico py ali datoteka s kon nico txt. Za tekstovne datoteke velja, da jih lahko odpremo z beleûnico ali beleûnici podobnimi orodji (npr. Notepad++) in je njihova vsebina bolj ali manj berljiva. Kaj pa binarne datoteke? V skupino binarnih datotek uvrö amo vse ostalo, npr. slike, filme in oblikovana besedila. e poskusite z beleûnico odpreti datoteko s kon nico docx (besedilo oblikovano z orodjem Microsoft Word), boste hitro videli, da to ni tekstovna datoteka. Tako kot pri tekstovnih datotekah je zapis s ötevili uporabljen tudi pri binarnih datotekah. Razli na je le interpretacija teh ötevil. Medtem, ko lahko vsebino tekstovnih datotek orodja, kot je beleûnica, enostavno dekodirajo kot zaporedje znakov, potrebujemo za binarne datoteke druga orodja oziroma programe, ki znajo njihovo vsebino dekodirati, glede na tip zapisa. Ena orodja pretvarjajo ötevilke v slike, druga v filme, tretja v oblikovano besedilo. Katero orodje bo posamezno datoteko ob dvojnem kliku nanju odprlo, dolo a njena kon nica. e se kon nica datoteke ne ujema z njeno vsebino, bo orodje vrnilo napako ali pa v najboljöem primeru prikazalo nekaj nenavadnega, podobno kot e datoteko s kon nico docx odpremo z beleûnico. 13.3 Tekstovne datoteke Tekstovne datoteke torej vsebujejo neoblikovan tekst, ki lahko predstavlja na primer neko besedilo, program v Pythonu ali pa podatke, ki smo jih zgenerirali tekom delovanja naöega programa. V okviru spoznavanja osnov programiranja se bomo neposredno ukvarjali zgolj s tekstovnimi datotekami. 13.4 Odpiranje datoteke 141 13.4 Odpiranje datoteke e ûelimo neko datoteko v naöem programu prebrati ali vanjo pisati, jo moramo najprej odpreti. To lahko naredimo z vgrajeno funkcijo open, ki ji bomo za za etek podali zgolj ime datoteke, do katere ûelimo dostopati. Naredimo tekstovno datoteko stevila.txt s slede o vsebino1: 1 4 2 5.6 3 2 4 100 5 15 Datoteko shranimo v mapo kamor pa najpogosteje shranjujemo svoje datoteke. Zdaj jo poskusimo odpreti v Pythonu: >>> open(’stevila .txt ’) FileNotFoundError : [ Errno 2] No such file or directory : ’stevila .txt ’ Python pravi, da datoteke ne najde, eprav smo jo pravkar ustvarili. Problem je v tem, da Python datoteko iö e v svoji trenutni delovni mapi, ki o itno ni enaka tisti, kamor smo mi shranili svojo datoteko2. Problem bi lahko reöili na dva na ina. Prvi na in je, da Pythonu podamo absolutno pot do lokacije datoteke. Pythonu smo do datoteke v gornjem primeru podali t.i. relativno pot, kar pomeni, da bo datoteko iskal relativno glede na trenutno delovno mapo. e je njegova delovna mapa npr. C:\Windows\system32, pri akuje, da se datoteka nahaja tu. Lahko pa Pythonu podamo celotno oziroma absolutno pot. e smo datoteko npr. shranili v mapo C:\Programiranje, jo zdaj lahko odpremo takole: >>> open(’C:\\ Programiranje \\ stevila .txt ’) <_io. TextIOWrapper name =’C:\\ Programiranje \\ stevila .txt ’ mode =’r’ encoding =’cp65001 ’> Tokrat smo datoteko le naöli. Mimogrede, zakaj je bilo pri podajanju poti potrebno podati dve poöevnici (\). Kot smo videli v prejönjem poglavju, poöevnico Python obravnava kot za etek posebnega znaka. Z njo lahko npr. zapiöemo tabulator (\t) ali pa znak za novo vrstico (\n). e bi radi zapisali poöevnico pa v niz enostavno vpiöemo dve poöevnici (\\). Drugi na in, ki je nekoliko enostavnejöi in bolj pogost, je, da spremenimo svojo delovno mapo in potem do datoteke podamo relativno pot. To ponavadi pomeni, 1Datoteko naredimo tako, da odpremo beleûnico, vpiöemo vsebino datoteke in datoteko shranimo. 2Do trenutne delovne mape lahko pridemo preko funkcije getcwd iz vgrajenega modula os. 142 Poglavje 13 Delo z datotekami da podamo samo ime datoteke (kot v primeru, ko je Python javil napako). Kako pa spremenimo trenutno delovno mapo? Na eloma nam za to ni potrebno posebej skrbeti, saj bo IDLE ob zagonu programa Pythonovo delovno mapo avtomatsko zamenjal za tisto, v kateri se program nahaja. e bomo datoteko odprli iz programa, ki se bo nahajal v isti mapi kot datoteka, lahko torej podamo zgolj njeno ime. e torej naredimo program branje.py in ga shranimo v mapo, v katero smo prej shranili stevila.txt, lahko iz njega datoteko odpremo takole: open(’stevila .txt ’) 13.5 Branje datoteke Datoteko smo torej uspeöno odprli, zdaj pa jo bomo poskusili prebrati. Dostopanje do datoteke lahko izvedemo preko objekta, ki ga vrne funkcija read. e ho emo datoteko prebrati, moramo temu objektu prirediti neko ime, preko katerega ga bomo lahko kasneje öe poklicali. Takole: f = open(’stevila .txt ’) Do metod za delo z datoteko lahko zdaj dostopamo preko imena f. Prvi na in za branje datoteke je uporaba metode read, ki prebere datoteko od za etka do konca in vrne niz z njeno vsebino. V naöem primeru bo rezultat slede : >>> f. read () ’4\ n5 .6\ n2\n100 \n15 ’ To je torej zapisano v datoteki. Niz vsebuje ötevila, ki so med seboj lo ena z znaki za novo vrstico \n. e bi ûeleli imeti lepöi izpis, bi stvar izpisali s funkcijo print, ki znake za novo vrstico izpiöe kot nove vrstice. Poskusimo: >>> print(f. read ()) Zakaj se tokrat ni ni izpisalo? Objekt za dostopanje do datoteke za imenom f beleûi, do kje je bila datoteka ûe prebrana. Datoteko lahko namre beremo tudi po kosih (npr. vrstico po vrstico), pri emer no emo, da bi vedno brali npr. samo prvo vrstico, ampak bi radi slej ko prej priöli do konca datoteke. Ker smo naöo datoteko ûe prebrali, jo moramo za ponovno branje ponovno odpreti. Ker je trenutna delovna mapa zdaj ûe enaka mapi, kjer se nahaja naöa datoteka, lahko odpiranje tokrat naredimo kar iz ukazne vrstice: >>> f = open(’stevila .txt ’) >>> print(f. read ()) 45.6 2100 15 13.5 Branje datoteke 143 ée prej smo omenili, da lahko datoteko beremo tudi po vrsticah. Lahko npr. uporabimo metodo readline: >>> f. readline () ’’ Tole spet ne deluje, ker se nahajamo na koncu datoteke. Ponovno jo odprimo in preberimo prvo vrstico: >>> f = open(’stevila .txt ’) >>> f. readline () ’4\n’ Branje po vrsticah lahko izvedemo tudi z zanko for. Takole: >>> for vrstica in f: print( vrstica ) 5.6 2 100 15 Prva vrstica seveda manjka, ker smo jo prej prebrali ûe z metodo readline. Zakaj se v izpisu pojavlja prazna vrstica? En znak za prazno vrstico je prispevala funkcija print, drugega pa smo dobili iz prebrane datoteke. Znake za prazno vrstico lahko iz niza, ki predstavlja trenutno prebrano vrstico, odstranimo z metodo strip, ki odstrani prazen prostor pred za etkom in po koncu prave vsebine niza: >>> f = open(’stevila .txt ’) >>> for vrstica in f: print( vrstica . strip ()) 45.6 2100 15 Zadnja metoda, preko katere lahko preberemo datoteko, je metoda readline, ki podobno kot metoda read prebere celo datoteko, jo razbije po vrsticah in te zapiöe v seznam. >>> f = open(’stevila .txt ’) >>> f. readlines () [’4\n’, ’5.6\ n’, ’2\n’, ’100\ n’, ’15 ’] 144 Poglavje 13 Delo z datotekami Datoteko lahko torej preberemo na ve razli nih na inov, pri emer je vsem skupno to, da vsebino datoteke preberejo kot niz. e bi ûeleli vsebino obravnavati kot kaj drugega, jo bomo morali prej ustrezno obdelati (podobno kot moramo npr. v ustrezen podatkovni tip pretvoriti rezultat funkcije input). Poskusimo branje na zgledu. Zgled 56. Napiöi funkcijo povprecje, ki kot argument prejme niz z imenom datoteke in izra una povpre je ötevil, ki so shranjena v datoteki. Predpostavljaö lahko, da je vsako ötevilo v datoteki zapisano v svoji vrstici. V primeru, da je datoteka prazna, naj funkcija vrne ötevilo 0. Reöitev 56. Funkcijo lahko napiöemo s sprehodom ez vrstice, pri emer vsebino vsake vrstice priötevamo skupni vsoti, ki jo na koncu delimo s ötevilom vrstic. Vsebino vrstice moramo prej seveda o istiti (metoda strip) in pretvoriti v ötevilo (funkcija float). 1 def povprecje ( ime_datoteke ): 2 f = open( ime_datoteke ) 3 s = 0 4 n = 0 5 for vrstica in f: 6 stevilo = float( vrstica . strip ()) 7 s += stevilo 8 n += 1 9 if n > 0: 10 return s/n 11 else: 12 return 0 — Kaj pa e bi imeli v eni vrstici ve ötevil? V tem primeru je potrebno vrstico prej öe razbiti na posamezna ötevila. To lahko naredimo z uporabo metode split, ki jo pokli emo nad posamezno vrstico in ji kot argument podamo ustrezno lo ilo. Zgled 57. Napiöi funkcijo povprecje, ki kot argument prejme niz z imenom datoteke in izra una povpre je ötevil, ki so shranjena v datoteki. Predpostavljaö lahko, da je v posamezni vrstici lahko zapisano ve ötevil, ki so med seboj lo ena z vejicami. Reöitev 57. Tokrat bomo posamezno vrstico z metodo split razbili na seznam, ez katerega se bomo sprehodili z ugnezdeno zanko for. äe vedno pa moramo posamezen element tega seznama pred priötevanjem skupni vsoti pretvoriti v ötevilo. 13.5 Branje datoteke 145 1 def povprecje ( ime_datoteke ): 2 f = open( ime_datoteke ) 3 s = 0 4 n = 0 5 for vrstica in f: 6 vrstica = vrstica . strip () 7 for element in vrstica . split (","): 8 stevilo = float( element ) 9 s += stevilo 10 n += 1 11 if n > 0: 12 return s/n 13 else: 14 return 0 — Poskusimo reöiti öe teûave, ki smo jih imeli s telefonskim imenikom (pa tega nismo vedeli). Zgled 58. Napiöi funkcijo preberi_imenik, ki kot argument prejme niz z imenom datoteke, ki vsebuje varnostno kopijo telefonskega imenika. Funkcija naj datoteko prebere in vrne slovar, v katerem so klju i imena oseb, vrednosti pa njihove telefonske ötevilke zapisane kot nizi. Predpostavljaö lahko, da so podatki v datoteki zapisani tako, da posamezna vrstica vsebuje ime osebe, ki je od telefonske ötevilke osebe lo eno z vejico. Pri tem se v eni vrstici nahajajo zgolj podatki ene osebe. Reöitev 58. Podobno kot v prejönjem zgledu bomo s posamezno vrstico naredili slede e: • odstranili bomo znak za novo vrstico z metodo strip; • vrstico bomo razbili na seznam nizov glede na uporabljeno lo ilo (’,’) z metodo split; • seznam nizov bomo razpakirali in uporabili kot veleva navodilo naloge. 1 def preberi_imenik ( ime_datoteke ): 2 f = open( ime_datoteke ) 3 imenik = {} # prazen slovar 4 for vrstica in f: 5 vrstica = vrstica . strip () # odstrani ’\n’ 6 seznam = vrstica . split (’,’) 146 Poglavje 13 Delo z datotekami 7 ime = seznam [0] 8 stevilka = seznam [1] 9 imenik [ime] = stevilka # dodajanje vnosa 10 return imenik Funkcijo bi lahko öe nekoliko izboljöali. Podobno, kot lahko razpakiramo terke, lahko razpakiramo tudi sezname. Torej lahko spremenljivkama ime in stevilka vrednosti priredimo kar v eni vrstici: ime , stevilka = vrstica . split (’,’) e ûelimo, da funkcija deluje, tudi v primeru, ko datoteka vsebuje prazne vrstice, moramo pred obdelavo posamezne vrstice preveriti, e ta slu ajno ni prazna. Po odstranjevanju znaka za novo vrstico torej preverimo neenakost s praznim nizom: if vrstica != ’’: To lahko krajöe zapiöemo kar takole: if vrstica : Zapiöimo celotno reöitev: 1 def preberi_imenik ( ime_datoteke ): 2 f = open( ime_datoteke ) 3 imenik = {} # prazen slovar 4 for vrstica in f: 5 vrstica = vrstica . strip () # odstrani ’\n’ 6 if vrstica : # vrstica ni prazna? 7 ime , stevilka = vrstica . split (’,’) # razpakiraj 8 imenik [ime] = stevilka # dodajanje vnosa 9 return imenik — 13.6 Pisanje v datoteko Pisanje v datoteko lahko izvedemo z metodo write, ki ji kot argument podamo niz, ki ga ûelimo v datoteko zapisati. Poskusimo: >>> f = open(’stevila .txt ’) >>> f. write (6) TypeError : write () argument must be str , not int Ravnokar smo rekli, da moramo metodi write podati niz, ki ga ûelimo zapisati. e ho emo torej v datoteko zapisati ötevilo, ga moramo prej pretvoriti v niz s funkcijo str. Poskusimo öe enkrat z nizom: 13.6 Pisanje v datoteko 147 >>> f = open(’stevila .txt ’) >>> f. write (’6’) io. UnsupportedOperation : not writable Tokrat smo dobili drugo napako. Vidimo, da pisanje ni mogo e, ker smo datoteko odprli za branje. Ve informacij o odprti datoteki, lahko izvemo preko spremenljivke, v katero smo datoteko odprli: >>> f <_io. TextIOWrapper name=’stevila .txt ’ mode =’r’ encoding =’cp65001 ’> Iz izpisa vidimo, da smo odprli datoteko z imenom ’stevila.txt’ (kar ûe vemo) in da je mode oziroma na in odpiranja datoteke enak vrednosti ’r’, ki se nanaöa na branje oziroma read. Na in odpiranja datoteke lahko funkciji open nastavimo z izbirnim argumentom mode, ki je privzeto enak vrednosti ’r’. e argumenta ne nastavimo, bomo torej datoteko odprli za branje. Pogosto uporabljena na ina odpiranja datoteke sta öe ’w’ oziroma pisanje (angl. write) in ’a’ oziroma dodajanje (angl. append). Pisanju bi lahko, e bi bili nekoliko bolj natan ni, rekli tudi prepisovanje, saj bomo s takim na inom odpiranja obstoje o datoteko prepisali. Dodajanju bi v podobnem kontekstu lahko rekli dopisovanje, saj bomo obstoje i datoteki vsebino dodajali (na koncu obstoje e vsebine). Pri obeh na inih pa zdaj napake, e datoteka s podanim imenom ne bo obstajala, ne bomo dobili, ampak bo Python to datoteko ustvaril. Poskusimo zdaj v datoteko ’stevila.txt’ zapisati par ötevil. Najprej jo odpremo za pisanje: >>> f = open(’stevila .txt ’, mode = ’w’) Zdaj pa piöimo: >>> f. write (’1’) 1>>> f.write(’2’) 1>>> f.write(’3’) 1>>> f.write(’45’) 2 Vsaki , ko pokli emo metodo ’write’ ta vrne ötevilo znakov, ki smo jih zapisovali. Ko zdaj datoteko stevila.txt odpremo z beleûnico (ali beleûnici podobnim orodjem), vidimo, da je ta prazna. Ko smo datoteko odprli za prepisovanje, je obstoje a vsebina izginila. Potem smo vanjo nekaj pisali. Zakaj je datoteka kljub pisanju öe vedno prazna? Pravilo lepega dela z datotekami je, da jih po vsakem odpiranju tudi zapremo za kar lahko uporabimo metodo close. Pri branju to ni nujno potrebno, saj se bo datoteka zaprla avtomatsko, ko bo iz naöega imenskega prostora izginila spremenljivka, preko katere smo datoteko najprej odprli in potem 148 Poglavje 13 Delo z datotekami brali (najkasneje, ko bomo resetirali okolje IDLE). Pri pisanju pa se lahko zgodi, da bo vsebina datoteke brez eksplicitnega zapiranja datoteke ostala prazna. Ker je pisanje v datoteko po asna operacija, Python, kot smo ûe navajeni, to v dolo eni meri optimizira oziroma pohitri. Ko od njega zahtevamo pisanje v datoteko, namre raje piöe v medpomnilnik (angl. buffer), katerega vsebino bo v dejansko datoteko Python zapisal bodisi takrat, ko se bo nabralo malo ve podatkov, da se mu bo spla alo to zapisati v datoteko, ali pa takrat, ko bomo datoteko zaprli, saj s tem povemo, da vanjo ne bomo ve pisali. e ûelimo, da se naöa ötevila v datoteko zagotovo zapiöejo, jo moramo torej zapreti: >>> f. close () Ko zdaj odpremo datoteko, vidimo, da se je prejönja vsebina datoteke prepisala – seveda, saj smo datoteko odprli za prepisovanje. V njej vidimo slede o vsebino: 1 12345 To ni tisto kar smo hoteli? Mogo e ne, je pa to tisto, kar smo zahtevali. Metoda write namre v datoteko zapiöe to no tisto, kar od nje zahtevamo. e bi ûeleli ötevila zapisati vsako v svojo vrstico, bi morali zraven vsakega ötevila zapisati tudi znak za novo vrstico (\n). Takole: >>> f. write (’1’) ValueError : I/O operation on closed file. Ker smo datoteko zaprli, zdaj vanjo ne moremo ve pisati. Pred ponovnim pisanjem, jo moramo torej spet odpreti: >>> f = open(’stevila .txt ’, mode =’w’) >>> f. write (’1\n’) 2>>> f.write(’2\n’) 2>>> f.write(’3\n’) 2>>> f.write(’45\n’) 3 Datoteko moramo na koncu seveda spet zapreti >>> f. close () Zdaj je njena vsebina slede a: 1 1 2 2 3 3 4 45 5 13.6 Pisanje v datoteko 149 Datoteka se kon a s prazno vrstico, v katero smo sko ili za nizom 45. Dodajmo v datoteko öe par ötevil. e ûelimo v datoteko novo vsebino dodajati k obstoje i vsebini, jo moramo odpreti za dopisovanje (mode=’a’). >>> f = open(’stevila .txt ’, mode =’a’) >>> f. write (’55\n’) 3>>> f.write(’133\n’) 4>>> f.write(’-6.98\n’) 6>>> f.close() Ko datoteko ponovno odpremo z beleûnico, vidimo, da smo v njej dobili tri nove vrstice: 1 1 2 2 3 3 4 45 5 55 6 133 7 -6.98 8 Povadimo pisanje öe na zgledu. Zgled 59. Napiöi funkcijo nakljucna, ki kot argument prejme niz z imenom datoteke in celo ötevilo, ki podaja ötevilo naklju nih ötevil, ki naj jih funkcija zapiöe v datoteko s podanim imenom. Funkcija naj v vsako vrstico v datoteki zapiöe najve 10 naklju nih celih ötevil iz intervala od 1 do 100. Reöitev 59. Naklju na ötevila bomo generirali z uporabo funkcije randint modula random. V datoteko bomo pisali po eno ötevilo naenkrat. Po vsakem pisanju lahko zapiöemo tudi presledek, na vsakih 10 pisanj pa bomo zapisali znak za novo vrstico. 1 # uvaûanje modulov vedno delamo izven funkcij 2 from random import randint 34 def nakljucna(ime_datoteke, n): 5 f = open( ime_datoteke , mode =’w’) # odpri za prepisovanje 6 for i in range (1,n+1): # zanko ponovimo n-krat 7 st = randint (1 ,100) # naklju no ötevilo od 1 do 100 8 niz = str(st) # ötevilo moramo pretvoriti v niz 9 f. write (niz) # niz zapiöemo v datoteko 10 if i % 10 == 0: # ali je as za novo vrstico 150 Poglavje 13 Delo z datotekami 11 f. write (’\n’) 12 else: 13 f. write (’ ’) 14 f. close () # datoteko na koncu zapremo — Metoda write torej v datoteko zapiöe to no tisto, kar ji podamo, vedno pa ji moramo podati to no en podatek tipa str. e bi ûeleli naenkrat zapisati ve podatkov, jih moramo torej pred tem sami zdruûiti. V dolo enih primerih bi bilo veliko bolj priro no, e bi lahko v datoteko pisali na enak na in, kot smo stvari izpisovali na zaslon, tj. s funkcijo print. Ta sprejema vrednosti poljubnega tipa, poleg tega pa ji lahko podamo ve vrednosti, ki jih bo med sabo zdruûila (privzeto z lo ilom ’ ’). Izkaûe se, da lahko za pisanje v datoteke uporabimo tudi funkcijo print. Pri tem moramo opcijskemu argumentu z imenom file prirediti spremenljivko, v katero smo datoteko odprli3 Poskusimo na prejönjem zgledu. Zgled 60. Napiöi funkcijo nakljucna, ki kot argument prejme niz z imenom datoteke in celo ötevilo, ki podaja ötevilo naklju nih ötevil, ki naj jih funkcija zapiöe v datoteko s podanim imenom. Funkcija naj v vsako vrstico v datoteki zapiöe najve 10 naklju nih celih ötevil iz intervala od 1 do 100. Za pisanje v datoteko uporabi funkcijo print. Reöitev 60. Reöitev je zelo podobna kot prej, le da nam tokrat ötevil ni potrebno pretvarjati v nize, funkcija print pa tudi sama poskrbi za pisanje presledkov in novih vrstic (le pravilno jo moramo poklicati). 1 # uvaûanje vedno delamo izven funkcij 2 from random import randint 34 def nakljucna(ime_datoteke, n): 5 f = open( ime_datoteke , mode =’w’) # odpri za prepisovanje 6 for i in range (1,n+1): # zanko ponovimo n-krat 7 st = randint (1 ,100) # naklju no stevilo od 1 do 100 8 if i % 10 == 0: # ali je as za novo vrstico 9 print(st , file=f) # nova vrstica 10 else: 11 print(st , end=’ ’, file=f) # presledek 12 f. close () — 3Opcijski argument file funkcije print je privzeto postavljen na vrednost sys.stdout, kar pomeni, da piöemo na standardni izhod – v naöem primeru je to konzola okolja IDLE. 13.7 Kodiranje znakov 151 Za konec dopolnimo öe zgled s telefonskim imenikom. Zgled 61. Napiöi funkcijo shrani_imenik, ki kot argument prejme niz z imenom datoteke, ki naj vsebuje varnostno kopijo telefonskega imenika in slovar, v katerem so klju i imena oseb, vrednosti pa njihove telefonske ötevilke. Funkcija zapiöe imenik v datoteko s podanim imenom, in sicer tako, da vsaka vrstica vsebuje podatke o eni osebi, pri emer je ime od telefonske ötevilke lo eno z vejico. Reöitev 61. V funkciji se bomo sprehodili ez cel imenik in v vsako vrstico zapisali ime in telefonsko ötevilko. Ker sta ime in telefonska ötevilka v slovarju shranjena kot niza, pretvorba v niz pred zapisovanjem ni potrebna. 1 def shrani_imenik ( ime_datoteke , imenik ): 2 f = open( ime_datoteke , mode =’w’) # prepisovanje 3 for ime , stevilka in imenik . items (): 4 f. write (ime) 5 f. write (’,’) 6 f. write ( stevilka ) 7 f. write (’\n’) 8 f. close () ätiri nize (ime, ’,’, stevilka in ’\ n’ bi lahko zdruûili skupaj in metodo write uporabili samo enkrat. 1 def shrani_imenik ( ime_datoteke , imenik ): 2 f = open( ime_datoteke , mode =’w’) # prepisovanje 3 for ime , stevilka in imenik . items (): 4 f. write (ime+’,’+ stevilka +’\n’) 5 f. close () Za pisanje pa bi lahko uporabili tudi funkcijo print. Tej moramo spremeniti privzeto lo ilo (presledek) v zahtevano lo ilo (vejica). Takole: 1 def shrani_imenik ( ime_datoteke , imenik ): 2 f = open( ime_datoteke , mode =’w’) # prepisovanje 3 for ime , stevilka in imenik . items (): 4 print(ime , stevilka , sep=’,’, file=f) 5 f. close () — 13.7 Kodiranje znakov ée na za etku poglavja smo omenili, da vsebino datoteke predstavlja zaporedje ötevil, ki jih lahko v primeru tekstovnih datotek pretvorimo v neoblikovan tekst 152 Poglavje 13 Delo z datotekami oziroma neko zaporedje znakov. Vsak znak je torej shranjen kot neko ötevilo. Kodiranje dolo a preslikavo, ki posamezno ötevilo oziroma kodo preslika v dolo en znak. Do kode znaka lahko pridemo z uporabo vgrajene funkcije ord, ki ji kot argument podamo en znak: >>> ord(’a’) 97 >>> ord(’A’) 65 Preslikavo lahko naredimo tudi v drugo smer (iz kode v znak), in sicer z uporabo funkcije chr, ki ji kot argument podamo kodo znaka: >>> chr (97) ’a’ >>> chr (65) ’A’ Koda posameznega znaka je odvisna od uporabljenega kodiranja, pri emer obstaja kar nekaj razli nih na inov kodiranja. Izkaûe se, da so osnovni znaki ne glede na uporabljeno kodiranje zakodirani na enak na in (enaka koda predstavlja enak znak). Pri kodiranju teh se namre uporablja 7-bitni standard ASCII (angl. American Standard Code for Information Interchange). Posamezen znak je torej predstavljen s 7 biti, kar pomeni, da lahko zakodiramo 27 = 128 razli nih znakov. Pri tem je 32 kod rezerviranih za posebne znake (med te sodi npr. tudi znak za novo vrstico). Torej nam ostane 96 kod, s katerimi moramo predstaviti male rke, velike rke, ötevilke in razli na lo ila ter simbole. Izkaûe se, da teh kod zelo hitro zmanjka in zato sluûijo zgolj predstavitvi osnovnih znakov in rk angleöke abecede. Kaj pa naöi öumniki, nemöki preglasi in kitajske pismenke? Za predstavitev teh moramo uporabiti razöiritve kodiranja ASCII. Omenimo dve razöiritvi, ki sta v pogosti uporabi v danaönjem asu. Kodne tabele (angl. code page) kodiranje ASCII razöirjajo z dodatnim bitom, kar pomeni, da lahko z njihovo uporabo predstavimo dodatnih 128 znakov. To je sicer dovolj za znake , û in ö, kaj pa za kitajske pismenke? Dodatnih kod seveda ni dovolj, da bi predstavili öe te, poleg njih pa mogo e öe rke iz cirilice, korejski hangul in öe kaj drugega. Iz tega razloga se v razli nih delih sveta uporabljajo razli ne kodne tabele. Pri nas je npr. v uporabi centralno-evropska kodna tabela cp1250, s katero lahko predstavimo tudi znake , û in ö. Znak je v tej kodni tabeli dolo en s kodo 232. V Nem iji uporabljajo zahodno-evropsko kodno tabelo cp1252, s katero sicer ne moremo predstaviti öumnikov, lahko pa predstavimo preglase. V tej kodni tabeli koda 232 predstavlja znak è, kar ste mogo e ûe kdaj opazili, npr. pri predvajanju datoteke s podnapisi, ki jih program za predvajanje filmov poskuöa dekodirati z napa nim kodiranjem. 13.7 Kodiranje znakov 153 Kljub temu, da se kodne tabele öe vedno uporabljajo, je v danaönjem asu v ve inski rabi standard unicode, katerega pomemben predstavnik je kodiranje UTF8. Pri tem je posamezen znak zapisan z minimalno 8 biti. V primeru, da se v naöem besedilu pojavijo znaki, ki ne nastopajo v osnovnem ASCII kodiranju (npr. ), se zapis posameznega znaka razöiri (V konkretnem primeru na 16-bitni zapis). Prednost tega na ina kodiranja je, da lahko datoteke iz razli nih delov sveta, ki vsebujejo razli ne lokalne pisave, dekodiramo z enim samim kodiranjem in uporabniku ni potrebno ro no preklapljati med razli nimi na ini kodiranja, kot v primeru kodnih tabel. Zakaj se moramo s kodiranjem znakov ukvarjati v okviru osnov programiranja? Pri branju in pisanju datotek je zaûeleno, da izrecno podamo tudi kodiranje, ki ga ûelimo uporabiti. S tem se namre izognemo teûavam z napa no interpretacijo znakov. e je nekdo zapisal datoteko z uporabo kodiranja UTF-8, mi pa jo poskusimo odpreti s kodiranjem cp1250, bo priölo do napake ali pa bodo znaki dekodirani narobe. e kodiranja ne podamo (kot ga nismo podali v primerih zgoraj), bo uporabljeno privzeto kodiranje naöega operacijskega sistema. Poglejmo si öe enkrat kaj se skriva za spremenljivko, preko katere odpremo datoteko: >>> f = open(’stevila .txt ’) >>> f <_io. TextIOWrapper name=’stevila .txt ’ mode =’r’ encoding =’cp65001 ’> Kodiranje (angl. encoding) je v konkretnem primeru nastavljeno na ’cp65001’, ki predstavlja nekaköen ekvivalent kodiranja UTF-8 v operacijskem sistemu Windows. Operacijski sistem Windows sicer pogosto uporablja kodiranje cp1250, Linux pa UTF-8. Zmeönjava je torej popolna. To lahko povzro i nemalo teûav, zato je pri delu z datotekami pomembno, da vedno podamo na in kodiranja. Dogovorimo se, da bomo od zdaj naprej pri delu z datotekami vedno podali na in kodiranja, ki naj bo kar UTF-8. Kodiranje, ki ga ûelimo uporabiti pri pisanju ali branju datoteke, navedemo pri odpiranju datoteke s funkcijo open preko izbirnega argumenta encoding. Zapiöimo datoteko z uporabo kodiranja UTF-84: >>> f = open(" posebni_znaki .txt", mode =’w’, encoding =" utf8 ") >>> f. write (" ûö") 3>>> f.close() Zdaj datoteko preberimo z uporabo kodiranja cp1250: >>> f = open(" posebni_znaki .txt", encoding =" cp1250 ") >>> f. read () ’Ä ’ 4Argument encoding bomo nastavili na vrednost ’utf8’, ’utf-8’, ’UTF8’ ali ’UTF-8’. 154 Poglavje 13 Delo z datotekami Rezultat je seveda napa en. Poskusimo öe s pravilnim kodiranjem: >>> f = open(" posebni_znaki .txt", encoding ="utf8 ") >>> f. read () ’ ûö’ Izbira ustreznega kodiranja pri delu z datotekami je torej pomembna. Dopolnimo naöi funkciji za branje telefonskega imenika iz datoteke in njegovo zapisovanje v datoteko, saj je velika verjetnost, da bodo v imenih naöih prijateljev nastopali tudi öumniki. Zgled 62. Napiöi funkciji za branje in shranjevanje telefonskega imenika v in iz datoteke, pri emer je imenik podan kot slovar, v katerem klju i predstavljajo imena oseb, vrednosti pa njihove telefonske ötevilke. Datoteka naj bo zakodirana z uporabo kodiranja UTF-8. Reöitev 62. Reöitev bo prakti no enaka kot v prej, le da bomo pri odpiranju datoteke izbirni argument encoding postavili na vrednost ’utf8’. 1 def shrani_imenik ( ime_datoteke , imenik ): 2 # pri odpiranju datoteke podamo öe kodiranje 3 f = open( ime_datoteke , mode =’w’, encoding =’utf8 ’) 4 for ime , stevilka in imenik . items (): 5 print(ime , stevilka , sep=’,’, file=f) 6 f. close () 78 def preberi_imenik(ime_datoteke): 9 # pri odpiranju datoteke podamo öe kodiranje 10 f = open( ime_datoteke , encoding =’utf8 ’) 11 imenik = {} # prazen slovar 12 for vrstica in f: 13 vrstica = vrstica . strip () # odstrani ’\n’ 14 if vrstica : # vrstica ni prazna? 15 ime , stevilka = vrstica . split (’,’) # razpakiraj 16 imenik [ime] = stevilka # dodajanje vnosa 17 return imenik — 13.8 Datoteke CSV Zapis CSV (angl. comma separated values) predstavlja na in zapisovanja podatkov v tekstovne datoteke, pri emer vsaka vrstica vsebuje svoj zapis (npr. osebo), podatki znotraj posamezne vrstice (npr. ime osebe in telefonska ötevilka) pa so med seboj 13.8 Datoteke CSV 155 lo eni z vejicami. Primer datoteke CSV smo v tem poglavju ûe sre ali, in sicer, ko smo v datoteko zapisovali telefonski imenik. Kot lo ilo je lahko uporabljeno tudi kaj drugega kot vejica, npr. podpi je ali tabulator. Pomembno je, da je za lo ilo uporabljen znak, ki se sicer med podatki nikoli ne pojavi. e bi npr. datoteka vsebovala decimalna ötevila zapisana z decimalno piko, bi bila pika zelo slaba izbira za lo ilo med podatki znotraj vrstice. V asih uporabljeno lo ilo nakazuje ûe kon nica datoteke. Kon nica csv npr. pogosto nakazuje na to, da je za lo ilo uporabljena vejica (angl. comma) ( eprav to ni nujno), tsv pa tabulator. V asih so datoteke CSV shranjene s kon nico txt. V tem primeru je najlaûje, e datoteko odpremo z beleûnico ali beleûnici podobnim orodjem in takoj vidimo, kateri znak je uporabljen za lo ilo. Prednost uporabe datotek CSV je v tem, da jih lahko zelo enostavno raz lenimo (angl. parse) oziroma z drugimi besedami, da lahko podatke iz njih zelo enostavno pridobimo in pretvorimo v obliko, ki bo najustreznejöa za nadaljnjo obdelavo (spomnimo se npr. na omreûja prijateljstev). Kot smo videli v primeru branja telefonskega imenika, smo posamezno vrstico, ki predstavlja en zapis (npr. osebo) zgolj razbili z metodo split. Tako smo priöli do seznama podatkov (npr. imena in telefonske ötevilke), ki pripadajo posameznemu zapisu (npr. osebi). Tudi zapisovanje podatkov datotek CSV je nadvse preprosto, saj moramo med podatke, ki jih zapisujemo zgolj vrniti izbrano lo ilo, zapise pa med seboj lo iti z novimi vrsticami. Na zapise lahko torej gledamo tudi kot na vrstice, na podatke znotraj zapisov pa kot na stolpce v tabeli. Datoteke CSV se v danaönjem asu uporabljajo zelo pogosto, saj zagotavljajo kompatibilnost zapisanih podatkov med razli nimi orodji. Vse kar moramo za branje teh podatkov in izvedbo nadaljnjih analiz poznati je lo ilo, ki lo i podatke znotraj zapisa (tega lahko dolo imo tako, da datoteko odpremo z beleûnico) in kodiranje, ki je bilo uporabljeno pri zapisovanju podatkov. V dolo enih primerih datoteka CSV vsebuje tudi glavo, ki podaja pomen posameznega stolpca v datoteki, v dolo enih primerih pa moramo vnaprej vedeti kaj posamezen stolpcev predstavlja. Datoteke CSV lahko enostavno uvozimo v razli na orodja za obdelavo podatkov, kot je npr. Excel, lahko pa obdelavo podatkov naredimo kar v jeziku Python. Za shranjevanje in branje datotek CSV obstaja kar nekaj knjiûnic (v zadnjem asu se za namen obdelave podatkov, ki zajema tudi branje in pisanje datotek CSV uporablja predvsem knjiûnica pandas, ki jo bomo öe sre ali), mi pa bomo branje malo kompleksnejöe datoteke CSV izvedli kar z osnovnimi konstrukti jezika Python, podobno kot smo to naredili ûe s telefonskim imenikom. Poglejmo si naslednji primer. Najprej bomo pridobili testne podatke, v obliki datoteke CSV. Zgled 63. Na spletni strani Statisti nega urada Republike Slovenije (https: // www. stat. si/ ) poiö i bazo podatkov SiStat, v njej pa podatke o povpre nih mese nih bruto in neto pla ah pri pravnih osebah javnega in zasebnega sektorja. 156 Poglavje 13 Delo z datotekami Te izvozi v obliki CSV, pri emer ûelimo imeti lo ene podatke za javni in zasebni sektor ter za bruto in neto pla e, zanimajo pa nas pla e za posamezen mesec. Reöitev 63. Na za etni strani poiö emo povezavo na podatkovno bazo SiStat (https: // pxweb. stat. si/ SiStat ). Sledimo povezavam: Demografsko in soci-alno podro je æ Trg dela æ Pla e in stroöki dela æ Povpre ne mese ne pla e æ Povpre ne mese ne bruto in neto pla e pri pravnih osebah javnega in zasebnega sektorja, Slovenija, mese no . Tu si izberemo mesece, ki jih ûelimo izvoziti, sektorja in bruto ter neto pla e. Pod Meritve si izberemo öe Pla e za mesec . Spodaj si izberemo eno izmed oblik CSV za izvoz in podatke izvozimo. Datoteko preimenujemo v place.csv. Primer tako izvoûene datoteke je na voljo na povezavi. — Zdaj bomo poskusili podatke uvoziti v Python in izra unali povpre je razpoloûljivih mese nih pla v javnem in v zasebnem sektorju. Zgled 64. Napiöi funkcijo uvozi_place, za uvoz podatkov o mese nih pla ah. Funkcija naj kot argument sprejme ime datoteke s podatki in slovar, ki kot klju vsebuje naziv sektorja, kot vrednost pa nov slovar. V tem slovarju naj bodo klju i nizi ’mesec’, ’bruto’ in ’neto’, za katerimi so seznami, ki predstavljajo mesece, bruto zneske in neto zneske. Seznami naj imajo na enakem indeksu podatek za isti mesec. Reöitev 64. Najprej moramo podrobneje pogledati kako je datoteka strukturirana. V ta namen jo odpremo z beleûnico ali podobnim orodjem (ne z orodjem Excel). e jo odpremo z orodjem Notepad++ , lahko enostavno preverimo kako je datoteka zakodirana. e dolo eni znaki niso berljivi, postanejo berljivi, ko kodiranje nastavimo na cp1250 ( Encoding æ Character Sets æ Central European æ Windows-1250 ). To kodiranje bomo torej uporabili pri odpiranju datoteke: f = open(" place .csv", encoding =" cp1250 ") Zdaj se lotimo vsebine datoteke. Takoj vidimo, da prve tri vrstice ne vsebujejo podatkov. Te bomo torej presko ili. Lahko kar tako, da jih preberemo: f. readline () f. readline () f. readline () Ostale vrstice bomo shranjevali v slovar seznamov, ki ga bomo lahko kasneje uporabili za analizo mese nih pla . Najprej si pripravimo prazen slovar: place = {} Zdaj se bomo z zanko for sprehodili ez preostanek datoteke. Posamezno vrstico bomo najprej oklestili znaka za novo vrstico, potem pa razbili glede na uporabljeno lo ilo. V beleûnici ali orodju Notepad++ vidimo, da je to tabulator (\ t). 13.8 Datoteke CSV 157 seznam = vrstica . strip (). split ("\t") Tako pridobljen seznam bo sestavljen iz ötirih podatkov: podatka o mesecu, sektorju, bruto pla i in neto pla i. Razpakirajmo ga: mesec = seznam [0] sektor = seznam [1] bruto = seznam [2] neto = seznam [3] älo bi tudi hitreje: mesec , sektor , bruto , neto = seznam Podatku o mesecu in sektorju lahko pred nadaljnjo obdelavo odstranimo dvojne navednice: mesec = mesec . replace (’"’,’’) sektor = sektor . replace (’"’,’’) Zneske bomo pretvorili v ötevila tipa float: bruto = float( bruto ) neto = float( neto ) Potem bomo preverili, e je sektor ûe med klju i slovarja pla . e ga öe ni, ga bomo dodali in nanj vezali za etni slovar za shranjevanje podatkov ’mesec’:[], ’neto’:[], ’bruto’:[]. Takole. if sektor not in place : # e sektorja öe ni place [ sektor ] = {’mesec ’:[] , ’neto ’:[] , ’bruto ’:[]} Zdaj v sezname, ki pripadajo trenutnemu sektorju dodamo trenutne podatke o mesecu, bruto in neto pla i: place [ sektor ][ ’mesec ’]. append ( mesec ) place [ sektor ][ ’neto ’]. append ( neto ) place [ sektor ][ ’bruto ’]. append ( bruto ) Celotno reöitev zapisano v obliki funkcije predstavlja spodnja koda: 1 def uvozi_place ( ime_datoteke ): 23 # datoteka je zakodirana s cp1250 4 f = open( ime_datoteke , encoding =’cp1250 ’) 56 # prve tri vrstice vrûemo pro 7 f. readline () 158 Poglavje 13 Delo z datotekami 8 f. readline () 9 f. readline () 10 11 # prazen slovar 12 place = {} 13 14 for vrstica in f: 15 # razbijmo vrstico na seznam s podatki 16 seznam = vrstica . strip (). split ("\t") 17 # razpakirajmo seznam 18 mesec , sektor , bruto , neto = seznam 19 20 # odstranimo dvojne navednice 21 mesec = mesec . replace (’"’,’’) 22 sektor = sektor . replace (’"’,’’) 23 24 # zneske pretvorimo v ötevila 25 bruto = float( bruto ) 26 neto = float( neto ) 27 28 if sektor not in place : # e sektorja öe ni 29 place [ sektor ] = {’mesec ’:[] , 30 ’neto ’:[] , 31 ’bruto ’:[]} 32 33 # dodamo podatke v ustrezen seznam 34 place [ sektor ][ ’mesec ’]. append ( mesec ) 35 place [ sektor ][ ’neto ’]. append ( neto ) 36 place [ sektor ][ ’bruto ’]. append ( bruto ) 37 38 # podatki so zdaj pripravljeni 39 return place — Podatke lahko zdaj preberemo in obdelamo. Lahko npr. izra unamo povpre je mese nih bruto pla v javnem in v zasebnem sektorju: >>> place = uvozi_place (’place .csv ’) >>> ZJ = place [’Javni sektor ’][ ’neto ’] >>> ZZ = place [’Zasebni sektor ’][ ’neto ’] >>> avg_javni = sum(ZJ )/ len(ZJ) 13.8 Datoteke CSV 159 >>> avg_zasebni = sum(ZZ )/ len(ZZ) >>> avg_javni 1886.8506756756758 >>> avg_zasebni 1502.0971621621622 Vidimo torej, da so pla e v javnem sektorju precej viöje. Z analizami bi lahko nadaljevali. Lahko bi npr. poiskali mesec, ko je bila pla a najviöja, pogledali kaköni so trendi itd. Pred podrobnejöo obdelavo pa vselej pomaga, e si podatke prej nariöemo. In ravno to bomo naredili v naslednjem poglavju. 14 Vizualizacija podatkov 14.1 Knjiûnica Matplotlib in njena namestitev Za vizualizacijo podatkov bomo uporabljali knjiûnico Matplotlib, ki predstavlja osnovo za risanje kakrönihkoli grafov. Ker v osnovni razli ici jezika Python öe ni nameö en, ga moramo pred uporabo namestiti ( e imate nameö eno distribucijo Anaconda, imate to knjiûnico ûe nameö eno). Kot smo spoznali v poglavju 8 lahko za namestitev knjiûnice uporabimo orodje pip, tako da zaûenemo ukazno vrstico svojega operacijskega sistema (ne okolja IDLE) in vanjo vpiöemo > pip install matplotlib Podrobnejöa navodila za namestitev paketov smo podali ûe v poglavju 8, zato jih tu ne bomo podvajali. e ima vaö ra unalnik povezavo z internetom, bo orodje pip samo preneslo potrebne namestitvene datoteke in knjiûnico namestilo. Za vizualizacijo naöih podatkov bomo uporabili Matplotlibov vmesnik (angl. in-terface) pyplot, ki nam risanje precej olajöa. V svoje programe ga bomo uvozili takole: import matplotlib . pyplot as plt Zdaj lahko do funkcij za risanje grafov dostopamo takole: plt. ime_funkcije ( argumenti ) 14.2 Funkciji plot in show Za eli bomo s funkcijo plot, ki omogo a izris rtnega grafa (angl. line plot). V osnovi ji lahko podamo zgolj en seznam. Poskusimo: >>> Y = [1, 3, 9, 12] >>> plt. plot (Y) [< matplotlib . lines . Line2D object at 0 x000001DD7FB28860 >] Nekaj se je o itno zgodilo, grafa pa öe vedno ne vidimo. Funkcija plot deluje tako, da grafe riöe v ozadju in te dodaja na risalno povröino, ki pa jo pokaûe, öele ko pokli emo funkcijo show. 161 162 Poglavje 14 Vizualizacija podatkov >>> plt. show () Zdaj se je prikazal graf, ki ga prikazuje slika 14.1. To ke, ki smo jih podali Slika 14.1 rtni graf, pri emer smo podali koordinate to k na osi y. funkciji plot, o itno predstavljajo koordinate y narisanega grafa. To ke na osi x je Matplotlib dolo il kar glede na indekse to ke v seznamu Y. To lahko spremenimo, tako da funkcijo plot pokli emo z dvema seznamoma, pri emer prvi dolo a koordinate to k na osi x drugi pa koordinate to k na osi y 1. Seznama morata biti seveda enakih dolûin. Do enakega grafa kot zgoraj, bi lahko priöli takole: >>> Y = [1, 3, 9, 12] >>> X = range(len(Y)) >>> plt. plot (X, Y) [< matplotlib . lines . Line2D object at 0 x000002042BEBC128 >] plt. show () 1To se zgodi v primeru, ko imamo v prvem seznamu ötevilske vrednosti. e bi imeli v prvem seznamu nize, bi bili ti uporabljeni kot oznake na osi x, pri emer bi bile lokacije to k na osi x zopet dolo ene kar z indeksi to k na osi y. 14.2 Funkciji plot in show 163 Os x bi lahko tudi spremenili. Poskusimo: >>> X = [1 ,3 ,4 ,5] >>> Y = [1, 3, 9, 12] >>> plt. plot (X,Y) [< matplotlib . lines . Line2D object at 0 x000002042A9D0DA0 >] >>> plt. show () Graf, ki smo ga narisali tako, prikazuje slika 14.2. Na sliki je prikazan samo zadnji Slika 14.2 rtni graf, pri emer smo podali koordinate to k na obeh oseh. graf. Kam je izginil prejönji? Kot smo ûe omenili knjiûnica Matplotlib grafe riöe na risalni povröini v ozadju. Te prikaûe, ko pokli emo funkcijo show, hkrati pa takrat risalno povröino tudi po isti. e ho emo na isti sliki prikazati ve grafov, bomo pred klicanjem funkcije show narisali ve grafov. Poskusimo to kar na zgledu s pla ami, in sicer bi radi narisali podatke o povpre nih bruto pla ah. Predpostavljali bomo, da smo funkcijo uvozi_place shranili v program place_beri.py in da se ta program nahaja v naöi trenutni delovni mapi. Najprej bomo uvozili funkcijo za uvoz podatkov o pla ah zraven pa öe knjiûnico Matplotlib: 164 Poglavje 14 Vizualizacija podatkov >>> from place_beri import uvozi_place >>> import matplotlib . pyplot as plt Potem preberimo podatke o pla ah: >>> place = uvozi_place (’place .csv ’) in potegnemo sezname is slovarjev: >>> MJ = place [’Javni sektor ’][ ’mesec ’] >>> ZJ = place [’Javni sektor ’][ ’bruto ’] >>> MZ = place [’Zasebni sektor ’][ ’mesec ’] >>> ZZ = place [’Zasebni sektor ’][ ’bruto ’] Zdaj bomo kot koordinate na osi x podali podatke o mesecih. Tako se nam bodo na osi x izpisali kar podatki o mesecih. Kot koordinate na osi y podamo podatke o zneskih. Potem bomo poklicali öe funkcijo za prikaz grafa. >>> plt. plot (MJ , ZJ) >>> plt. plot (MZ , ZZ) >>> plt. show () Ve grafov lahko na isto sliko nariöemo tudi tako, da naötejemo pare seznamov kar po vrsti. Takole: >>> plt. plot (MJ , ZJ , MZ , ZZ) >>> plt. show () Kljub temu, da je rezultat v zgornjih dveh primerih enak, bomo raje uporabljali prvi na in. Zapiöimo zdaj vse skupaj kot program risi_place.py. 1 from place_beri import uvozi_place # funkcija za uvoz 2 import matplotlib . pyplot as plt 34 # uvozi podatke v slovar 5 place = uvozi_place (’place .csv ’) 6 # pridobi sezname iz slovarja 7 MJ = place [’Javni sektor ’][ ’mesec ’] 8 ZJ = place [’Javni sektor ’][ ’bruto ’] 9 MZ = place [’Zasebni sektor ’][ ’mesec ’] 10 ZZ = place [’Zasebni sektor ’][ ’bruto ’] 11 12 plt.plot(MJ , ZJ) # riöi javni sektor 13 plt.plot(MZ , ZZ) # riöi zasebni sektor 14 plt.show () # prikaûi graf Rezultat izvedbe zgornjega programa prikazuje slika 14.3. 14.3 Dodajanje oznak 165 Slika 14.3 Osnovni izris podatkov o pla ah. 14.3 Dodajanje oznak Vsak graf seveda potrebuje oznake. Ozna iti moramo kaj prikazuje posamezna os, za kar lahko uporabimo funkciji xlabel in ylabel, ki kot argument prejmeta niz, ki ga ûelimo prikazati. V naöem primeru bi bilo smiselno napisati takole: plt. xlabel (" mesec ") plt. ylabel (" znesek [EUR]") Dodamo lahko tudi naslov grafa z uporabo funkcije title. Takole: plt. title (" Povpre ne mese ne pla e") Manjka seveda tudi legenda. Kaj prikazuje modra linija in kaj oranûna? Legendo lahko dodamo tako, da oznake dodamo posameznemu grafu kar med izrisom. Funkciji plot lahko preko opcijskega argumenta label podamo niz, ki predstavlja oznako grafa, ki ga bo izrisala. V naöem primeru bi to naredili takole: plt. plot (MJ , ZJ , label =’Javni sektor ’) # riöi javni sektor plt. plot (MZ , ZZ , label =’Zasebni sektor ’) # riöi zasebni sektor 166 Poglavje 14 Vizualizacija podatkov e ûelimo legendo pokazati, moramo poklicati öe funkcijo legend, ki prikaz legende vklopi: plt. legend () Vsebino legende, ki jo ûelimo izpisati bi lahko podali tudi neposredno funkciji legend. Takole: plt. legend ([ ’Javni sektor ’, ’Zasebni sektor ’]) Pri tem moramo paziti na to, da oznake v legendi podajamo v enakem vrstnem redu, kot smo izvajali risanje grafov. Zapiöimo celoten program, ga poûenimo in poglejmo rezultat. 1 from place_beri import uvozi_place # funkcija za uvoz 2 import matplotlib . pyplot as plt 34 # uvozi podatke v slovar 5 place = uvozi_place (’place .csv ’) 6 # pridobi sezname iz slovarja 7 MJ = place [’Javni sektor ’][ ’mesec ’] 8 ZJ = place [’Javni sektor ’][ ’bruto ’] 9 MZ = place [’Zasebni sektor ’][ ’mesec ’] 10 ZZ = place [’Zasebni sektor ’][ ’bruto ’] 11 12 plt.plot(MJ , ZJ) # riöi javni sektor 13 plt.plot(MZ , ZZ) # riöi zasebni sektor 14 15 # dodaj oznake 16 plt. xlabel (" mesec ") 17 plt. ylabel (" znesek [EUR]") 18 plt. title (" Povpre ne mese ne pla e") 19 plt. legend ([ ’Javni sektor ’, ’Zasebni sektor ’]) 20 21 plt.show () # prikaûi graf Rezultat izvedbe zgornjega programa prikazuje slika 14.4. 14.4 äe malo prilagajanja oznak Kar nam öe vedno ni vöe na sliki 14.4, je neberljiv izpis na osi x. e graf pribliûamo, vidimo, da je na oseh izpisan podatek o mesecu. Mogo e bi bilo bolje, e bi ta podatek izpisali samo vsak januar, poleg tega pa bi bilo potem smiselno izpisati samo informacijo o letu (brez meseca). Poskusimo odrezati rezino po mesecih od za etka do konca, pri emer za korak nastavimo vrednost 12. 14.4 äe malo prilagajanja oznak 167 Slika 14.4 Izris podatkov o pla ah z dodanimi oznakami. >>> MJ [::12] [’2014 M01 ’, ’2015 M01 ’, ’2016 M01 ’, ’2017 M01 ’, ’2018 M01 ’, ’2019 M01 ’, ’2020 M01 ’] Pripravimo si seznam oznake, ki bo vseboval samo podatke o letih. Vzeli bomo vsak 12-ti podatek iz obstoje ega seznama mesecev (izhajamo lahko bodisi iz seznama MJ ali MZ), pri emer bomo upoötevali samo prve ötiri znake (podatek o letu). oznake = [] for mj in MJ [::12]: # vzamemo vsako 12-to oznako oznake . append (mj [:4]) # vzamemo samo podatek o letu Dolo iti moramo öe lokacije, kjer bomo te oznake prikazali. Trenutno so oznake prikazane na lokacijah, ki se ujemajo z njihovimi indeksi, torej bi lahko lokacije oznak dobili s seznamoma range(len(MJ)) ter range(len(MZ)). Ker bi radi prikazali vsako 12-to oznako, bomo morali torej upoötevati tudi vsako 12-to lokacijo. Takole: # vsaka 12-ta lokacija 168 Poglavje 14 Vizualizacija podatkov lokacije = range (0, len(MJ), 12) Lokacijo in vsebino oznak lahko zdaj naöemu risarju podamo preko funkcije xticks ( e bi ûeleli prilagajati oznake na osi y, bi uporabili funkcijo yticks): plt. xticks ( lokacije , oznake ) Celoten program je zdaj slede : 1 from place_beri import uvozi_place # funkcija za uvoz 2 import matplotlib . pyplot as plt 34 # uvozi podatke v slovar 5 place = uvozi_place (’place .csv ’) 6 # pridobi sezname iz slovarja 7 MJ = place [’Javni sektor ’][ ’mesec ’] 8 ZJ = place [’Javni sektor ’][ ’bruto ’] 9 MZ = place [’Zasebni sektor ’][ ’mesec ’] 10 ZZ = place [’Zasebni sektor ’][ ’bruto ’] 11 12 13 # dodaj oznake 14 plt. xlabel ("leto") # zdaj prikazujemo samo leta 15 plt. ylabel (" znesek [EUR]") 16 plt. title (" Povpre ne mese ne pla e") 17 plt. legend ([ ’Javni sektor ’, ’Zasebni sektor ’]) 18 19 oznake = [] 20 for mj in MJ [::12]: # vzamemo vsako 12-to oznako 21 oznake . append (mj [:4]) # vzamemo samo podatek o letu 22 23 # vsaka 12-ta lokacija 24 lokacije = range (0, len(MJ), 12) 25 26 plt. xticks (lokacije , oznake ) 27 28 plt.show () # prikaûi graf Rezultat izvedbe programa prikazuje slika 14.5. Kaj pa e bi imeli v podatkih o pla ah öe kaköen sektor ve ? Ker imamo podatke shranjene v dokaj prilagodljivi strukturi (slovarju), bi lahko izris naredili neodvisno od ötevila sektorjev. Enostavno se sprehodimo ez klju e slovarja in riöemo. Takole: for sektor in place : mesec = place [ sektor ][ ’mesec ’] 14.4 äe malo prilagajanja oznak 169 Slika 14.5 Izris podatkov o pla ah s prilagojenimi oznakami na osi x. Namesto podatkov o mesecu smo izpisali podatke o letu. znesek = place [ sektor ][ ’bruto ’] plt. plot (mesec , znesek , label = sektor ) Tokrat smo oznake grafov dodajali ûe kar med izrisovanjem preko argumenta label. Pri risanju oznak na osi x lahko uporabimo kar spremenljivko mesec, v kateri so ostali podatki zadnjega sektorja (po sprehodu z zanko for). Celotna koda je slede a: 1 from place_beri import uvozi_place # funkcija za uvoz 2 import matplotlib . pyplot as plt 34 # uvozi podatke v slovar 5 place = uvozi_place (’place .csv ’) 6 # pridobi sezname iz slovarja 7 MJ = place [’Javni sektor ’][ ’mesec ’] 8 ZJ = place [’Javni sektor ’][ ’bruto ’] 9 MZ = place [’Zasebni sektor ’][ ’mesec ’] 170 Poglavje 14 Vizualizacija podatkov 10 ZZ = place [’Zasebni sektor ’][ ’bruto ’] 11 12 13 plt.plot(MJ , ZJ) # riöi javni sektor 14 plt.plot(MZ , ZZ) # riöi zasebni sektor 15 16 # dodaj oznake 17 plt. xlabel (" mesec ") 18 plt. ylabel (" znesek [EUR]") 19 plt. title (" Povpre ne mese ne pla e") 20 plt. legend ([ ’Javni sektor ’, ’Zasebni sektor ’]) 21 22 oznake = [] 23 for mj in MJ [::12]: # vzamemo vsako 12-to oznako 24 oznake . append (mj [:4]) # vzamemo samo podatek o letu 25 26 # vsaka 12-ta lokacija 27 lokacije = range (0, len(MJ), 12) 28 29 # prikaûi oznake na osi x 30 plt. xticks (lokacije , oznake ) 31 32 plt.show () # prikaûi graf 14.5 Ostale prilagoditve izrisa e nam grafi öe vedno niso vöe , se lahko igramo naprej. Preko funkcije plot lahko nastavljamo barvo izrisa (argument color), debelino rte (argument linewidth), tip rte (argument linestyle) in öe marsikaj. Poleg tega lahko dolo amo razpon osi (funkcija axis), riöemo ve podgrafov (funkcija subplot) in graf shranjujemo v datoteko (funkcija savefig). Moûnosti je res veliko in jih tukaj ne bomo ve naötevali. Primere razli nih grafov, ki jih lahko izriöemo z uporabo knjiûnice Matplotlib, si lahko bralec pogleda (in prosto dostopno kodo prilagodi za risanje svojih grafov) na povezavi https://matplotlib.org/gallery. 14.6 Ostali tipi grafov Matplotlib poleg rtnega diagrama (funkcija plot) omogo a risanje tudi ostalih tipov grafov, npr. stolp nega diagrama (angl. bar plot) s funkcijo bar, histograma s funkcijo hist, kvartilnega diagrama s funkcijo box itd. Poglejmo si öe primer izrisa stolp nega diagrama, pri emer bomo prikazali podatke o povpre nih pla ah 14.6 Ostali tipi grafov 171 za leto 2018. Najprej iz podatkov izluö imo zgolj podatke za leto 2018. Hkrati se bomo morali sprehajati ez mesece in zneske. Ker sta seznama poravnana (isti indeks se nanaöa na isti mesec), lahko naredimo sprehod s pomo jo funkcije zip. Znotraj sprehoda pogledamo, e se mesec nanaöa na leto 2018 in v tem primeru mesec in znesek dodamo v nova seznama, ki se nanaöata na leto 2018. To naredimo za javni in zasebni sektor posebej: # uvozi podatke v slovar place = uvozi_place (’place .csv ’) # pridobi sezname iz slovarja MJ = place [’Javni sektor ’][ ’mesec ’] ZJ = place [’Javni sektor ’][ ’bruto ’] MZ = place [’Zasebni sektor ’][ ’mesec ’] ZZ = place [’Zasebni sektor ’][ ’bruto ’] MJ_2018 = [] ZJ_2018 = [] MZ_2018 = [] ZZ_2018 = [] for mj , zj in zip(MJ , ZJ ): if "2018" in mj: MJ_2018 . append (mj) ZJ_2018 . append (zj) for mz , zz in zip(MZ , ZZ ): if "2018" in mz: MZ_2018 . append (mz) ZZ_2018 . append (zz) Zdaj lahko podatke nariöemo, pri emer bomo za izris uporabili funkcijo bar. Ena izmed razlik med funkcijo plot in bar je, da moramo pri slednji lokacije stolpcev na osi x vedno podati. Uporabimo lahko kar funkcijo range: plt.bar(range(len( ZJ_2018 )), ZJ_2018 ) plt.bar(range(len( ZZ_2018 )), ZZ_2018 ) Z uporabo funkcije xticks lahko dolo imo öe oznake na osi x: plt. xticks (range(len(MJ)), MJ) Poglejmo si rezultat plt. show () Prikazuje ga slika 14.6. Oznake na osi x so zopet mote e. Grafu bi lahko do- 172 Poglavje 14 Vizualizacija podatkov Slika 14.6 Izris podatkov o pla ah za leto 2018 s stolp nim diagramom. dali naslov, da gre za leto 2018, na osi x pa prikazali samo informacijo o mesecu. Tako bo postal graf bolj pregleden. Najprej iz oznak odstranimo podatek o letu. To lahko naredimo ûe med filtiranjem podatkov za leto 2018, kjer namesto stavkov MJ_2018.append(mj) in MZ_2018.append(mz) uporabimo stavka MJ_2018.append(mj[-3:]) in MZ_2018.append(mz[-3:]). Naslov grafa dodamo s slede o vrstico: plt. title (’Podatki o pla ah za leto 2018 ’) Dodajmo öe legendo: plt. legend ([ ’Javni sektor ’, ’Zasebni sektor ’]) Kar nas öe moti je to, da sta grafa naloûena drug na drugega. e bi npr. najprej izrisali zasebni sektor, bi ga javni sektor prekril. Bolj pravilno bi bilo, e bi grafa narisala drug ob drugemu. Kako lahko to naredimo? Tako, da en graf zamaknemo v levo, drugega pa v desno. Popravili bomo tudi öirino stolpcev, da bodo malo oûji. Najprej dolo imo lokacije stolpcev: loc1 = list(range(len( ZJ_2018 ))) 14.6 Ostali tipi grafov 173 loc2 = list(range(len( ZZ_2018 ))) for i in range(len(loc1 )): loc1 [i] -= 0.2 loc2 [i] += 0.2 Zdaj te lokacije upoötevajmo pri izrisu, poleg tega pa preko izbirnega argumenta width nastavimo öe öirino stolpcev: plt.bar(loc1 , ZJ_2018 , width = 0.4) plt.bar(loc2 , ZZ_2018 , width = 0.4) Celoten program je zdaj slede : 1 from place_beri import uvozi_place # funkcija za uvoz 2 import matplotlib . pyplot as plt 34 # uvozi podatke v slovar 5 place = uvozi_place (’place .csv ’) 6 # pridobi sezname iz slovarja 7 MJ = place [’Javni sektor ’][ ’mesec ’] 8 ZJ = place [’Javni sektor ’][ ’bruto ’] 9 MZ = place [’Zasebni sektor ’][ ’mesec ’] 10 ZZ = place [’Zasebni sektor ’][ ’bruto ’] 11 12 # izluö imo podatke za leto 2018 13 MJ_2018 = [] 14 ZJ_2018 = [] 15 MZ_2018 = [] 16 ZZ_2018 = [] 17 18 for mj , zj in zip(MJ , ZJ ): 19 if "2018" in mj: 20 MJ_2018 . append (mj [ -3:]) # brez leta 21 ZJ_2018 . append (zj) 22 23 for mz , zz in zip(MZ , ZZ ): 24 if "2018" in mz: 25 MZ_2018 . append (mz [ -3:]) # brez leta 26 ZZ_2018 . append (zz) 27 28 loc1 = list(range(len( ZJ_2018 ))) 29 loc2 = list(range(len( ZZ_2018 ))) 30 for i in range(len(loc1 )): 31 loc1 [i] -= 0.2 174 Poglavje 14 Vizualizacija podatkov 32 loc2 [i] += 0.2 33 34 plt.bar(loc1 , ZJ_2018 , width = 0.4) 35 plt.bar(loc2 , ZZ_2018 , width = 0.4) 36 plt. xticks (range(len( MJ_2018 )), MJ_2018 ) 37 38 plt. title (’Podatki o pla ah za leto 2018 ’) 39 plt. legend ([ ’Javni sektor ’, ’Zasebni sektor ’]) 40 plt. xlabel (" Mesec ") 41 plt. ylabel (" Znesek [EUR]") 42 plt.show () Rezultat izvedbe programa prikazuje slika 14.7. Slika 14.7 Dopolnjen in popravljen izris podatkov o pla ah za leto 2018 s stolp nim diagramom. 14.7 Risanje matemati nih funkcij 175 14.7 Risanje matemati nih funkcij Kaj pa e bi ûeleli narisati graf matemati ne funkcije? Podobno kot v zgornjih primerih, bomo funkciji za risanje podali seznam koordinat na osi x in seznam koordinat na osi y. Koordinate na osi x lahko dolo imo glede na ûeljen razpon vrednosti – tega dolo imo sami. Za dolo itev vrednosti na osi y pa moramo funkcijo tabelirati pri podanih vrednostih osi x. Poskusimo na zgledu: Zgled 65. Nariöi grafe logaritemskih funkcij z osnovo 2, e in 10 na intervalu od 0 do 5. Sliko opremi z legendami, vsak graf pa naj bo narisan s svojo barvo. Reöitev 65. Najprej bomo dolo ili koordinate osi x. Lahko bi jih naöteli, lahko pa uporabimo kaköno funkcijo za generiranje ötevil v podanem intervalu – npr. funkcijo range. >>> X = range (6) e ûelimo v razpon vklju iti tudi ötevilo 5, moramo kot argument stop podati ötevilo 6. Kako bomo priöli do funkcij za izra un logaritmov? Spomnimo se na modul math. e pogledamo njegovo dokumentacijo, vidimo, da vsebuje funkcije log2, log in log10. Uvozimo jih. >>> from math import log2 , log , log10 Poskusimo zdaj izra unati koordinate to k na osi y: >>> Y_2 = log2 (X) TypeError : must be real number , not range Tole ne bo ölo. funkcije za izra un logaritmov znajo delati samo s ötevilskimi argumenti, mi pa smo podali seznam. Logaritem bomo morali torej izra unati za vsako to ko posebej. Poskusimo najprej z dvojiökim logaritmom. Najprej bomo naredili prazen seznam. Potem bomo naredili sprehod ez vse to ke na osi x in za vsako posebej izra unali logaritem ter ga dodali v seznam. >>> Y_2 = [] >>> for x in X: Y_2. append ( log2 (x)) ValueError : math domain error Kaj je narobe tokrat? Tokrat problem ni programerski, ampak matemati ni, saj logaritem ötevila 0 ni definiran. Razpon to k na osi x bo torej potrebno zmanjöati na interval od vrednosti 1 do 5. Takole: >>> X = range (1 ,6) >>> Y_2 = [] >>> for x in X: Y_2. append ( log2 (x)) 176 Poglavje 14 Vizualizacija podatkov Podobno naredimo öe za naravni in desetiöki logaritem in stvar nariöemo. Celotna reöitev bo slede a: 1 from math import log2 , log , log10 2 import matplotlib . pyplot as plt 34 X = range(1,6) # koordinate to k na x osi 56 Y_2 = [] 7 Y_e = [] 8 Y_10 = [] 9 10 for x in X: 11 Y_2. append ( log2 (x)) # dvojiöki 12 Y_e. append (log(x)) # naravni 13 Y_10 . append ( log10 (x)) # desetiöki 14 15 plt.plot(X,Y_2 , label =" $log_2 (x)$") 16 plt.plot(X,Y_e , label =" $log_e (x)$") 17 plt.plot(X,Y_10 , label =" $log_ {10}( x)$") 18 plt. xlabel (’x’) 19 plt. ylabel (’y’) 20 plt. legend () 21 22 plt.show () Graf, ki ga na ta na in dobimo, prikazuje slika 14.8. 2 Problem je, da ta graf zgolj pribliûno spominja na izris logaritemskih funkcij. Izrisali smo namre samo 5 to k in te med seboj povezali. Prav tako nam manjka izris to k, ki imajo koordinato x na intervalu (0, 1). Kako bi lahko resolucijo izrisovanja pove ali? Tako, da bi zmanjöali korak, s katerim generiramo koordinate na osi x. Idealno bi bilo, e bi izrisovanje za eli pri neki zelo majhni vrednosti x-a (npr. 0.001), potem pa to vrednost pove evali do ötevila 5, z nekim zelo majhnim korakom (npr. 0.001). Funkciji range bi to podali na slede na in: >>> X = range (0.001 , 5.001 , 0.001) TypeError : ’float ’ object cannot be interpreted as an integer Problem tega pristopa je ta, da lahko funkciji range podamo samo celoötevilske argumente. Znajti se bomo morali torej druga e. Alternativen pristop bi bil, da s 2Oznake posameznih linij (label) smo podali med simboloma $. S tem smo povedali, da podajamo zapis, ki ga bo Matplotlib pretvoril v ena bo. Zapis $log_2(x)$ bo Matplotlib tako izpisal kot log 2( x). Pri takem na inu podajanja ena b moramo upoötevati pravila pisanja ena b v jeziku LaTeX, kjer je pod rtaj (_) simbol za podpisan tekst. 14.7 Risanje matemati nih funkcij 177 Slika 14.8 Osnovni izris logaritemskih funkcij. funkcijo range zgeneriramo to ke od 1 do 5000, in potem vsako izmed njih delimo s 1000. Tako bomo dobili definicijsko obmo je z ûeljeno resolucijo. >>> X_cel = range (1 ,5001 ,1) >>> X = [] >>> for x in X_cel : x /= 1000 X. append (x) Zdaj lahko pri ra unanju upoötevamo te vrednosti. Dopolnimo zgornjo kodo in poglejmo rezultat, ki ga prikazuje slika 14.9. Celotno reöitev prikazuje spodnja koda: >>> X = range (1 ,6) >>> Y_2 = [] >>> for x in X: Y_2. append ( log2 (x)) Podobno naredimo öe za naravni in desetiöki logaritem in stvar nariöemo. Celotna reöitev bo slede a: 178 Poglavje 14 Vizualizacija podatkov Slika 14.9 Izris logaritemskih funkcij z ustreznejöo resolucijo. 1 from math import log2 , log , log10 2 import matplotlib . pyplot as plt 34 X_cel = range(1,5001,1) # predpriprava za koordinate x 56 X = [] 7 Y_2 = [] 8 Y_e = [] 9 Y_10 = [] 10 11 for x in X_cel : 12 x /= 1000 # koordinata x 13 X. append (x) 14 15 Y_2. append ( log2 (x)) # dvojöiki 16 Y_e. append (log(x)) # naravni 17 Y_10 . append ( log10 (x)) # desetiöki 14.7 Risanje matemati nih funkcij 179 18 19 plt.plot(X,Y_2 , label =" $log_2 (x)$") 20 plt.plot(X,Y_e , label =" $log_e (x)$") 21 plt.plot(X,Y_10 , label =" $log_ {10}( x)$") 22 plt. xlabel (’x’) 23 plt. ylabel (’y’) 24 plt. legend () 25 26 plt.show () — Tole je bilo precej nerodno predvsem iz dveh razlogov. Prvi , za izra un logaritmov vseh vrednosti v seznamu X smo se morali sprehoditi ez cel seznam in vsako vrednost izra unati posebej. Veliko laûje bi bilo, e bi lahko funkcijo poklicali kar nad celotnim seznamom (brez uporabe zanke for). Drugi , funkciji range lahko podamo samo celoötevilske argumente. Ker smo ûeleli biti pri izrisovanju logaritmov nekoliko bolj natan ni, smo morali generiranje definicijskega obmo ja nekoliko zakomplicirati. Idealno pa bi bilo, e bi lahko funkciji range oziroma njej podobni funkciji podali decimalne korake. Izkaûe se, da lahko zgornja problema reöimo z uporabo knjiûnice NumPy, ki pa nam poleg tega olajöa öe marsikaj drugega. 15 Knjiûnica NumPy in hitro ra unanje 15.1 NumPy in ndarray Knjiûnica NumPy nadgrajuje Pythonove sezname, poleg tega pa nudi ötevilne funkcije, ki nam olajöajo predvsem matemati ne operacije nad t.i. nadgrajenimi seznami oziroma strukturo ndarray (angl. N-dimensional array). Vse se bo torej vrtelo okrog strukture ndarray. Kljub temu, da osnovna namestitev okolja Python knjiûnice NumPy ne vsebuje, se je knjiûnica namestila skupaj z namestitvijo knjiûnice Matplotlib, saj jo slednja potrebuje za svoje delovanje. Uvozimo jo v svoje delovno okolje. >>> import numpy as np Zdaj lahko poljuben seznam (ali seznamu podobno strukturo) pretvorimo v ndarray z uporabo funkcije array: >>> X = np. array ([1 ,2 ,3]) >>> Y = np. array ([4 ,5 ,6]) Kaj lahko z novimi seznami po nemo? Podobno kot obi ajne sezname, lahko tudi te indeksiramo, nad njimi uporabljamo vgrajene funkcije, preverjamo, e vsebujejo dolo en element in se ez njih sprehajamo z zanko for: >>> X[0] 1>>> len(X) 3>>> 1 in X True >>> for x in X: print(x) 123 181 182 Poglavje 15 Knjiûnica NumPy in hitro ra unanje 15.2 Aritmeti ni operatorji in strutkura ndarray Sezname smo lahko med seboj tudi seötevali. Tudi ndarray-e lahko, vendar je rezultat nekoliko druga en: >>> X+Y array ([5 , 7, 9]) Dobili smo torej nov ndarray, ki je sestavljen iz vsote istoleûnih elementov izhodiö nih ndarray-ev. Na tak ndarray lahko torej gledamo kot na vektor, na vsoto dveh ndarray-ev pa kot na vsoto dveh vektorjev. Zdaj je pogoj za seötevanje to, da sta oba vektorja oziroma ndarray-a enako dolga. Tole, npr. ne bo ölo: >>> Z = np. array ([7 ,8]) >>> X+Z ValueError : operands could not be broadcast together with shapes (3 ,) (2 ,) Lahko pa vektorju priötejemo skalar: >>> X+10 array ([11 , 12, 13]) Nad strukturami tipa ndarray lahko za razliko od seznamov uporabimo tudi druge aritmeti ne operatorje. Poskusimo: >>> X*Y array ([ 4, 10, 18]) >>> X/Y array ([0.25 , 0.4 , 0.5 ]) >>> X-Y array ([-3, -3, -3]) >>> X**5 array ([ 1, 32, 243] , dtype = int32 )1 >>> X/10 array ([0.1 , 0.2 , 0.3]) Aritmeti ne operatorje lahko torej na tak na in izvedemo nad vsakim elementom vektorja posebej. Poleg tega, da za to ne potrebujemo zank, so te operacije ra unsko zelo u inkovite in delujejo hitro tudi v primeru, ko delamo z velikimi koli inami podatkov. 1Pri potenciranju izpis vsebuje tudi atribut dtype, ki podaja podatkovni tip rezultata. V tem primeru je to 32-bitni int. Tak rezultat smo sicer dobili tudi pri mnoûenju, odötevanju in seötevanju, le da Python v teh primerih tega ni posebej izpisal. Atribut je spremenljivka, ki pripada dolo enemu objektu, podobno kot je metoda funkcija, ki pripada dolo enemu objektu. 15.3 Primerjalni operatorji, indeksiranje s seznami in filtiranje 183 15.3 Primerjalni operatorji, indeksiranje s seznami in filtiranje Podobno kot aritmeti ni operatorji delujejo nad strukturo ndarray tudi primerjalni operatorji, in sicer tako, da primerjanje izvedejo nad vsakim elementom strukture posebej. Primerjamo lahko npr. dva vektorja: >>> X = np. array ([1 ,2 ,3]) >>> Z = np. array ([0.1 , 10, -2]) >>> X < Z array ([ False , True , False ]) Dobili smo torej vektor, ki vsebuje rezultate preverjanja na posameznih mestih. Primerjanje bi lahko izvajali tudi s skalarjem >>> X >= 2 array ([ False , True , True ]) Dodatna prednost uporabe strukture ndarray je, da lahko to indeksiramo z vektorjem vrednosti True in False, pri emer bomo kot rezultat dobili ndarray elementov, ki smo jih naslovili z vrednostjo True. Rezultat primerjanja strukture ndarray lahko torej uporabimo za indeksiranje. e bi npr. ûeleli dobiti tiste elemente vektorja X, ki so ve ji ali enaki 2, bi lahko to naredili takole: >>> sito = X >= 2 >>> X[ sito ] array ([2 , 3]) oziroma krajöe: >>> X[X >= 2] array ([2 , 3]) Tej operaciji lahko re emo tudi filtriranje vrednosti. 15.4 Generiranje strukture ndarray Kot smo videli zgoraj lahko strukturo ndarray dobimo tako, da s funkcijo array vanjo pretvorimo obi ajen seznam. Modul NumPy pa ponuja tudi ötevilne funkcije, ki jih lahko uporabimo pri generiranju struktur ndarray z vnaprej poznanimi lastnostmi. Strukturo ndarray, ki npr. vsebuje same ni le, lahko zgeneriramo s funkcijo zeros, same enice s funkcijo ones, matriko naklju nih ötevil pa preko modula numpy.random. Podrobneje si bomo pogledali funkcijo arange in linspace, ki sta namenjeni generiranju struktur ndarray na vnaprej dolo enem intervalu. Funkcija arange deluje zelo podobno kot funkcija range, le da vra a strukturo tipa ndarray poleg tega pa za razliko od funkcije range ni omejena na celoötevilske 184 Poglavje 15 Knjiûnica NumPy in hitro ra unanje argumente. e bi npr. ûeleli zgenerirati vektor vrednosti od 0.001 do 5 s korakom 0.001, bi to naredili s slede im klicem: >>> X = np. arange (0.001 , 5.001 , 0.001) Funkcija linspace deluje podobno, le da tej kot argumente podamo za etno in kon no to ko intervala (tokrat bo slednja v intervalu vsebovana) in ötevilo to k, ki jih ûelimo v intervalu imeti. Enak rezultat, kot ga dobimo z gornjim klicem funkcije arange, lahko dobimo tudi s funkcijo linspace takole: >>> X = np. linspace (0.001 , 5, 5000) Rekli smo torej, da ûelimo imeti 5000 to k na intervalu, ki se za ne s to ko 0.001 in kon a s to ko 5 (ta to ka je v intervalu öe vsebovana). Pri tem je uporabljena linearna interpolacija med to kami, kar z drugimi besedami pomeni, da je med sosednjima to kama v vektorju razlika vselej enaka. 15.5 Funkcije nad strukturo ndarray e ûelimo narisati graf logaritemske funkcije, lahko torej koordinate na osi x enostavneje zgeneriramo s funkcijo arange. Zdaj moramo le öe izra unati njihove logaritme. Poskusimo: >>> from math import log2 , log , log10 >>> log2 (X) TypeError : only size -1 arrays can be converted to Python scalars Funkcije modula math torej nad seznami ne moremo vektorsko izvajati2, ampak se moramo spet zate i k zanki for. Izkaûe pa se, da knjiûnica NumPy vsebuje tudi matemati ne funkcije, ki zamenjujejo tiste iz modula math, poleg tega pa podpirajo vektorski na in izvajanja. Nad celotnim vektorjem jih torej lahko izvedemo z enim samim klicem. Poskusimo: >>> np. log2 (X) array ([ -9.96578428 , -8.96578428 , -8.38082178 , ... , 2.3213509 , 2.32163953 , 2.32192809]) Zdaj lahko dokon amo zgled z risanjem logaritemskih funkcij. Zgled 66. Nariöi grafe logaritemskih funkcij z osnovo 2, e in 10 na intervalu od 0 do 5. Sliko opremi z legendami, vsak graf pa naj bo narisan s svojo barvo. Reöitev 66. Reöitev bo zdaj bistveno krajöa, saj bomo vrednosti na osi x generirali s funkcijo np.arange (ena vrstica), logaritme pa ra unali s funkcijami np.log2, 2Vektorsko izvajanje pomeni, da isto operacijo izvedemo nad vsemi elementi seznama (seznamov), ki si ga (jih) lahko v tem primeru interpretiramo kot vektor (vektorje). 15.6 Ve dimenzij 185 np.log in np.log10 (tri vrstice). Ostala koda bo ostala enaka, prav tako pa bo enak rezultat, ki je prikazan na sliki 14.9. 1 import numpy as np 2 import matplotlib . pyplot as plt 34 # hitro generiranje vrednosti na osi x 5 X = np. arange (0.001 , 5.001 , 0.001) 67 # ra unanje logaritmov brez zanke for 8 Y_2 = np.log2(X) 9 Y_e = np.log(X) 10 Y_10 = np. log10 (X) 11 12 # nespremenjena koda od prej 13 plt.plot(X,Y_2 , label =" $log_2 (x)$") 14 plt.plot(X,Y_e , label =" $log_e (x)$") 15 plt.plot(X,Y_10 , label =" $log_ {10}( x)$") 16 plt. xlabel (’x’) 17 plt. ylabel (’y’) 18 plt. legend () 19 20 plt.show () — 15.6 Ve dimenzij Tekom spoznavanja seznamov smo se sre ali tudi s seznami seznamov oziroma z ugnezdenimi seznami. Primer takega seznama je slede : >>> sez = [[1 , 2, 3], [4, 5, 6], [7, 8, 9]] Kaj se zgodi, e tak seznam pretvorimo v strukturo ndarray: >>> A = np. array (sez) >>> A array ([[1 , 2, 3], [4, 5, 6], [7, 8, 9]]) Na prvi pogled ni kaj presenetljivega, ampak e smo si lahko strukturo ndarray, ki smo jo dobili iz navadnega seznama, interpretirali kot vektor, lahko na strukturo ndarray, ki jo dobimo iz ugnezdenih seznamov, gledamo kot na matriko oziroma na 186 Poglavje 15 Knjiûnica NumPy in hitro ra unanje dvodimenzionalno strukturo, ki ima podatke zapisane v vrsticah (dimenzija 0) in stolpcih (dimenzija 1). Pri tem velja omejitev, da morajo biti vsi ugezdeni seznami enako dolgi. Tako dobljeno matriko lahko indeksiramo kot obi ajne ugnezdene sezname. Do ugnezdenega podseznama na npr. indeksu 1, lahko pridemo takole: >>> A[1] array ([4 , 5, 6]) V matri ni interpretaciji to predstavlja vrstico 1. Do elementa na indeksu 2 vrstice 1 lahko naprej pridemo takole: >>> A [1][2] 6 Delovanje je bilo do tukaj zelo podobno kot pri obi ajnih ugnezdenih seznamih, le interpretacija je malo druga na. Enako bi lahko naredili tudi pri obi ajnem seznamu >>> sez [1][2] 6 Pri delu s strukturo ndarray lahko do elementov matrike pridemo tudi tako, da indeksa vrstice in stolpca podamo skupaj in tako indeksiranje izvedemo le enkrat. To pri obi ajnih seznamih ne gre: >>> A[1 ,2] 6>>> sez[1,2] TypeError : list indices must be integers or slices , not tuple Pri indeksiranju smo torej najprej podali indeks po ni ti dimenziji oziroma indeks vrstice, potem pa indeks po prvi dimenziji oziroma indeks stolpca (ni nas ne omejuje pri tem, da ne bi ötevilo dimenzij öe pove ali – tako bi dobili tenzor). Tako dobljene matrike lahko podobno kot vektorje med seboj tudi npr. seötevamo in jim priötevamo skalarje: >>> B = np. array ([[10 , 11, 12] , [-1, -2, -3], [0.1 , 0.2 , 0.3]]) >>> A+B array ([[11. , 13. , 15. ], [ 3. , 3. , 3. ], [ 7.1 , 8.2 , 9.3]]) Nad njimi lahko delamo tudi rezine, in sicer preko vsake dimenzije posebej. e bi ûeleli npr. iz matrike A dobiti vrstice od 0 do 2, in stolpce od 1 do konca, bi to napisali takole: 15.7 Ostale uporabne funkcije 187 >>> A[:2 ,1:] array ([[2 , 3], [5, 6]]) Lahko bi dobili tudi specifi no vrstico. Vrstico 2, bi npr. dobili takole: >>> A[2 ,:] array ([7 , 8, 9]) Z zgornjo vrstico smo povedali, da ûelimo ni to dimenzijo fiksirati na indeks 2 (vrstica ötevilka 2), dimenzijo 1 pa ûelimo imeti v celoti (vsi stolpci). To bi lahko sicer napisali tudi takole >>> A[2] array ([7 , 8, 9]) in bi seveda delovalo tudi nad navadnimi seznami. Kako bi lahko priöli do posameznega stolpca? Podobno kot prej, le da zdaj fiksiramo indeks stolpca in se sprehajamo ez vse vrstice. Do stolpca ötevilka 2 bi torej priöli takole: >>> A[: ,2] array ([3 , 6, 9]) Tega nad obi ajnimi seznami ne moremo (tako enostavno) narediti. 15.7 Ostale uporabne funkcije Knjiûnica NumPy ponuja öe vrsto drugih uporabnih funkcij, ki pa jih boste spoznali, e boste knjiûnico bolj intenzivno uporabljali. Nekaj jih bomo vseeno omenili. Za izra un vsote elementov, dolo itev minimalnega in maksimalnega elementa lahko uporabimo vgrajene funkcije min, max in sum. Te funkcije pa odpovejo, ko ima naöa struktura ve dimenzij ali pa vsebuje posebne vrednosti (glej razdelek 15.8). V tem primeru lahko uporabljamo funkcije modula NumPy z enakimi imeni. V osnovi te funkcije delujejo nad celotno strukturo (nad vsemi dimenzijami). Takole: >>> A = np. array ([[1 ,2 ,3] ,[4 ,5 ,6] ,[7 ,8 ,9]]) >>> np. min(A) 1>>> np. max(A) 9>>> np. sum(A) 45 V asih ûelimo dolo eno operacijo izvesti le nad npr. vrsticami ali stolpci matrike. V tem primeru lahko pri uporabi zgornjih funkcij posebej podamo öe vrednost argumenta axis. e ta argument nastavimo na vrednost 0, se bomo tekom operacije znebili dimenzije 0 (vrstic), kar pomeni, da bomo dobili rezultat izvedbe operacije po stolpcih. 188 Poglavje 15 Knjiûnica NumPy in hitro ra unanje >>> A = np. array ([[1 ,2 ,3] ,[4 ,5 ,6] ,[7 ,8 ,9]]) >>> np. min(A, axis =0) array ([1 , 2, 3]) >>> np. max(A, axis =0) array ([7 , 8, 9]) >>> np. sum(A, axis =0) array ([12 , 15, 18]) e argument axis nastavimo na vrednost 1, se bomo s tem znebili dimenzije 1 (stolpcev), kar pomeni, da bomo dobili minimum, maksimum in vsoto vrstic: >>> A = np. array ([[1 ,2 ,3] ,[4 ,5 ,6] ,[7 ,8 ,9]]) >>> np. min(A, axis =1) array ([1 , 4, 7]) >>> np. max(A, axis =1) array ([3 , 6, 9]) >>> np. sum(A, axis =1) array ([ 6, 15, 24]) 15.8 Posebne vrednosti Knjiûnica NumPy omogo a, da kot ötevila predstavimo tudi posebne vrednosti. Prva taka vrednost je uporabna predvsem v primerih, ko nam dolo en podatek manjka. e npr. beleûimo viöino, telesno maso in starost oseb, se lahko hitro zgodi, da med podatki kaköen manjka (nekdo npr. ne ûeli povedati koliko je star). Narobe bi bilo, e bi tak podatek postavili na neko privzeto numeri no vrednost (npr. 0), saj bi nam to pokvarilo dolo ene statistike, kot je npr. povpre je. Prav tako velikokrat podatka ne moremo kar izpustiti. e ûelimo podatke npr. beleûiti v matriki tipa ndarray, morajo imeti vse vrstice enako ötevilo stolpcev. V tem primeru lahko uporabimo posebno vrednost nan (angl. not a number), ki predstavlja t.i. placeholder in drûi prazno mesto, hkrati pa ga lahko pri analizi vrednosti obravnavamo druga e kot ostale vrednosti (npr. ga izpustimo). V primeru, da naöi podatki vsebujejo vrednost nan in ûelimo to pri analizah ignorirati, lahko namesto funkcij kot so min, max in sum uporabimo funkcije nanmin, nanmax in nansum. Poleg vrednosti nan knjiûnica NumPy vsebuje tudi posebno vrednost inf, s katero lahko npr. zapiöemo rezultat deljenja z ni ali pa logaritem ötevila 0. Pri tem sicer dobimo opozorilo (ne pa napake): >>> np.log (0) RuntimeWarning : divide by zero encountered in log -inf 15.9 Uvaûanje vrednosti in omejitve strukture ndarray 189 15.9 Uvaûanje vrednosti in omejitve strukture ndarray V prejönjih poglavjih smo ûe omenili datoteke CSV. Ker se taka oblika zapisovanja pogosto uporablja tudi za numeri ne podatke, knjiûnica NumPy podpira uvoz tovrstnih datotek preko funkcij genfromtxt in loadtxt. Ker je knjiûnica NumPy namenjena delu s ötevili, obe funkciji vsebino datoteke poskuöata vrniti kot ndarray ötevil. Kadar datoteka vsebuje zgolj numeri ne podatke (ötevila) funkciji ustvarita enak rezultat. Pri tem (obi ajno) funkcijama kot argument encoding podamo kodiranje datoteke3 kot argument delimiter pa lo ilo, ki je uporabljeno znotraj datoteke. Naj ima datoteka z imenom stevila1.csv slede o vsebino: 1 180.3 ,87.3 2 161.5 ,77.3 3 170 ,56.5 4 156 ,55.3 Preberemo jo lahko takole: >>> A1 = np. genfromtxt (" stevila1 .csv", encoding =" utf8 ", delimiter =",") >>> B1 = np. loadtxt (" stevila1 .csv", encoding =" utf8 ", delimiter =",") Pri tem v obeh primerih dobimo enak rezultat: >>> A1 array ([[180.3 , 87.3] , [161.5 , 77.3] , [170. , 56.5] , [156. , 55.3]]) >>> B1 array ([[180.3 , 87.3] , [161.5 , 77.3] , [170. , 56.5] , [156. , 55.3]]) Do teûav lahko pride, kadar datoteka vsebuje tudi podatke, ki niso ötevilskega tipa. Ena izmed glavnih omejitev strukture ndarray je namre ta, da zahteva, da vsi njeni podatki pripadajo enakemu podatkovnemu tipu. Podatkovni tip strukture lahko preverimo preko atributa4 dtype. 3 e vemo, da datoteka vsebuje samo ötevila, kodiranja ni potrebno podajati. 4Atribut je spremenljivka, ki pripada dolo enemu objektu, podobno kot je metoda funkcija, ki pripada dolo enemu objektu. 190 Poglavje 15 Knjiûnica NumPy in hitro ra unanje 1 >>> A1. dtype # do atributov dostopamo brez oklepajev 2 dtype (’float64 ’) Vsi podatki v strukturi A1 so torej decimalna ötevila5). Kaj se torej zgodi, e datoteka vsebuje podatke, ki niso ötevila. Dopolnimo naöo datoteko s stolpcem, ki bo vsebovala imena ljudi, ki seveda niso ötevila. To shranimo v datoteko stevila2.csv: 1 Janez ,180.3 ,87.3 2 Andrej ,161.5 ,77.3 3 Ana ,170 ,56.5 4 Katja ,156 ,55.3 Funkcija genfromtxt bo imena enostavno pretvorila v vrednosti nan, saj to lahko zapiöe kot ötevilo. S tem bo dobljena struktura öe vedno lahko vsebovala ötevila, s katerimi bomo lahko ra unali. Poskusimo: >>> A2 = np. genfromtxt (" stevila2 .csv", encoding =" utf8 ", delimiter =",") >>> A2 array ([[ nan , 180.3 , 87.3] , [ nan , 161.5 , 77.3] , [ nan , 170. , 56.5] , [ nan , 156. , 55.3]]) S tem smo informacijo o imenih sicer izgubili, smo pa ohranili podatkovni tip strukture, tako da ta öe vedno vsebuje ötevila >>> A2. dtype dtype (’float64 ’) Kaj pa funkcija loadtxt? Ta poskuöa na vsak na in podatke pretvoriti v ötevila, zato ob odpiranju take datoteke vrne napako: >>> B2 = np. loadtxt (" stevila2 .csv", encoding =" utf8 ", delimiter =",") ValueError : could not convert string to float: ’Janez ’ Lahko jo nekoliko prelisi imo, da ji naro imo, naj datoteko uvozi kot nize. To naredimo preko opcijskega argumenta dtype, ki ga nastavimo na vrednost str ali pa vrednost ’U’ (angl. Unicode string)6. 5float64 predstavlja zapis ötevil v plavajo i vejici s 64 biti oziroma z dvojno natan nostjo. Knjiûnica NumPy pri zapisovanju decimalnih ötevil uporablja ve jo natan nost kot vgrajeni podatkovni tip float. 6Tako kot za decimalna ötevila knjiûnica NumPy tudi za zapisovanje nizov uporablja svoj podatkovni tip ’U’ (angl. Unicode string) 15.9 Uvaûanje vrednosti in omejitve strukture ndarray 191 >>> B2 = np. loadtxt (" stevila2 .csv", encoding =" utf8 ", delimiter =",", dtype =str) array ([[ ’Janez ’, ’180.3 ’, ’87.3 ’], [’Andrej ’, ’161.5 ’, ’77.3 ’], [’Ana ’, ’170 ’, ’56.5 ’], [’Katja ’, ’156 ’, ’55.3 ’]], dtype =’>> imena = B2 [: ,0] >>> imena array ([ ’Janez ’, ’Andrej ’, ’Ana ’, ’Katja ’], dtype =’>> vrednosti = B2 [: ,1:]. astype (float) >>> vrednosti array ([[180.3 , 87.3] , [161.5 , 77.3] , [170. , 56.5] , [156. , 55.3]])) Opomba: funkcija astype izhodiö ne strukture ne spreminja, ampak vrne strukturo predstavljeno s podanim podatkovnim tipom. Datoteka bi lahko vsebovala tudi imena stolpcev. V naöem primeru bi to izgledalo nekako takole (datoteka stevila3.csv: 1 ime ,viöina ,teûa 2 Janez ,180.3 ,87.3 3 Andrej ,161.5 ,77.3 4 Ana ,170 ,56.5 5 Katja ,156 ,55.3 V tem primeru lahko funkciji genfromtxt naro imo, naj lo eno uvozi imena stolpcev preko izbirnega argumenta names, ki ga nastavimo na vrednost True: >>> A3 = np. genfromtxt (" stevila3 .csv", encoding =" utf8 ", delimiter =",", names = True ) 192 Poglavje 15 Knjiûnica NumPy in hitro ra unanje Zdaj so imena stolpcev zapisana lo eno znotraj atributa dtype. >>> A3 array ([( nan , 180.3 , 87.3) , (nan , 161.5 , 77.3) , (nan , 170. , 56.5) , (nan , 156. , 55.3)] , dtype =[( ’ime ’, ’>> A3. dtype . names (’ime ’, ’viöina ’, ’teûa’) Do imen stolpcev torej lahko pridemo, je pa malo nerodno. Pri uporabi funkcije np.loadtxt bi lahko postopali podobno kot prej in celotno datoteko uvozili kot ndarray nizov: >>> B3 = np. loadtxt (" stevila3 .csv", encoding =" utf8 ", delimiter =",", dtype =’U’) >>> B3 array ([[ ’ime ’, ’viöina ’, ’teûa’], [’Janez ’, ’180.3 ’, ’87.3 ’], [’Andrej ’, ’161.5 ’, ’77.3 ’], [’Ana ’, ’170 ’, ’56.5 ’], [’Katja ’, ’156 ’, ’55.3 ’]], dtype =’ pip install pandas Zdaj lahko knjiûnico uvozimo v svoj program oziroma v svoje delovno okolje, ponavadi pod psevdonimom pd: >>> import pandas as pd Ve je koli ine podatkov bomo ponavadi brali iz datotek zapisanih v obliki CSV. Pandas tako branje omogo a preko funkcije read_csv. Poskusimo kar na naöem zgledu s pla ami. 195 196 Poglavje 16 Knjiûnica pandas >>> pd. read_csv (’place .csv ’) UnicodeDecodeError : ’utf -8 ’ codec can ’t decode byte 0xe8 in position 6: invalid continuation byte Tole ni delovalo, ker moramo podati pravilno dekodiranje. Nastavimo argument encoding, poleg tega pa rezultat branja shranimo v spremenljivko: >>> df = pd. read_csv (’place .csv ’, encoding =" cp1250 ") Funkcija read_csv vra a prebrane podatke v obliki strukture dataframe, ki predstavlja tabelo z zapisanimi podatki. Prvih pet vrstic tabele lahko dobimo z metodo head(). Poglejmo kaj smo prebrali. >>> df. head () Povpre ne mese ne bruto in neto pla e pri pravnih osebah javnega in zasebnega sektorja , Slovenija , mese no 0 MESEC \t" SEKTOR "\t" Bruto pla a Pla a za mesec [E... 1 2014 M01\t" Javni sekto r"\ t1758 .50\ t1151 .30 2 2014 M01\t" Zasebni sekto r"\ t1421 .34\ t932 .14 3 2014 M02\t" Javni sekto r"\ t1745 .63\ t1136 .41 4 2014 M02\t" Zasebni sekto r"\ t1406 .47\ t922 .19 Tabela öe vedno ni tabela. Smiselno bi bilo, da prve vrstice z opisom vsebine datoteke, izpustimo (to smo naredili tudi prej), tako da izbirnemu argumentu skiprows priredimo vrednost 2. Zakaj bomo tokrat izpustili 2 vrstici, ko smo datoteko brali z metodo read pa smo izpustili 3? Struktura dataframe bo prvo prebrano vrstico (za izpuö enima dvema vrsticama) uporabila kot glavo tabele. Druga stvar, ki jo moralo naöemu bralniku nastaviti je öe lo ilo oziroma separator, ki je v tem primeru tabulator oziroma znak ’\t’. Tega nastavimo preko argumenta sep. Poskusimo datoteko öe enkrat prebrati: >>> df = pd. read_csv (’place .csv ’, encoding =" cp1250 ", skiprows =2, sep=’\t’) Preverimo, katere stolpce imamo v tabeli >>> df. columns Index ([ ’MESEC ’, ’SEKTOR ’, ’Bruto pla a Pla a za mesec [EUR]’, ’Neto pla a Pla a za mesec [EUR]’], dtype =’object ’) Na prvi poglej izgleda, da smo tabelo zdaj uspeöno uvozili. Stolpce lahko tudi preimenujemo v kaj krajöega: >>> df. columns = [’mesec ’, ’sektor ’, ’bruto ’, ’neto ’] 16.3 Indeksiranje tabel 197 Zdaj pa izpiöimo prvih pet vrstic tabele: >>> df. head () mesec sektor bruto neto 0 2014 M01 Javni sektor 1758.50 1151.30 1 2014 M01 Zasebni sektor 1421.34 932.14 2 2014 M02 Javni sektor 1745.63 1136.41 3 2014 M02 Zasebni sektor 1406.47 922.19 4 2014 M03 Javni sektor 1741.44 1133.47 Preverimo lahko tudi strukturo tabele preko atributa shape: >>> df. shape (148 , 4) Naöa tabela ima torej 148 vrstic in 4 stolpce. Do osnovne statistike lahko pridemo preko metode describe >>> df. describe () bruto neto count 148.000000 148.000000 mean 1694.473919 1099.702230 std 217.336086 133.755268 min 1393.050000 915.740000 25% 1471.885000 963.452500 50% 1743.535000 1136.695000 75% 1865.522500 1203.942500 max 2174.570000 1408.770000 Ta nam vrne osnovno statistiko, ampak zgolj za numeri ne stolpce. Teûava je le v tem, da imamo zdaj javni in zasebni sektor zdruûena skupaj. Tudi to bomo reöili v kratkem. 16.3 Indeksiranje tabel Tabele indeksiramo podobno kot sezname, le da tokrat indeksiranje izvajamo po stolpcih. Do stolpca z oznako bruto bi torej lahko priöli takole: >>> df[’bruto ’] 0 1758.50 1 1421.34 2 1745.63 ... 146 2055.48 147 1682.86 Name : bruto , Length : 148 , dtype : float64 198 Poglavje 16 Knjiûnica pandas Lahko bi dostopali tudi do ve stolpcev naenkrat, tako da pri indeksiranju podamo seznam stolpcev: >>> df [[ ’bruto ’,’neto ’]] bruto neto 0 1758.50 1151.30 1 1421.34 932.14 2 1745.63 1136.41 ... 146 2055.48 1327.33 147 1682.86 1098.04 [148 rows x 2 columns ] Kaj pa, e ûelimo priti do dolo enih vrstic? V tem primeru uporabimo metodo loc, ki ji znotraj oglatih oklepajev podamo oznako oziroma index vrstice. Ko smo prej izpisali tabelo, se je pred stolpcem mesec izpisal dodaten stolpec oznak. Ko tabelo preberemo, ima ta stolpec vrednosti od 0 do ötevila vrstic ≠ 1, lahko pa index nastavimo tudi na kaj drugega. Do 0-te vrstice bi torej priöli takole: >>> df.loc [0] mesec 2014 M01 sektor Javni sektor bruto 1758.5 neto 1151.3 Name : 0, dtype : object Metodi loc lahko podamo tudi stolpec ali seznam stolpcev: >>> df.loc [0,’bruto ’] 1758.5 Stolpec oznak vrstic lahko spremenimo tudi na kakönega izmed obstoje ih stolpcev. Naredimo novo tabelo, ki bo imela za index kar stolpec mesec: >>> df2 = df. set_index (’mesec ’) >>> df2. head () sektor bruto neto mesec 2014 M01 Javni sektor 1758.50 1151.30 2014 M01 Zasebni sektor 1421.34 932.14 2014 M02 Javni sektor 1745.63 1136.41 2014 M02 Zasebni sektor 1406.47 922.19 2014 M03 Javni sektor 1741.44 1133.47 Zdaj bomo metodi loc podali kar mesec, ki nas zanima: >>> df2.loc[’2018 M01 ’] 16.4 Filtriranje vrednosti 199 sektor bruto neto mesec 2018 M01 Javni sektor 1936.41 1246.69 2018 M01 Zasebni sektor 1526.94 997.06 Zadnji na in indeksiranja, ki je neodvisen od oznak vrstic in stolpcev, uporablja metodo iloc. Tej podamo ötevilko (indeks) vrstice lahko pa tudi stolpca. Podobno kot pri indeksiranju strukture ndarray. Takole: >>> df. iloc [1] mesec 2014 M01 sektor Zasebni sektor bruto 1421.34 neto 932.14 Name : 1, dtype : object >>> df. iloc [1 ,3] 932.14 Seveda lahko s to metodo delamo tudi rezine. e nas zanimajo npr vse vrstice, stolpca ötevilka 2, bi to napisali takole: >>> df. iloc [: ,2] 0 1758.50 1 1421.34 2 1745.63 ... 146 2055.48 147 1682.86 Name : bruto , Length : 148 , dtype : float64 16.4 Filtriranje vrednosti Vrednosti lahko filtriramo na podoben na in kot pri uporabi strukture ndarray. Lahko bi npr. pogledali samo tiste vrstice, ki pripadajo javnemu sektorju: >>> df[’sektor ’] == ’Javni sektor ’ 0 True 1 False 2 True ... 146 True 147 False Name : sektor , Length : 148 , dtype : bool Tako dobljen rezultat primerjanja lahko zdaj uporabimo pri indeksiranju: 200 Poglavje 16 Knjiûnica pandas >>> df[df[’sektor ’] == ’Javni sektor ’] mesec sektor bruto neto 0 2014 M01 Javni sektor 1758.50 1151.30 2 2014 M02 Javni sektor 1745.63 1136.41 4 2014 M03 Javni sektor 1741.44 1133.47 ... 144 2020 M01 Javni sektor 2096.96 1351.52 146 2020 M02 Javni sektor 2055.48 1327.33 [74 rows x 4 columns ] e bi nas zanimala statistika po sektorjih, lahko najprej iz tabele izluö imo posamezen sektor, potem pa nad tem pokli emo metodo describe: >>> df[df[’sektor ’] == ’Javni sektor ’]. describe () bruto neto count 74.000000 74.000000 mean 1886.850676 1218.003243 std 106.509594 63.695605 min 1741.440000 1133.470000 25% 1794.557500 1161.612500 50% 1866.195000 1204.535000 75% 1944.307500 1249.582500 max 2174.570000 1408.770000 >>> df[df[’sektor ’] == ’Zasebni sektor ’]. describe () bruto neto count 74.000000 74.000000 mean 1502.097162 981.401216 std 93.494044 59.959989 min 1393.050000 915.740000 25% 1421.515000 931.450000 50% 1471.790000 962.165000 75% 1569.237500 1018.832500 max 1790.520000 1177.510000 16.5 Risanje grafov Tudi risanje grafov postane zelo enostavno. Pokli emo lahko kar metodo plot, ki pripada tabeli dataframe: >>> df. plot () 16.5 Risanje grafov 201 Da lahko tak graf prikaûemo, moramo uvoziti öe knjiûnico Matplotlib in poklicati funkcijo show: >>> import matplotlib . pyplot as plt >>> plt. show () Lahko tudi eksplicitno zahtevamo, kaj naj se prikaûe na osi x, kaj pa na y: df.plot (x=’mesec ’, y=[ ’neto ’,’bruto ’]) plt. show () Tako dobljen graf prikazuje slika 16.1. Problem dobljenega grafa je, da z isto linijo Slika 16.1 rtni graf nad celotno tabelo. prikazuje javni in zasebni sektor skupaj. Sektorja moramo torej lo iti. To lahko naredimo kar s filtriranjem: df_javni = df[df[’sektor ’]== ’Javni sektor ’] df_zasebni = df[df[’sektor ’]== ’Zasebni sektor ’] Zdaj izriöemo oba sektorja. e ju ho emo narisati na skupnem grafu, oziroma isti osi (angl. axis), moramo to eksplicitno podati. Najprej pridobimo trenutno os preko funkcije gca (angl. get current axis): 202 Poglavje 16 Knjiûnica pandas ax = plt.gca () Potem os podamo pri risanju, skupaj z ostalimi argumenti. Graf lahko dopolnimo öe z legendo, oznakami itd. df_javni . plot (x=’mesec ’, y=[ ’neto ’,’bruto ’], ax = ax) # podamo os ax df_zasebni . plot (x=’mesec ’, y=[ ’neto ’,’bruto ’], ax = ax) # podamo isto os ax plt. legend ([ ’Javni sektor ( neto )’, ’Javni sektor ( bruto )’, ’Zasebni sektor (neto )’, ’Zasebni sektor ( bruto )’]) plt. ylabel (’Znesek [EUR]’) plt. show () Tako dobljen graf prikazuje slika 16.2. Graf bi lahko na enostaven na in spremenili Slika 16.2 rtni graf z lo enim izrisom za javni in zasebni sektor. 16.6 Izvoz podatkov 203 v kaköen drug tip, tako da bi nastavili izbirni argument kind na kaköno drugo vrednost, npr. ’bar’ za stolp ni diagram ali pa ’box’ za izris s kvartili. Graf bi lahko narisali tudi tako, da bi podatke iz strukture dataframe pretvorili npr. v strukturo ndarray z uporabo metode values (metoda values zavrûe imena stolpcev in kot matriko tipa ndarray vrne vsebino tabele). Do matrike bruto in neto pla javnega in zasebnega sektorja, bi lahko npr. priöli takole: >>> J = df_javni [[ ’bruto ’,’neto ’]]. values >>> Z = df_zasebni [[ ’bruto ’,’neto ’]]. values Naenkrat lahko nariöemo ve grafov tudi tako, da funkciji plot preko vmesnika matplotlib.pyplot podamo kar matriko. Funkcija bo za vsak stolpec matrike izrisala svoj graf. >>> plt. plot (J) >>> plt. plot (Z) >>> plt. show () 16.6 Izvoz podatkov Podatke lahko iz tabel dataframe tudi enostavno izvozimo. e bi npr. ûeleli podatke za javni sektor izvoziti v datoteko CSV, bi to lahko naredili z uporabo metode to_csv. df_javni . to_csv (’place_javni .csv ’, index = False ) Opcijski argument index smo nastavili na vrednost False, saj ponavadi stolpca z indeksi ne ûelimo izvaûati (v asih pa). Podatke bi lahko izvozili tudi v Excelovo datoteko, in sicer z metodo to_excel, ki deluje zelo podobno kot metoda za izvoz v datoteke CSV: df_javni . to_excel (’place_javni . xlsx ’, index = False ) Na podoben na in lahko Excelovo datoteko tudi uvozimo. Tokrat uporabimo funkcijo read_excel: df_javni2 = pd. read_excel (’place_javni .xlsx ’) 17 Okolje Jupyter 17.1 Interaktivni zvezki Okolje Jupyter predstavlja alternativo okolju IDLE, ki pa uporablja tudi nekoliko druga en zapis programov. Programe namre zapisujemo v tako imenovane in-teraktivne zvezke (angl. IPython Notebooks), s kon nico ipynb. Preden si okolje podrobneje pogledamo, ga moramo namestiti. Spet uporabimo orodje pip: > pip install jupyter Zdaj lahko okolje jupyter zaûenemo, tako da se v ukazni vrstici naöega operacijskega sistema premaknemo v mapo, kjer imamo shranjene datoteke, s katerimi bomo delali, in zaûenemo ukaz: > jupyter notebook S tem smo pognali streûnik okolja Jupyter (angl. Jupyter server), s katerim se poveûemo preko spletnega brskalnika, ki se po izvedbi zgornjega ukaza prav tako avtomatsko zaûene. 17.2 Celice, tipi celic in njihovo poganjanje Najbolje je, da delovanje okolja Jupyter poskusimo kar na ûivem zgledu. Zgled s pla ami, ki smo ga naredili v prejönjem poglavju, je v obliki Juypter zvezka na voljo na povezavi. Prenesimo ga na svoj ra unalnik in shranimo v mapo, iz katere smo pognali Juypter. Zdaj bi morali datoteko z imenom place.ipynb videti v za etnem oknu okolja Jupyter. Odprimo jo s klikom nanjo. Vidimo, da je zvezek sestavljen iz dveh tipov celic. Prvi tip celic nudi razlago. Zapisane so v jeziku Markdown, ki predstavlja relativno preprost ozna evalni jezik oziroma jezik za oblikovanje besedila. e posamezno celico dvakrat poklikamo, lahko vidimo njeno izvorno kodo. e ho emo celico spet pretvoriti v kon no obliko, jo poûenemo. To lahko naredimo s kombinacijo tipk Ctrl + Enter (poûeni celico) oziroma Shift + Enter (poûeni celico in sko i na naslednjo). Drugi tip celic vsebuje kodo v jeziku Python. Te celice so ozna ene z oznako In. Poganjamo jih na enak na in kot celice tipa Markdown. Ko dolo eno celico 205 206 Poglavje 17 Okolje Jupyter poûenemo, se pod njo pojavi njena izhodna (Out) celica, ki prikazuje rezultat njene izvedbe. Celice si med seboj delijo imenski prostor, kar pomeni, da lahko do spremenljivk, ki smo jih definirali v posamezni celici, dostopamo tudi iz ostalih celic. V zvezke lahko dodajamo nove celice, celice briöemo, kopiramo in spreminjamo tipe. Podrobneje v razlago okolja Jupyter ne bomo öli, saj je zelo intuitivno za uporabo, zahteva pa nekaj vaje. Tako kot zahteva vajo in trening tudi programiranje samo. Lotimo se ga... Document Outline Uvod Racunalništvo Zakaj se uciti programiranja Racunalništvo in kemija (in druge vede) Kaj je programiranje? Zakaj Python? Kaj me caka na koncu knjige? Nekaj navodil za branje knjige Spoznavanje z okoljem Izbira in namestitev okolja Okolje IDLE Ukazna vrstica Podatkovni tipi Funkcije Spremenljivke Pisanje programov Funkcija print Funkcija input Pretvarjanje med podatkovnimi tipi Pisanje komentarjev Pogojni stavek Zakaj pogojni stavki? Osnovna oblika stavka if Kaj je pogoj? Primerjalni operatorji in podatkovni tip bool Operatorja vsebovanosti Združevanje rezultatov primerjanja Veja else Veja elif in gnezdenje stavkov if Zanka while Kaj so zanke? Zanka while Štetje z zanko while Iskanje najvecjega skupnega delitelja Stavek += Neskoncna zanka Stavek break Veja else Seznami in metode Sekvencni podatkovni tipi Kaj so seznami? Indeksiranje seznamov Operatorji nad seznami Spreminjanje in brisanje elementov seznama Vgrajene funkcije nad seznami Metode Dodajanje elementov Branje seznamov iz ukazne vrstice Sortiranje seznamov Seznami seznamov Generiranje seznamov s funkcijo range Rezine Indeksiranje nizov Sprehajanje cez sezname Zanka for Sprehajanje cez sezname z zanko for Sprehajanje s funkcijo range in sprehajanje cez indekse Sprehajanje cez elemente ali cez indekse? Spreminjanje elementov seznama z zanko for Zanka for ali zanka while? Stavek break Veja else Gnezdenje zank Izbirni argumenti funkcij in izbirni argumenti funkcije print Uporaba in pisanje funkcij Kaj so funkcije in zakaj so uporabne? Kako definiramo funkcijo? Globalni imenski prostor Kaj se zgodi ob klicu funkcije in lokalni imenski prostor Vsaka funkcija vraca rezultat Izbirni argumenti Uporaba in pisanje modulov Kaj so moduli? Uporaba modulov Definicija in uporaba lastnih modulov Namešcanje novih modulov Spremenljivost podatkovnih tipov in terke Kaj je spremenljivost? Kaj se zgodi ob prirejanju spremenljivk? Kaj se zgodi ob spreminjanju vrednosti spremenljivk? Ali funkcije spreminjajo vrednosti svojim argumentom? Terke Uporaba terk Seznami terk in razpakiranje elementov terk Pakiranje seznamov v sezname terk Zahteva po nespremenljivosti Slovarji Zakaj slovarji? Kako uporabljamo slovarje? Iskanje vrednosti Dodajanje in spreminjanje vrednosti Brisanje vrednosti Kljuci in vrednosti Imenski prostor in slovarji Množice In še množice Uporaba množic Omejitve pri uporabi množic Osnovne operacije nad množicami Presek, unija in razlika Metode množic: dodajanje in brisanje elementov Zgled uporabe množic Oblikovanje nizov Delo z nizi Iskanje podnizov Odstranjevanje in spreminjanje (pod)nizov Razdruževanje in združevanje nizov Odstranjevanje praznega prostora Prilagajanje izpisa in formatiranje nizov Formatiranje nizov in f-Strings Delo z datotekami Zakaj pisati v datoteke in brati iz njih? Kaj je datoteka? Tekstovne datoteke Odpiranje datoteke Branje datoteke Pisanje v datoteko Kodiranje znakov Datoteke CSV Vizualizacija podatkov Knjižnica Matplotlib in njena namestitev Funkciji plot in show Dodajanje oznak Še malo prilagajanja oznak Ostale prilagoditve izrisa Ostali tipi grafov Risanje matematicnih funkcij Knjižnica NumPy in hitro racunanje NumPy in ndarray Aritmeticni operatorji in strutkura ndarray Primerjalni operatorji, indeksiranje s seznami in filtiranje Generiranje strukture ndarray Funkcije nad strukturo ndarray Vec dimenzij Ostale uporabne funkcije Posebne vrednosti Uvažanje vrednosti in omejitve strukture ndarray Omejitve knjižnice NumPy Knjižnica pandas Delo s podatki Knjižnica pandas in dataframe Indeksiranje tabel Filtriranje vrednosti Risanje grafov Izvoz podatkov Okolje Jupyter Interaktivni zvezki Celice, tipi celic in njihovo poganjanje