Uvod v Python

Programiranje različnim ljudem pomeni različne stvari. Pri tem predmetu se bomo učili računalniškega programiranja. Naš cilj bo, da računalniku dopovemo, kako naj namesto nas naredi tisto, česar mi ne zmoremo ali preprosto nočemo.

Računalnik programiramo preko programskih jezikov. Tako kot običajni jeziki imajo tudi ti pravila zapisa in pomena, vendar so običajno bolj preprosti in nedvoumni, saj jih morajo poleg ljudi razumeti tudi računalniki. Nekateri programski jeziki so bliže ljudem, drugi bliže računalnikom, vsi pa slonijo na zelo podobnih idejah. Ko znamo programirati v enem, lahko hitro preklopimo na drugega.

V našem primeru se bomo naučili jezika Python, ki je precej prijazen do začetnikov, hkrati pa tudi izjemno popularen, zato bo pridobljeno znanje takoj uporabno. Pythonov avtor in BDFL (benevolent dictator for life oziroma dobrohotni dosmrtni diktator) je Guido van Rossum.

Delo s Pythonom

S Pythonom se najenostavneje pogovarjamo prek interaktivne konzole, do katere lahko dostopamo na več načinov: neposredno iz ukazne vrstice, z uporabo enostavnega okolja IDLE, ki je priloženo vsaki namestitvi Pythona, ali pa prek kakšnega od naprednejših razvijalskih okolij, na primer PyCharm. Za navodila, kako to storimo, si oglejte video Namestitev Pythona pod Windowsi.

V vseh primerih nas pozdravi približno tak izpis:

Python 3.5.1 (default, Jan 22 2016, 08:54:32)
[GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

Na začetku natančno piše, katero različico Pythona uporabljamo, temu pa sledi nekaj kazalcev na osnovne informacije. Mi se bomo osredotočili na zadnjo vrstico, v kateri nam poziv >>> kaže, da je Python pripravljen na naš vnos.

Previdno

Pozorni bodite, da v prvi vrstici piše Python 3.x.x (zadnji dve številki nista tako ključni). Če tam piše Python 2.x.x, uporabljate Python 2, starejšo, a še vedno precej razširjeno starejšo različico Pythona. Ob prehodu na Python 3 leta 2008 so razvijalci jezika naredili nekaj večjih sprememb, ki so jezik prečistile, vendar so zaradi njih nekateri programi, napisani v Pythonu 2, prenehali delovati. Razvijalci Pythona so upali, da bodo avtorji starih programov prešli na Python 3, vendar se to ni zgodilo dovolj hitro, tako da sta danes še vedno v uporabi obe različici. V tem učbeniku se bomo ukvarjali izključno s Pythonom 3.

Za začetek izračunajmo, koliko je 1 + 1. Vnesemo 1 + 1 ter pritisnimo znak za novo vrstico. Ob tem Python prebere naš vnos, ga izračuna in izpiše rezultat.

>>> 1 + 1
2

Števila in aritmetične operacije

Poleg seštevanja so nam na voljo tudi ostale osnovne računske operacije: - za odštevanje, * za množenje in ** za potenciranje. Za deljenje Python pozna dve operaciji: običajno deljenje / in pa celoštevilsko deljenje //, ki zavrže morebitni ostanek. Če želimo izračunati samo ostanek, uporabimo %. Prioriteta operatorjev je določena tako kot običajno: najtesneje veže potenciranje, nato množenje in deljenji, nazadnje pa seštevanje in odštevanje. Če želimo vrstni red spremeniti, uporabimo običajne oklepaje. Še to: da je koda bolj berljiva, damo na vsaki strani operatorja po en presledek.

>>> (1 + 5) * (9 - 2)
42
>>> 123 / 10
12.3
>>> 123 // 10
12
>>> 123 % 10
3
>>> 123 ** (45 + 67)
1173201153236117392747457141184065170953567764283837482787268119007036898684512512556572577156186549602764788041495818311329933349581701014867937205332087819177539156963702612817234021747525564287508352993790061063457990401206082438721

Vidimo, da velika števila Pythonu ne povzročajo velikih težav.

Uporaba vgrajenih funkcij

Na voljo so tudi osnovne funkcije, kot na primer max in min za izračun maksimuma in minimuma.

>>> max(3, 6)
6
>>> min(12, -5)
-5
>>> max(min(10, 20), 30 // 2)
15

Matematične funkcije so na voljo v ločeni knjižnici math. Do njih lahko dostopamo na dva osnovna načina:

  1. Knjižnico uvozimo s stavkom import math, nato pa do funkcij in konstant dostopamo tako, da dodamo math. pred vsako ime:

    >>> import math
    >>> math.sqrt(2) / 2
    0.7071067811865476
    >>> math.sin(math.pi / 4)
    0.7071067811865475
    >>> math.sin(math.pi)
    1.2246467991473532e-16
    
V zadnjem ukazu nismo dobili pričakovanega odgovora 0. Računalnik namreč ne dela s čisto pravimi realnimi števili, temveč z njihovimi približki, ki jim pravimo števila s plavajočo vejico. Za ta števila običajno najprej zapišemo decimalke (ki jim pravimo mantisa), nato pa še eksponent. Število, ki smo ga dobili, je tako enako približno \(1{,}22 \cdot 10^{-16}\), saj e-16 pomeni \(10^{-16}\). Na primer 3.2445e2 pa označuje število \(324{,}45 = 3{,}2445 \cdot 10^2\)).
  1. Iz knjižnice s stavkom from math import ... uvozimo posamezne vrednosti, nato pa do njih dostopamo direktno:

    >>> from math import sqrt, sin, pi
    >>> sqrt(2) / 2
    0.7071067811865476
    >>> sin(pi / 4)
    0.7071067811865475
    

Obstaja tudi tretji način, ko iz knjižnice s stavkom from math import * uvozimo vse naštete vrednosti, vendar je odsvetovan, ker potem nikoli ne vemo, kaj vse smo uvozili.

Napake

Pri programiranju dostikrat naredimo tudi kakšno napako. Načeloma lahko ločimo tri vrste napak:

  1. Sintaktične napake, v katerih program napišemo drugače, kot določajo pravila. Na primer, če argumente funkcije ločimo s podpičjem namesto z vejico, ali pa če narobe pišemo oklepaje:

    >>> max(2; 4)
    Traceback (most recent call last):
      ...
        max(2; 4)
             ^
    SyntaxError: invalid syntax
    
    >>> max(2, 4))
    Traceback (most recent call last):
      ...
        max(2, 4))
                 ^
    SyntaxError: invalid syntax
    

    Na take napake nas Python opozori, še preden začne z izvajanjem programa, zato jih ne moremo zgrešiti.

  2. Napake ob izvajanju, v katerih program napišemo sintaktično pravilno, vendar uporabimo neveljavno operacijo:

    >>> 1 / 0
    Traceback (most recent call last):
      ...
    ZeroDivisionError: division by zero
    
    >>> mix(3, 5)
    Traceback (most recent call last):
      ...
    NameError: name 'mix' is not defined
    

    Opozorila o napakah si bomo še ogledali bolj podrobno, zaenkrat pa si zapomnimo le, da je ključna informacija o napaki v zadnji vrstici opozorila. V prvem primeru je bila napaka deljenje z 0, v drugem pa to, da ime mix ni definirano.

    Take napake se pojavijo šele ob izvajanju programa, in izvajanje tudi prekinejo. To zna biti nerodno, kadar gre za kritično pomemben program (npr. za nadzor jedrskega reaktorja) ali pa kadar s tem izgubimo veliko dela (recimo, da se računalnik po 10-urnem izračunu ustavi, preden izpiše rezultat). Lahko se tudi zgodi, da do napak pride šele ob kakšnih robnih pogojih, zato jih lahko precej časa sploh ne opazimo. Vseeno pa je njihova prednost vsaj ta, da jih opazimo, kadar se zgodijo (kot bomo videli, jih lahko včasih tudi naknadno rešimo).

  3. Vsebinske napake, pri katerih program navidez deluje brez težav, vendar izračuna napačen odgovor, ker smo mu dali napačna navodila. Recimo, da želimo izračunati razdaljo med točkama (2, 3) in (5, 7):

    >>> ((2 - 5) ** 2 + (3 - 7) ** 2) ** 1 / 2
    12.5
    

    Program smo napisali brez sintaktičnih napak in izvajanje je uspešno vrnilo rezultat, ki pa je žal napačen, ker nismo potencirali na 1/2, temveč potencirali na 1 in delili z 2, saj ima potenciranje prednost pred deljenjem. Take napake so še posebej zlobne, ker jih lahko precej dolgo časa ne opazimo. Znan primer te napake je Mars Climate Orbiter, ki je po devetih mesecih potovanja proti Marsu prehitro vstopil v atmosfero in razpadel. Vzrok je bil v tem, da je del kode delal s SI merskimi enotami, del kode pa z imperialnimi. Škode je bilo za 300 milijonov dolarjev.

Prirejanje vrednosti spremenljivkam

Izračunane vrednosti si lahko shranimo tudi v spremenljivke, ki jih potem uporabljamo v kasnejših izračunih. Za to uporabimo prireditveni stavek oblike

ime_spremenljivke = vrednost_ki_jo_zelimo_shraniti

na primer:

>>> x = 3 + 3
>>> 7 * x
42
>>> y = x + 8
>>> y
14

Če želimo, lahko hkrati priredimo tudi več vrednosti:

>>> x, y = 10, 15
>>> x + y
25
>>> z = y - x
>>> z
5

Vrednost spremenljivke lahko tudi povozimo z novo vrednostjo, vendar to na preostale spremenljivke ne vpliva, saj se vedno shrani tista vrednost, ki smo jo podali v prireditvenem stavku.

>>> x = 10
>>> y = x + 3
>>> y
13
>>> x = 25
>>> y
13

Ko smo v x shranili novo vrednost, se vrednost y ni spremenila, saj je prireditveni stavek y = x + 3 najprej izračunal vrednost desne strani, torej 13, in v y shranil samo število.

Shranjevanje programov v datoteke

Interaktivna konzola je uporabna za krajše programe, daljše pa raje shranimo v datoteko. S tem preprečimo, da izgubili vse svoje delo, pa tudi lažje popravljamo napake, saj nam ni treba vsega ponovno vnašati. Pythonove programe shranjujemo v običajne tekstovne datoteke, kar pomeni, da jih lahko odpremo s katerim koli urejevalnikom besedila, na primer Notepad, Notepad++, Emacs ali Vi. Pythonovim datotekam običajno damo končnico .py. Za natančnejša navodila si oglejte video Nalaganje programov iz datotek.

Za primer daljšega programa si oglejmo Fermijevo oceno števila učiteljev matematike v slovenskih osnovnih šolah. Sledeče stavke vpišite v datoteko fermi.py:

stevilo_slovencev = 2000000
pricakovana_zivljenska_doba = 75
velikost_generacije = stevilo_slovencev / pricakovana_zivljenska_doba
stevilo_osnovnosolcev = 9 * velikost_generacije
stevilo_razredov = stevilo_osnovnosolcev / 25
stevilo_ur_matematike_na_teden = 4.5 * stevilo_razredov
stevilo_uciteljev_matematike = stevilo_ur_matematike_na_teden / 20

Ko datoteko naložimo, lahko vidimo, da bi moralo v Sloveniji biti približno 2000 učiteljev matematike:

>>> stevilo_uciteljev_matematike
2160.0

Pisanje preglednih programov

Vidimo, da lahko imena spremenljivk vsebujejo več kot eno črko, česar smo navajeni v matematiki. V programiranju je zelo pomembno, da so imena čimbolj opisna, saj tako hitreje razumemo, kaj počne program. Računalnik bi razumel tudi sledeč program in izračunal enak odgovor, vendar vidimo, da smiselna imena in presledki kodo naredijo veliko bolj berljivo.

s,z=2000000,75
g=s/z
o=9*g
r=o/25
m=4.5*r
u=m/20
>>> u
2160.0

Zato se bomo držali sledečih pravil:

  • Na vsaki strani dvomestne operacije (=, +, **, …) pišemo presledek.
  • Za ločili (na primer ,) pišemo presledek, pred njimi pa ne.
  • Spremenljivkam dajemo opisna imena, ki jih pišemo z malimi črkami. Posamezne besede ločimo z znakom _.

Funkcije

Ploščino trikotnika s stranicami \(a, b, c\) lahko izračunamo po Heronovi formuli

\[\sqrt{s (s - a) (s - b) (s - c)}\]

kjer je \(s = (a + b + c) / 2\). Ploščino trikotnika s stranicami 4, 13 in 15 bi v Pythonu lahko torej izračunali s programom:

import math

a, b, c = 4, 13, 15
s = (a + b + c) / 2
ploscina = math.sqrt(s * (s - a) * (s - b) * (s - c))

Tedaj je

>>> ploscina
24.0

Kako pa bi izračunali površino tetraedra, ki ima za lica štiri trikotnike? Načeloma bi lahko pisali:

a, b, c, d, e, f = 896, 1073, 1073, 990, 1073, 1073
s_abc = (a + b + c) / 2
p_abc = math.sqrt(s_abc * (s_abc - a) * (s_abc - b) * (s_abc - c))
s_aef = (a + e + f) / 2
p_aef = math.sqrt(s_aef * (s_aef - a) * (s_aef - e) * (s_aef - f))
s_cde = (c + d + e) / 2
p_cde = math.sqrt(s_cde * (s_cde - c) * (s_cde - d) * (s_cde - f))
s_bdf = (b + d + f) / 2
p_bdf = math.sqrt(s_bdf * (s_bdf - b) * (s_bdf - d) * (s_bdf - f))
povrsina = p_abc + p_aef + p_bdf + p_cde

Kot vidimo, to ni najbolj pregledno. V taki kodi z veliko verjetnostjo naredimo kakšno napako. Bolje je, da uporabimo funkcije. Že prej smo uporabili nekaj vgrajenih funkcij, na primer min in max. Python pa nam omogoča, da si funkcije definiramo tudi sami.

Definicije lastnih funkcij

Definicija funkcije, ki izračuna ploščino trikotnika, je sledeča:

import math

def ploscina_trikotnika(a, b, c):
    s = (a + b + c) / 2
    return math.sqrt(s * (s - a) * (s - b) * (s - c))

Oglejmo si njene sestavne dele. Vsaka definicija funkcije se začne s ključno besedo def, ki ji sledi ime funkcije, v našem primeru ploscina_trikotnika, tej pa v oklepajih našteti argumenti, ki jih funkcija sprejme. Funkcije lahko sprejmejo različno število argumentov. Naša sprejme tri argumente, ki jih bomo shranili v spremenljivke a, b in c.

Nato sledi telo funkcije, torej ukazi, ki naj se izvedejo, ko funkcijo pokličemo. Vsako vrstico telesa funkcije moramo zamakniti za štiri presledke, da se jasno vidi, kaj vse funkcija obsega. Prvo vrstico telesa smo že videli, v drugi pa z ukazom return povemo, katero vrednost naj vrne funkcija. Tako definirano funkcijo potem kličemo na enak način kot vgrajene funkcije.

>>> ploscina_trikotnika(4, 13, 15)
24.0

S pomočjo funkcije ploscina_trikotnika lahko tudi na veliko bolj pregleden način zapišemo funkcijo za izračun površine tetraedra:

def povrsina_tetraedra(a, b, c, d, e, f):
    p_abc = ploscina_trikotnika(a, b, c)
    p_aef = ploscina_trikotnika(a, e, f)
    p_bdf = ploscina_trikotnika(b, d, f)
    p_cde = ploscina_trikotnika(c, d, e)
    return p_abc + p_aef + p_bdf + p_cde
>>> povrsina_tetraedra(896, 1073, 1073, 990, 1073, 1073)
1816080.0

Stavek return

Tako kot drugje v Pythonu, se tudi stavki v telesu funkcije izvajajo od prvega proti zadnjemu. Ko dosežemo stavek return, funkcija vrne vrednost danega izraza ter zaključi z izvajanjem. Tako tudi funkcija

def f(x):
    return x ** 2
    return 1000

vrne kvadrat števila x in ne števila 1000, saj se izvajanje ustavi ob prvem stavku return, zato do drugega sploh ne pride. Če stavka return ne napišemo, funkcija vrne posebno vrednost None, ki označuje manjkajočo vrednost. Pozorno se ji bomo posvetili kasneje, zaenkrat pa jo omenimo le zato, da bomo znali razumeti spodnjo (precej pogosto) napako:

def g(x):
    x ** 2
>>> 2 * g(10)
Traceback (most recent call last):
  ...
TypeError: unsupported operand type(s) for *: 'int' and 'NoneType'

Pričakovali bi, da bo rezultat klica 2 * g(10) enak 200. Toda ker smo v funkciji g pozabili na return, je funkcija vrnila vrednost None. To lahko razberemo iz opozorila, v katerem približno piše, da operacije * ne moremo uporabiti na celem številu in vrednosti None. Vsakič, ko dobite opozorilo TypeError, v katerem se pojavlja NoneType, posumite na to, da nekje manjka stavek return.

Lokalnost spremenljivk

Argumenti funkcije in spremenljivke, ki jih definiramo v telesu funkcije, se izven funkcije ne vidijo. Pravimo, da so lokalne. Namen tega je, da funkcije ne motijo ena druge s spremenljivkami, ki jih uporabljajo. Na primer, če definiramo

def f(x):
    y = 3 * x
    return y

tedaj tudi po klicu funkcije f ne x ne y ne bosta definirana:

>>> f(4)
12
>>> x
Traceback (most recent call last):
  ...
NameError: name 'x' is not defined
>>> y
Traceback (most recent call last):
  ...
NameError: name 'y' is not defined

Če pa je y na primer že definiran drugje, pa ga klic funkcije f ne zmoti:

>>> y = 10
>>> f(4)
12
>>> y
10

Logične vrednosti

Poleg števil Python pozna tudi logični vrednosti True in False, ki označujeta resnico in neresnico. Logične vrednosti ponavadi dobimo kot rezultat primerjav, kot so enakost ==, neenakost != ali urejenostne relacije <, >, <=, >=, ter prek logičnih operacij and, or in not.

>>> 1 + 1 == 3
False
>>> 3 != 2
True
>>> True and False
False
>>> not (5 == 10)
True
>>> 3 < 5 or 10 > 20
True

Pogojni stavek

Logične vrednosti uporabimo v pogojnih stavkih (oziroma stavkih ``if``) oblike

if pogoj:
    # stavki, ki jih izvedemo,
    # ko pogoj drži
else:
    # stavki, ki jih izvedemo,
    # ko pogoj ne drži

Ključnima besedama if/else in pripadajočim stavkom pravimo tudi veji pogojnega stavka. Stavke v obeh vejah moramo zamakniti za štiri presledke tako kot v funkcijah.

Na primer, če izvedemo program

x = 5
if x < 10:
    y = 2 * x
else:
    y = 3 * x - 1
x = y + 7

se bo izvedla veja if, zato bo x na koncu enak 17, y pa 10. V primeru, da bi bila začetna vrednost x = 12, pa bi se izvedla veja else in vrednost x bi na koncu bila 42, vrednost y pa 35.

Pogojne stavke lahko pišemo tudi v funkcijah. Na primer, funkcijo, ki računa absolutno vrednost, lahko s pomočjo pogojnega stavka napišemo kot:

def absolutna_vrednost(x):
    if x >= 0:
        return x
    else:
        return -x
>>> absolutna_vrednost(-5)
5
>>> absolutna_vrednost(3)
3

Razširjeni pogojni stavek

Če bi želeli vrniti predznak števila, pa moramo ločiti tri primere: negativno število, nič in pozitivno število. To lahko storimo kot:

def predznak(x):
    if x < 0:
        return -1
    else:
        if x == 0:
            return 0
        else:
            return 1

Zgornji pogojni stavek je malo nerodno zapisan. Ker se nam bo dostikrat zgodilo, da se ne bomo odločali le med dvema primeroma, temveč med večimi, nam Python omogoča splošnejše pogojne stavke oblike:

if pogoj1:
    # stavki, ki jih izvedemo,
    # ko pogoj1 drži
elif pogoj2:
    # stavki, ki jih izvedemo,
    # ko pogoj1 ne drži, ampak drži pogoj2
elif pogoj3:
    # stavki, ki jih izvedemo,
    # ko tudi pogoj2 ne drži, ampak drži pogoj3
else:
    # stavki, ki jih izvedemo,
    # ko noben od pogojev ne drži

Beseda elif je okrajšava za else-if. Funkcijo za izračun predznaka bi lepše zapisali kot

def predznak(x):
    if x < 0:
        return -1
    elif x == 0:
        return 0
    else:
        return 1