Osnove Programiranja Skripta

Osnove programiranja - programski jezik Python

Sadržaj

Ovo su beleške sa vežbi.

  • Beleške predstavljaju pojašnjenja i rešenja zadataka na predmetu Osnove Programiranja.
  • Tekstove zadataka i materijale sa predavanja možete pronaći na enastav-i.

1 Upoznavanje sa python okruženjem

Kako da pokrenem Python?

  • Otvorite terminal — može korišćenjem kombinacije tastera Ctrl + Alt + t a može i klikom na ikonicu.
  • U terminalu kucate python3. Dobićete potpis Python-a (Python runtime-a koji trenutno radi na računaru).

    Python 3.4.3 (default, Sep 14 2016, 12:36:27)
    [GCC 4.8.4] on linux
    Type "help", "copyright", "credits" or "license" for more information.
    >>>
    

    Ono što želite da kažete Python-u ide posle znaka >>>.

  • Na ovom kursu ćemo koristiti Python 3.

1.1 Komunikacija sa Python-om

U Python-u su 2 i 2 4.

2 + 2
4

Želimo da ispišemo nešto u konzolu?

print("Zdravo svima!")
Zdravo svima!

Želimo da napišemo funkciju?

# coding: utf-8

def say_hello():
    print("Ćao!")

Želimo da pozovemo funkciju?

say_hello()
Ćao!
  • Napuštanje Python interpretera u terminalu

    exit()
    

1.2 Pokratanje Python programa

Kada želimo da pokrenemo program koji se nalazi na putanji /home/vas_username/Desktop/ime_fajla.py:

python3 /home/vas_username/Desktop/ime_fajla.py

1.3 Terminal

  • Pri otvaranju terminala uvek se nalazimo u home direktorijumu korisnika koji je ulogovan na sistem.
  • Želim da vidim gde se trenutno nalazim

    pwd
    
  • Želimo da idemo u neki direktorijum

    cd putanja/do/vaseg/direktorijuma
    
  • Vraćanje u home direktorijum.

    cd ~
    
  • Šta imam u direktorijumu?

    ls
    
  • Želim da znam koje postoje opcije neke komande

    man naziv_komande
    # man ls
    

1.4 primer chaos.py

Imamo primer jednog Python programa:

# File: chaos.py
# A simple program illustrating chaotic behavior.

def main():
    print("This program illustrates a chaotic function")
    x = eval(str(input("Enter a number between 0 and 1: ")))
    for i in range(10):
        x = 3.9 * x * (1 - x)
        print(x)
        main()
  • Neki standardni idiomi petlji:

    # nije mi bitno koliko puta se izvršava petlja
    for _ in range(3):
        print("Hello!")
    
    # ista stvar samo sa while
    i = 0
    while i < 3:
        i += 1
        print("Hello!")
    
    ... ... Hello!
    Hello!
    Hello!
    ... >>> ... ... ... Hello!
    Hello!
    Hello!
    

    Koji je čitljiviji način?

  • Promeniti ispis chaos.py tako da ispisuje u tabeli sa 2 kolone.

    def chaos():
        print("This program illustrates a chaotic function")
        first = eval(str(input("Enter first number between 0 and 1: ")))
        second = eval(str(input("Enter second second between 0 and 1: ")))
        for i in range(10):
            x = 3.9 * first * (1 - first)
            y = 3.9 * second * (1 - second)
            print(str(x) + 4 * " " + "|" + 4 * " " + str(y))
    
    # za 0.2 i 0.3
      chaos()
    
    This program illustrates a chaotic function
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    0.6240000000000001    |    0.819
    

2 Python programi

2.1 convert.py

Program sa uvodnom porukom:

# convert.py
#     A program to convert Celsius temps to Fahrenheit
# by: Susan Computewell

def fahrenheit():
    print("Dobrodošli u program koji konvertuje Farenhajte u Celzijuse")
    celsius = eval(str(input("What is the Celsius temperature? ")))
    fahrenheit = 9/5 * celsius + 32
    print("The temperature is " + str(fahrenheit) + " degrees Fahrenheit.")

Pozivanje funkcije:

fahrenheit()
Dobrodošli u program koji konvertuje Farenhajte u Celzijuse
What is the Celsius temperature? 12
The temperature is 53.6 degrees Fahrenheit.
  • convert.py takav da ispisuje konverziju u tabeli od 10 do 100 stepeni Celzijusa.
def fahrenheit_table():
    print("celzius" + "\t" + "|" + "\t" + "farenhajt")
    print(30 * "-")
    for c in range(0, 100, 10):
        f = 9/5 * c + 32
        print(str(c) + "\t" + "|" + "\t" + str(f))

Poziv:

fahrenheit_table()
celzius	|	farenhajt
------------------------------
0	|	32.0
10	|	50.0
20	|	68.0
30	|	86.0
40	|	104.0
50	|	122.0
60	|	140.0
70	|	158.0
80	|	176.0
90	|	194.0

2.2 Eval funkcija Python interpretera

  • eval(...) je funkcija kojom praktično kažete:

"Python-e, ovaj tekst koji ti dajem izvrši kao program."

broj = eval('10')
broj
n_torka = eval('1, 2, 3')
n_torka
lista = eval('[11, 22, 33]')
lista
10
>>> (1, 2, 3)
>>> [11, 22, 33]

2.3 avg.py

Program koji izračunava srednje vrednosti:

# avg2.py
#   A simple program to average two exam scores
#   Illustrates use of multiple input

def my_avg():
    print("This program computes the average of two exam scores.")
    score1, score2, score3 = eval(
        str(input("Enter three scores separated by a comma: ")))
    average = (score1 + score2 + score3) / 3
    print("The average of the scores is: " + str(average))

Poziv funkcije:

my_avg()
This program computes the average of two exam scores.
The average of the scores is: 11

2.4 futval.py

Program za računanje investicija

# futval.py
#    A program to compute the value of an investment
#    carried 10 years into the future

def futval():
    print("This program calculates the future value")
    print("of a 10-year investment.")
    principal = eval(input("Enter the initial principal: "))
    inflation = eval(input("Enter actual inflation: "))
    for i in range(10):
        principal = principal/(1 + inflation)
    print("The value in 10 years is: " + str(principal))

futval()
... ... >>> ... ... ... ... ... ... ... ... >>> This program calculates the future value
of a 10-year investment.
The value in 10 years is: 7.253815028640569

3 Brojevi

3.1 Različito

4 != 2 ** 2
False

3.2 Math lib

Sva naprednija matematika se nalazi u biblioteci math. Da bismo je koristili moramo je importovati.

  • Trigonometrija radi u Radijanima
import math

math.sin(90)
math.cos(90)
math.sqrt(4)
>>> 0.8939966636005579
-0.4480736161291701
2.0

3.3 range funkcija

  • Vraća lenju sekvencu - objekat range čiji se pojedinačni elementi izračunavaju naknadno po potrebi. O lenjoj evaluaciji možete čitati ovde.
for i in range(3):
    i
... 0
1
2
for i in range(10, 20, 2):
    i
... 10
12
14
16
18
for i in range(10, -10, -3):
    i
... 10
7
4
1
-2
-5
-8

3.4 Prosta primena brojeva

  • Program koji izračunava cenu pice po kvadratnom centimetru za dati poluprečnik i cenu cele pice:

    # import alias
    import math as m
    
    def cena_po_centimetru(r, cena):
        """Izracunava cenu pice po kvadratnom centimetru.
    
        Arguments:
        r -- poluprecnik cele pice u centimetrima
        cena -- cena cele pice u dinarima"""
        # kovertujem argumente iz stringa u realne brojeve
        r = float(r)
        cena = float(cena)
        print("Cena pice po kvadratnom centimetru je: " +
              str(r ** 2 * m.pi / cena) + " din.")
    

    Obratite pažnju na docstring kojim opisujemo šta radi funkcija. Više o tome možete naći ovde.

    Pozivanje funkcije:

    r = eval(input("Unesite poluprecnik pice u centimetrima: "))
    cena = eval(input("Unesite cenu cele pice u dinarima: "))
    
    cena_po_centimetru(r, cena)
    
    ... >>> Cena pice po kvadratnom centimetru je: 12.723450247 din.
    

3.5 Izračunavanje molekularne mase ugljovodonika:

def carbohydrate_mm(carbo, hydro):
    m_C = 12.011
    m_H = 1.0079
    return "Molekularna masa takvog jedinjenja je: " \
        + str(carbo * m_C + hydro * m_H) + "."

br_C = eval(input("Unesite broj ugljenika: "))
br_H = eval(input("Unesite broj vodonika: "))

print(carbohydrate_mm(br_C, br_H))
... ... ... >>> >>> >>> >>> Molekularna masa takvog jedinjenja je: 178.4006.

3.6 Udaljenost munje

def flash_distance(happened, heard):
    sound_speed = 340
    distance = (heard - happened) * sound_speed
    is_dead = True if distance == 0 else False
    output =  "Udaljenost posmatraca od munje je: " + str(distance)
    # ako je udaljenost munje od posmatraca 0, pretpostavimo da se posmatrac
    # nije bas proveo najbolje
    if is_dead:
        return output + "\n I tu je kraj."
    else:
        return output

munja_se_desila = eval(input("Unesite kada se desila munja: "))
covek_je_cuo_munju = eval(input("Unesite kada je covek cuo munju: "))

print(flash_distance(munja_se_desila, covek_je_cuo_munju))
... ... ... ... ... ... ... ... ... ... >>> >>> >>> >>> Udaljenost posmatraca od munje je: 37740
  • Poziv funkcije koji nije dobar za posmatrača…

    print(flash_distance(0, 0))
    
    Udaljenost posmatraca od munje je: 0
     I tu je kraj.
    

3.7 Zbir prvih n prirodnih brojeva

  • Način koji troši vreme. sum realizuje sve brojeve iz range kako bi ih sabrala. To troši vreme.
unos = eval(input("Unesite n: "))

print(sum(range(unos + 1)))
>>> 10
  • Bolji način. Kolikigod broj da prosledimo funkciji first_n ona će se izvršiti momentalno, nema nikakakvog prolaženja - iteracije.

    def first_n(n):
        return int((n * (n + 1))/2)
    
    print("Po formuli: " + str(first_n(10000000)))
    
    ... >>> Po formuli: 50000005000000
    

3.8 Unos sa tastature n brojeva i njihov zbir

  • Način sa for:
broj_brojeva = eval(input("Unesite broj brojeva koje zelite da unesete: "))

suma = 0
for b in range(broj_brojeva):
    suma += eval(input("Unesite " + str(b + 1) + ". broj: "))

print("Zbir je: " + str(suma))
Unesite broj brojeva koje zelite da unesete: 5
Unesite 1. broj: 12
Unesite 2. broj: 34
Unesite 3. broj: 123
Unesite 4. broj: 12
Unesite 5. broj: 45
Zbir je: 226
  • Način sa evaluacijom u tuple:

    def n_inputs():
        br_brojeva = eval(input("Unesite broj brojeva koje želite da sabirate: "))
        unos = eval(input("Unesite te brojeve razdvojene zarezon: "))
        if len(unos) != br_brojeva:
            print("Uneli ste " + str(len(unos)) + " brojeva umesto " + \
                  str(br_brojeva) + " koliko ste rekli da cete uneti.")
        else:
            print("Zbir brojeva je: " + str(sum(unos)))
    
    n_inputs()
    
    Unesite broj brojeva koje želite da sabirate: 3
    Unesite te brojeve razdvojene zarezon: 3424,321421,3213
    Zbir brojeva je: 328058
    ----------------------------------------
    Unesite broj brojeva koje želite da sabirate: 4
    Unesite te brojeve razdvojene zarezon: 1,2,34,565,67
    Uneli ste 5 brojeva umesto 4 koliko ste rekli da cete uneti.
    

4 Rukovanje stringovima

Stringovi i liste se u Pythonu predstavljaju na sličan način, često ono što važi za liste važi i za Stringove.

  • Napraviti novi string od prva 3 karaktera jednog i poslednja 2 karaktera drugog.

    def build_from_begin_end(first_s, second_s):
        return first_s[:3] + second_s[-3:]
    
    f = input("Unesite prvi strng: ")
    s = input("Unesite drugi strng: ")
    
    print(build_from_begin_end(f, s))
    
  • Napraviti akronim

    def acronim(text):
        acronim = ""
        for word in text.split(" "):
            acronim += word[0].upper()
        return acronim
    
    text = input("Unesite tekst: ")
    print(acronim(text))
    

5 Fajlovi

  • Upisati korisničke kredencijale u fajl

    Korišćeni koncepti:

    • with izraz. Služi nam ovde kako ne bismo morali eksplicitno da pišemo fajl.close(). Ova konstrukcija ume sama da otpusti resurs kada je to potrebno — kada se with blok završi.
    • try...except konstrukcija.
      Znači sledeće: pokušaj da uradiš sve što je navedeno u try bloku, ako se pri tome desi neka greška tu stani i uradi ono što je u except bloku.
      Ovde je korišćena kako bismo ispisali na konzolu da je došlo do greške pri čitanju fajla, ako se to desi.
    # coding: utf-8
    
    import os
    
    def save_credentials(uname, passw):
        try:
            with open("credentials.supa_od_kornjače", 'a') as creds:
                creds.write(uname + "|$# ovo je delimiter #$|" + passw + os.linesep)
            print("Uspesno ste upisali u fajl vaše kredencijale, %s." % uname)
        except:
            print("Doslo je do greske pri upisu.")
    
    uname = input("Unesite vas username: ")
    passwd = input("Unesite vas password: ")
    
    save_credentials(uname, passwd)
    
    >>> >>> >>> ... ... ... ... ... ... ... >>> Uspesno ste upisali u fajl vaše kredencijale, Petar.
    
  • [Obratiti pažnju!]
    Fajlovi se u fajl sistemu identifikuju preko URI-ja tj. lokacija u fajl sistemu.
    U kontekstu fajl sistem ime fajla kao pojam NE POSTOJI, postoji samo njegova putanja čiji kraj mi (ljudi) između sebe zovemo "ime fajla".

    Kada navedemo samo ime fajla (kao u primeru iznad credentials.supa_od_kornjače) onda zapravo upućujemo fajl sistem da traži u repozitorijumu u kojem se trenutno izvršava program. Dakle:

    ime.extension == putanja/do/direktorijuma/gde/se/izvrsava/program/ime.extension
    

    putanja/do/direktorijuma/gde/se/izvrsava/program/ se još zove i current directory i u terminalu u kome pokrećete program se može dobiti sa:

    pwd
    
  • Čitanje podataka iz fajla

    # coding: utf-8
    
    def read_credentials(file_name):
        delimiter = "|$# ovo je delimiter #$|"
        with open(file_name, 'r') as f:
            for l in f.readlines():
                print("korisničko ime: " + l.split(delimiter)[0])
                print("lozinka: " + l.split(delimiter)[1])
    
    read_credentials("credentials.supa_od_kornjače")
    
    >>> ... ... ... ... ... ... >>> korisničko ime: sda
    lozinka: dsad
    
    korisničko ime: pera
    lozinka: petrovicNjegos
    

5.1 Program koji kombinuje sadržaj dva fajla i snima ga u treći

import os

def kombinuj(korisnci_f, artikli_f , statistika_f):
    delimiter = "|"
    with open(korisnci_f, 'r') as korisnici, \
         open(artikli_f, 'r') as artikli, \
         open(statistika_f, 'w') as statistika:
        korisnici_lines = korisnici.readlines()
        artikli_lines = artikli.readlines()
        # koliko ima korisnika toliko ima i evidencija o njihovim artiklima
        l = len(korisnici_lines)
        for i in range(l):
            ime = korisnici_lines[i].split(delimiter)[0]

            # nalazim ukupnu cenu artikala iz odgovarajuce linije
            cene_kao_stringovi = artikli_lines[i].split(delimiter)
            ukupna_cena = 0
            for c in cene_kao_stringovi:
                ukupna_cena += int(c)

            # racunam prosecnu cenu
            prosecna_cena = ukupna_cena / len(cene_kao_stringovi)

            # upisujem u statistiku u fajl
            statistika.write(ime + delimiter + str(ukupna_cena) +
                             delimiter + str(prosecna_cena) + os.linesep)

kombinuj("korisnici.txt", "racuni.txt", "statistika.txt")

5.1.1 Isti program, malo pametnije

Korišćeni koncepti:

  • zip funkcija. Ovde nam služi da istovremeno iteriramo kroz linije u oba fajla. Veoma je korisna i često se koristi u Python-u.
    U Python 3 zip funkcija vraća lenji objekat (slično kao range, to je razmatrano ovde):

    # U python 3
    zip([1, 2, 3], ["zec", "macke", "psa"], ["skace", "predu", "laju"])
    
    <zip object at 0x7f6de7201308>
    
    # realizujem zip-ovanu listu tako što joj pristupam u for-u
    for (broj, zivotinja, predikat) in zip([1, 2, 3], \
                                           ["zec", "macke", "psa"], \
                                           ["skace", "predu", "laju"]):
        print(broj, zivotinja, predikat)
    
    ... ... ... ... 1 zec skace
    2 macke predu
    3 psa laju
    

    U Python 2 zip funkcija vraća već izvršenu(evaluiranu) listu (pa je tu jasnije šta zaista radi zip):

    # U python 2
    zip([1,2,3], ["zec", "macke", "psa"], ["skace", "predu", "laju"])
    
    [(1, 'zec', 'skace'), (2, 'macke', 'predu'), (3, 'psa', 'laju')]
    

  • List comprehensions da pretvorimo string-ove u int-ove kako bismo mogli da ih saberemo sa sum.
    List comprehensions je veoma moćan i često korišćen koncept u Python-u.
    Za Python važi - tamo gde može list comprehensions nikada ne koristiti imperativne for petlje.
import os

def kombinuj(korisnci_f, artikli_f , statistika_f):
    delimiter = "|"
    with open(korisnci_f, 'r') as korisnici, \
         open(artikli_f, 'r') as artikli, \
         open(statistika_f, 'w') as statistika:
        for (l_korisnik, l_artikli) in zip(korisnici.readlines(),
                                           artikli.readlines()):
            ime = l_korisnik.split(delimiter)[0]

            # ova konstrukcija se zove list comprehensions
            cene = [int(c) for c in l_artikli.split(delimiter)]

            # nalazim ukupnu cenu artikala iz odgovarajuce linije
            ukupna_cena = sum(cene)

            # racunam prosecnu cenu
            prosecna_cena = ukupna_cena / len(cene)

            # upisujem statistiku u fajl
            # prikaz na tri decimale
            statistika.write(ime + delimiter + '%.3f'%ukupna_cena +
                             delimiter + '%.3f'%prosecna_cena + os.linesep)

kombinuj("korisnici.txt", "racuni.txt", "statistika2.txt")

5.1.2 Dodatak o try...except konstrukciji   za_radoznale

  • Želimo da otvorimo fajl sa putanje. Putanja se prosleđuje kao parametar funkcije tako da korisnik može proslediti neku nepostojeću putanju. Ako se to desi potrebno je obavestiti korisnika da fajl na putanji ne postoji i otvoriti podrazumevani fajl.

    def otvaranje_fajla(file_name):
        try:
            # pokušavam da otvorim fajl čije je ime prosleđeno kao argument
            with open(file_name, 'r') as f:
                return f.readlines()
        except FileNotFoundError:
            # ako je Python "bacio" FileNotFoundError onda uradi ovo ovde
            print("""Fajl koji želite da otvorite ne postoji.
                     Otvoriću podrazumevani...""")
            with open("korisnici.txt", 'r') as f:
                return f.readlines()
    
    # ova putanje ne postoji
    otvaranje_fajla("C:\\Documents\Pictures\more.jpg")
    
    Fajl koji želite da otvorite ne postoji.
                     Otvoriću podrazumevani...
    ['pera|peric\n', 'jova|jovic\n', 'steva|stevic\n']
    
  • except blok koji nema navedenu klasu greške će uhvatiti sve greške

    def otvaranje_fajla(file_name):
        try:
            # pokušavam da otvorim fajl čije je ime prosleđeno kao argument
            with open(file_name, 'r') as f:
                return f.readlines()
        except:
            # ako je Python "bacio" bilo kakvu grešku
            print("""Fajl koji želite da otvorite ne postoji.
                     Otvoriću podrazumevani...""")
            with open("korisnici.txt", 'r') as f:
                return f.readlines()
    

    Ovaj program je ekvivalentan sa onim predhodnim.

  • U except izrazu je takođe moguć as izraz:

    def otvaranje_fajla(file_name):
        try:
            # pokušavam da otvorim fajl čije je ime prosleđeno kao argument
            with open(file_name, 'r') as f:
                return f.readlines()
        except FileNotFoundError as e:
            # ako je Python "bacio" FileNotFoundError onda uradi ovo ovde
            print("""Fajl koji želite da otvorite ne postoji.
                     Otvoriću podrazumevani...""")
            print("Greška koju ste dobili je: " + e.strerror)
            with open("korisnici.txt", 'r') as f:
                return f.readlines()
    
    otvaranje_fajla("C:\\Documents\Pictures\more.jpg")
    
    Fajl koji želite da otvorite ne postoji.
                     Otvoriću podrazumevani...
    Greška koju ste dobili je: No such file or directory
    ['pera|peric\n', 'jova|jovic\n', 'steva|stevic\n']
    
  • Moguće je navoditi više except blokova, tada će biti uhvaćena prva greška koja odgovara grešci koja se desila.

    def otvaranje_fajla(file_name):
        try:
            # pokušavam da otvorim fajl čije je ime prosleđeno kao argument
            with open(file_name, 'r') as f:
                return f.readlines()
        except FileNotFoundError as e:
            # ako je Python "bacio" FileNotFoundError onda uradi ovo ovde
            print("""Fajl koji želite da otvorite ne postoji.
                     Otvoriću podrazumevani...""")
            print("Greška koju ste dobili je: " + e.strerror)
            with open("korisnici.txt", 'r') as f:
                return f.readlines()
        except IOError:
            print("Desio se neki error")
    
    otvaranje_fajla("nepostojeća putanja")
    
    Fajl koji želite da otvorite ne postoji.
                     Otvoriću podrazumevani...
    Greška koju ste dobili je: No such file or directory
    ['pera|peric\n', 'jova|jovic\n', 'steva|stevic\n']
    
    # coding: utf-8
    
    import os
    
    def otvaranje_fajla(file_name):
        try:
            # pokušavam da otvorim fajl čije je ime prosleđeno kao argument
            with open(file_name, 'r') as f:
                return f.readlines()
        except IOError as e:
            # iako se zapravo desio FileNotFoundError ipak će se ući u ovaj blok
            # jer je FileNotFoundError vrsta IOError-a a
            # ovaj except blok je naveden pre onog sa FileNotFoundError-om
            print("Desio se neki error sa porukom: " + e.strerror + os.linesep +
                  "i brojem: " + str(e.errno))
        except FileNotFoundError as e:
            print("""Fajl koji želite da otvorite ne postoji.
                     Otvoriću podrazumevani...""")
            print("Greška koju ste dobili je: " + e.strerror)
            with open("korisnici.txt", 'r') as f:
                return f.readlines()
    
    otvaranje_fajla("nepostojeća putanja")
    
    Desio se neki error sa porukom: No such file or directory
    i brojem: 2
    

6 Funkcije

Funkcije su izrazi (expressions) koji enkapsuliraju radnju nad nekim podacima.
Zato svaka funkcija treba da ima:

  • ime (ime funkcije se zadaje malim slovima tako da se reči odvajaju donjom crtom _ — standardi pisanja Python koda su dati ovde).
  • dokumentaciju (tekst koji opisuje šta ta funkcija radi, o tome je bilo reči ranije)
  • listu parametara (podatci nad kojima se vrši radnja — idealno bi bilo da se radnja vrši samo nad njima)
  • telo (opis same radnje)
  • povratnu vrednost (rezultat koji ta radnja ima — idealno bi bilo da on zavisi samo od parametara)

    # coding: utf-8
    
    def ime_funkcije(parametar_1, parametar_2):
        """Ova funkcija sabira dva broja.
    
        Ovde ide širi opis toga što radi funkcija. Ova prosto sabira dva broja.
    
        Args:
            parametar_1: Prvi broj koji se sabira.
            parametar_2: Drugi broj koji se sabira.
    
        Returns:
            Broj koji je rezultat sabiranja
    
        Raises:
            ...
        """
        # telo
        ret_val = parametar_1 + parametar_2
        # povratna vrednost
        return ret_val
    

Nakon definicije funkcije se mogu pozvati.
Funkcija se poziva tako što se navede njeno ime sa sintaksom poziva (to praktično znači ime_funkcije(), () je sintaksa poziva u Python-u).

# 2 i 3 su argumenti
ime_funkcije(2, 3)
5

6.1 Čiste funkcije

Ako Python izraze želimo da posmatramo kao matematičke izraze onda moramo obezbediti da važi matematički princip zamene simbola.

Šta to praktično znači?

Ovako izgleda niz nekih matematičkih izraza:

x = 1
y = 2

def f1(x):
    return 5 * x + 1

def f2(x):
    return x**2 + 2

# Koliko je z?
z = f1(x) + f2(y)

Ovo se rešava tako što prosto zamenjujemo simbole:

z = f1(1) + f2(2)

Dalje imamo:

z = (5 * 1 + 1) + (2**2 + 2)

Pa je rezultat:

z
12

Da bismo Python izraze posmatrali kao matematičke izraze moramo obezbediti da sve funkcije budu čiste i bez sporednih efekata.

Funkcije su čiste (pure functions) ako za njih važe oba naredna uslova?

  1. Uvek daju isti rezultat za iste argumente.
  2. Nemaju sporednih efekata (side effects)

Šta znači da funkcije nemaju sporednih efekata?

  • Ne menjaju globalne variable niti bilo koje druge koje nisu u njenom vidljivom opsegu (tj. nisu definisane unutar njenog def bloka, u Python-u)

Ako su naše funkcije čiste onda UVEK možemo da kažemo šta će one uraditi u našem sistemu.
Tako što prosto zamenimo pozive funkcija sa njihovim rezultatom.

Kada izraz poziva funkcije možemo uvek da zamenimo njenim rezultatom onda za funkciju kažemo da je referencijalno transparentna.

6.2 Primeri funkcija

  • Funkcija koja učitava fajl i njegova sadržaj vraća kao listu.

    # coding: utf-8
    
    def citanje_iz_fajla(file_name, delimiter):
        """Učitava kredencijale korisnika.
    
        Args:
            file_name: Ime fajla u kome se nalaze kredencijali.
            delimiter: Delimiter koji je korišćen u fajlu.
    
        Returns:
            Vraća listu koja sadrži parove (kao liste) korisničkog imena i lozinke.
        """
        with open(file_name, 'r') as f:
            return [l.strip().split(delimiter) for l in f.readlines()]
    
    print(citanje_iz_fajla
          ("credentials.supa_od_kornjače", "|$# ovo je delimiter #$|"))
    
    [['uno', 'dos'], ['kilo', 'kilo'], ['joki', 'kijo'], ['mile', 'milic']]
    
  • Funkcija koja vrši registraciju korisnika

    # coding: utf-8
    
    import os
    
    def upisi_fajl(uname, pas, file_name, delimiter):
        """Vrši registraciju korisnika tako što upisuje njihove kredencijale u fajl.
    
        Args:
            uname: Korisnično ime
            pas: Lozinka
            file_name: Ime fajla u koji se upisuju kredencijali korisnika
            delimiter: Delimiter koji je korišćen u tom fajlu
    
        Retruns:
            Vraća listu koja sadrži parove (kao liste) korisničkog imena i lozinke.
        """
        with open(file_name, 'a') as f:
            f.write(uname + delimiter + pas + os.linesep)
        with open(file_name, 'r') as f:
            return [l.strip().split(delimiter) for l in f.readlines()]
    
    upisi_fajl("ana", "ananananic",
               "credentials.supa_od_kornjače", "|$# ovo je delimiter #$|")
    
    [['uno', 'dos'], ['kilo', 'kilo'], ['joki', 'kijo'], ['mile', 'milic'], ['ana', 'ananananic']]
    

7 Grananje — upravljanje tokom programa

Kontrola toka programa ili grananje se odnosi na mehanizam kojim usmeravamo tok izvršavanja programa u odnosu na postavljene uslove.

Osnovna konstrukcija grananja u Python-u je if ... [elif]* ... else:

if uslov:
    posao_ako_je_uslov_ispunjen
else:
    posao_ako_uslov_nije_ispunjen

Uslova u opštem slučaju može biti više:

if uslov_1:
    posao_ako_je_uslov_1_ispunjen
elif uslov_2:
    posao_ako_je_uslov_1_neispunjen_a_uslov_2_ispunjen
...
elif uslov_n:
    posao_ako_su_uslov_1_do_uslov_n-1_neispunjeni_a_uslov_n_ispunjen
else:
    posao_ako_ni_jedan_navedeni_uslov_nije_ispunjen
  • Program koji uzračunava zaradu radnika u odnosu na radne sate.

    # coding: utf-8
    
    import os
    
    def racunanje_zarada(ime_f, cena_sata):
        koeficijent_uvecanja = 1.5
        with open(ime_f, 'r') as f:
            for l in f.readlines():
                ime_radnika = l.split('|')[0]
                radni_sati = l.split('|')[1:]
                # konvertujem tekst u brojeve
                zbir = 0
                for sat in radni_sati:
                    zbir += int(sat)
                osnovna_plata = cena_sata * zbir
                # provera da li je broj radnih sati veći od 40
                # ovo je grananje
                if zbir > 40:
                    print("ime: " + ime_radnika + os.linesep +
                          "zarada: " + str(osnovna_plata * koeficijent_uvecanja))
                else:
                    print("ime: " + ime_radnika + os.linesep + "zarada: "
                          + str(osnovna_plata))
    
    racunanje_zarada("radnici.txt", 1000)
    
    ime: pera
    zarada: 61500.0
    ime: jova
    zarada: 38000
    ime: steva
    zarada: 40000
    
  • Isti zadatak ali uz malo pametnije računanje zbira radnih sati. Korišćen je koncept list comprehensions.

    # coding: utf-8
    
    import os
    
    def racunanje_zarada(ime_f, cena_sata):
        koeficijent_uvecanja = 1.5
        with open(ime_f, 'r') as f:
            for l in f.readlines():
                ime_radnika = l.split('|')[0]
                radni_sati = l.split('|')[1:]
                zbir = sum([int(s) for s in radni_sati])
                osnovna_plata = cena_sata * zbir
                # provera da li je broj radnih sati veći od 40
                # ovo je grananje
                if zbir > 40:
                    print("ime: " + ime_radnika + os.linesep +
                          "zarada: " + str(osnovna_plata * koeficijent_uvecanja))
                else:
                    print("ime: " + ime_radnika + os.linesep + "zarada: "
                          + str(osnovna_plata))
    
    racunanje_zarada("radnici.txt", 1000)
    
    ime: pera
    zarada: 61500.0
    ime: jova
    zarada: 38000
    ime: steva
    zarada: 40000
    
  • Program koji ocenjuje učenike po tabeli ocena.

    def ocenjivanje(bodovi):
        if bodovi > 0 and bodovi < 55:
            return 5
        elif bodovi >= 55 and bodovi < 65:
            return 6
        elif bodovi >= 65 and bodovi < 75:
            return 7
        elif bodovi >= 75 and bodovi < 85:
            return 8
        elif bodovi >= 85 and bodovi < 95:
            return 9
        elif bodovi >= 95 and bodovi <= 100:
            return 10
        else:
            return "Nepoznata ocena"
    
    ocenjivanje(77)
    ocenjivanje(95)
    ocenjivanje(96)
    ocenjivanje(56)
    ocenjivanje(101)
    
    8
    10
    10
    6
    'Nepoznata ocena'
    
  • Program koji računa body mass index i određuje stepen uhranjenosti po tabeli.

    # coding: utf-8
    
    def indeks_telesne_mase(masa, visina):
        bmi = masa / visina**2
        if bmi <= 18.5:
            return "Pothranjenost"
        elif bmi > 18.5 and bmi <= 25:
            return "Idealna telesna težina"
        elif bmi > 25 and bmi <= 30:
            return "Preterana telesna težina"
        elif bmi > 30:
            return "Gojaznost"
    
    print(indeks_telesne_mase(55,1.8))
    print(indeks_telesne_mase(75,1.8))
    print(indeks_telesne_mase(82,1.8))
    print(indeks_telesne_mase(120,1.8))
    
    Pothranjenost
    Idealna telesna težina
    Preterana telesna težina
    Gojaznost
    
  • Program koji računa kaznu za prekoračenje brzine.

    # coding: utf-8
    
    def kazna(brzina, ogranicenje):
        glavnica = 5000
        dodatak_po_jedinici_prekoracenja = 500
        dodatak_za_iznad_120 = 5000
        prekoracenje = brzina - ogranicenje
        za_uplatu = 0
        # ako je prekoracio naplati mu za svaku jedinicu prekoracenja jos
        # dodatnih 500 dinara
        if brzina < 120 and prekoracenje > 0:
            za_uplatu = glavnica
            za_uplatu += prekoracenje * dodatak_po_jedinici_prekoracenja
        # ako je brzina veća od 120 km/h naplati 5000 dinara za svaku jedinicu
        # prekoračenja
        if brzina > 120:
            prekoracenje_preko_120 = brzina - 120
            za_uplatu += prekoracenje_preko_120 * dodatak_za_iznad_120
        if za_uplatu > 0:
            return "Vaša kazna iznosi: " + str(za_uplatu) + "."
        else:
            return "Niste prekoračili brzinu."
    
    print(kazna(80,60))
    print(kazna(50,60))
    print(kazna(130,60))
    
    Vaša kazna iznosi: 15000.
    Niste prekoračili brzinu.
    Vaša kazna iznosi: 50000.
    
  • Program koji računa zaradu dadilje.

    Korišćeni koncepti:

    • funkcija round(...). Zaokružuje razlomljen broj na ceo.
    def vreme_u_minute(vreme):
        """Pretvara vreme u formatu hh:mm u minute."""
        h, m = [int(i) for i in vreme.split(":")]
        return h*60 + m
    
    def minuti_u_sate(minuti):
        """Pretvara minute u sate."""
        return minuti / 60
    
    def dadilja(pocetak, kraj):
        p = vreme_u_minute(pocetak)
        k = vreme_u_minute(kraj)
        devet = vreme_u_minute("21:00")
        do_devet = devet - p
        posle_devet = k - devet
        za_isplatu = minuti_u_sate(do_devet) * 150 + \
                     minuti_u_sate(posle_devet) * 100
        return "Zarada dadilje je: " + str(round(za_isplatu)) + " din."
    
    print(dadilja('18:35','22:50'))
    
    Zarada dadilje je: 546 din.
    

  • Program koji računa da li je godina prestupna.

    def is_prestupna(godina):
        # proveravam da li je poslenja godina u veku
        # da bi bila poslednja u veku to znači da mora biti deljiva sa 100
        # a ako je deljiva sa 100 onda je deljiva i sa 400
        if godina % 400 == 0:
            return True
        # ako nije poslednja godina u veku onda je prestupna ako
        # je dejiva sa 4
        elif godina % 100 != 0 and godina % 4 == 0:
            return True
        else:
            return False
    

    Funkcija vraća True ako je godina prestupa, u suprotnom vraća False.

    is_prestupna(1983)
    is_prestupna(1984)
    is_prestupna(1800)
    is_prestupna(1900)
    is_prestupna(2000)
    
    False
    True
    False
    False
    True
    
  • Program koji računa da li je datum validan.

    Korišćeni koncepti:

    • Set — Python reprezentacija matematičkih skupova. Zapisuje se {} notacijom.
      Funkcija set(kolekcija) pretvara druge Python kolekcije u set.

      Primer jednog seta:

      neki_set = {1, 2, 3, 4}
      

      Neke operacije nad setovima:

      lista_kolekcija = [1, 2, 3, 4]
      tuple_kolekcija = (3, 4, 5, 6, 7)
      
      A = set(lista_kolekcija)
      B = set(tuple_kolekcija)
      
      # A bez B
      A - B
      
      # B bez A
      B - A
      
      # Unija A i B
      B.union(A)
      
      # presek A i B
      B.intersection(A)
      
      # presek B i A
      A.intersection(B)
      
      A.intersection(B) == B.intersection(A)
      
      >>> >>> >>> >>> >>> ... {1, 2}
      >>> ... {5, 6, 7}
      >>> ... {1, 2, 3, 4, 5, 6, 7}
      >>> ... {3, 4}
      >>> ... {3, 4}
      >>> True
      
    • Operator in — operator pripadanja (membership operator).
      Radi za kolekcije i vraća True kada se element nalazi u kolekciji, a u suprotnom Flase.

      1 in [2, 3, 4, 5, 1, 2, 12]
      1 in (2, 3, 4, 5, 1, 2, 12)
      1 in {2, 3, 4, 5, 1, 2, 12}
      
      12 in range(11, 23)
      
      True
      True
      True
      >>> True
      

      Operator in se u uslovima može koristiti kao zamena za or lanac.

      a = 3
      
      a == 1 or a == 2 or a == 3
      
      a in (1, 2, 3)
      
      (a == 1 or a == 2 or a == 3) == (a in (1, 2, 3))
      
      >>> True
      >>> True
      >>> True
      

    Provera validnosti datuma:

    # coding: utf-8
    
    def is_valid(datum):
        dan, mesec, godina = [int(i.strip()) for i in datum.split("/")]
        meseci_od_30 = [4, 6, 9, 11]
        # meseci od 31 su svi između 1 i 12 koji ne spadaju
        # u one koji imaju 30 dana i nisu februar
        meseci_od_31 = set(range(1, 13)) - set(meseci_od_30) - set([2])
        # ako je u pitanju februar
        if mesec == 2 and dan > 0:
            # prestupnim godinama ima 29 dana
            if is_prestupna(godina) and dan <= 29:
                return True
            # kada nije prestupna godina ima 28 dana
            elif dan <= 28:
                return True
            else:
                return False
        elif mesec in meseci_od_30 and dan > 0 and dan <= 30:
            return True
        elif mesec in meseci_od_31 and dan > 0 and dan <= 31:
            return True
        else:
            return False
    
    is_valid("24/5/1962")
    is_valid("31/9/2000")
    is_valid("29/2/2000")
    is_valid("29/2/2001")
    is_valid("30/2/2000")
    
    True
    False
    True
    False
    False
    
  • Program koji računa redni broj dana u godini.

    U ovom zadatku je korišćena funkcija za računanje prestupne godine koju smo ranije definisali.

    def redni_broj_dana(datum):
        dd, mm, gggg = [int(i.strip()) for i in datum.split("/")]
        dan_u_godini = 31  * (mm - 1) + dd
        if mm > 2:
            dan_u_godini -= (4 * mm + 23) / 10
            if is_prestupna(gggg):
                dan_u_godini += 1
        return round(dan_u_godini)
    
    redni_broj_dana("1/1/2000")
    redni_broj_dana("14/3/2000")
    redni_broj_dana("31/12/2000")
    redni_broj_dana("31/12/2001")
    
    1
    74
    366
    365
    

8 Petlje i logički izrazi

  • Tabela subjektivnog osećaja hladnoće

    def sub_osecaj(t, v):
        return 3.74 + 0.6215 * t - 35.75 * (v**0.16) + \
            0.4275 * t * (v**0.16)
    
    def napravi_tabelu(min_v, max_v, min_t, max_t):
        temperatures = range(min_t, max_t + 1)
        # prvi red
        first_row = [" "] + ["t=" + str(i) for i in temperatures]
        print("\t".join(first_row))
        # ostali redovi
        for v in range(min_v, max_v + 1):
            # deo sa v = ...
            vetar = "v=" + str(v)
            # ostale kolone
            temps = ["{0:.3f}".format(sub_osecaj(t, v)) for t in temperatures]
            # ceo red
            red = "\t".join([vetar] + temps)
            print(red)
    
    napravi_tabelu(0, 10, 5, 10)
    
            t=5	t=6	t=7	t=8	t=9	t=10
    v=0	6.848	7.469	8.091	8.712	9.334	9.955
    v=1	-26.765	-25.716	-24.667	-23.618	-22.569	-21.520
    v=2	-30.707	-29.608	-28.509	-27.410	-26.311	-25.212
    v=3	-33.224	-32.093	-30.962	-29.831	-28.700	-27.569
    v=4	-35.112	-33.957	-32.802	-31.647	-30.491	-29.336
    v=5	-36.637	-35.463	-34.288	-33.113	-31.939	-30.764
    v=6	-37.924	-36.733	-35.542	-34.352	-33.161	-31.970
    v=7	-39.042	-37.837	-36.632	-35.427	-34.222	-33.017
    v=8	-40.033	-38.816	-37.598	-36.380	-35.162	-33.945
    v=9	-40.925	-39.696	-38.467	-37.238	-36.009	-34.780
    v=10	-41.737	-40.498	-39.258	-38.019	-36.780	-35.540
    
  • Koliko treba da prođe godina da bi se novac u banci udvostručio

    def broj_godina(kamata):
        ulog = 1
        dvostruko = 2 * ulog
        godina = 0
        while ulog < dvostruko:
            ulog += ulog * kamata
            godina += 1
            print("U godini " + str(godina) + " ulog se uvećao na " + str(ulog))
        print("Da bi se ulog udvostručio potrebno je da prođe " + str(godina) +
              " godina.")
    
    broj_godina(0.04)
    
    U godini 1 ulog se uvećao na 1.04
    U godini 2 ulog se uvećao na 1.0816000000000001
    U godini 3 ulog se uvećao na 1.124864
    U godini 4 ulog se uvećao na 1.16985856
    U godini 5 ulog se uvećao na 1.2166529024
    U godini 6 ulog se uvećao na 1.265319018496
    U godini 7 ulog se uvećao na 1.3159317792358398
    U godini 8 ulog se uvećao na 1.3685690504052734
    U godini 9 ulog se uvećao na 1.4233118124214843
    U godini 10 ulog se uvećao na 1.4802442849183437
    U godini 11 ulog se uvećao na 1.5394540563150774
    U godini 12 ulog se uvećao na 1.6010322185676804
    U godini 13 ulog se uvećao na 1.6650735073103877
    U godini 14 ulog se uvećao na 1.7316764476028033
    U godini 15 ulog se uvećao na 1.8009435055069154
    U godini 16 ulog se uvećao na 1.872981245727192
    U godini 17 ulog se uvećao na 1.9479004955562795
    U godini 18 ulog se uvećao na 2.025816515378531
    Da bi se ulog udvostručio potrebno je da prođe 18 godina.
    
  • Sirakuza sekvenca

    def sirakuza(start):
        ret_val = [start]
        num = start
        while num != 1:
            if num % 2 == 0:
                num = num / 2
            else:
                num = 3 * num + 1
            ret_val.append(int(num))
        return ret_val
    
    sirakuza(5)
    
    [5, 16, 8, 4, 2, 1]
    
  • Provera da li je broj prost

    import math as m
    
    def is_prost(broj):
        for i in range(2, int(m.sqrt(broj)) + 1):
            if broj % i == 0:
                return False
        return True
    
    is_prost(127)
    is_prost(123)
    is_prost(2)
    is_prost(24)
    is_prost(13)
    is_prost(15)
    is_prost(9)
    
    True
    False
    True
    False
    True
    False
    False
    
  • Funkcija koja vraća listu prostih brojeva koji su manji od prosleđeno argumenta

    def get_prosti_to(broj):
        ret_val = []
        for i in range(1, broj):
            if is_prost(i):
                ret_val.append(i)
        return ret_val
    
    get_prosti_to(20)
    
    [1, 2, 3, 5, 7, 11, 13, 17, 19]
    

    Isti zadatak ali uz korišćenje list comprehensions.

    def get_prosti_to(broj):
        return [i for i in range(1, broj) if is_prost(i)]
    
    get_prosti_to(20)
    
    [1, 2, 3, 5, 7, 11, 13, 17, 19]
    
  • Najveći zajednički delilac po Euklidovom algoritmu

    def nzd(m, n):
        while m != 0:
            m_staro = m
            n_staro = n
            n = m_staro
            m = n_staro % m_staro
        return n
    
    nzd(15, 25)
    nzd(15, 25)
    
    5
    5
    

8.1 get_prost_to uz korišćenje filter funkcije.   za_radoznale

def get_prost_to(broj):
    filter(is_prost, range(1, broj))
get_prosti_to(20)
[1, 2, 3, 5, 7, 11, 13, 17, 19]

filter dakle kao parametar prima funkciju i neku strukturu kroz koju se može "prolaziti". Vraća samo one elemente strukture za koje prosleđena funkcija vraća istinitosno tačnu vrednost (npr. True). Pogledaj poglavlje o funkcijama višeg reda u dodatku.

8.2 is_prost uz korišćenje funkcija višeg reda   za_radoznale

import math as m

def is_prost(broj):
    return not any(filter(lambda x: broj % x == 0,
                          range(2, int(m.sqrt(broj)) + 1)))
is_prost(127)
is_prost(123)
is_prost(2)
is_prost(24)
is_prost(13)
is_prost(15)
True
False
True
False
True
False

8.3 Zadatak koji liči na projekat

Korisnik se prijavljuje na sistem. Nakon toga unosi proizvode. Svaki put kada unese neki proizvod program mu ispisuje sve proizvode koji se nalaze na spisku. Program se prekida kada korisnik unese quit za naziv, količinu ili cenu nekog proizvoda. Spisak registrovanih korisnika se nalazi u korisnici.register a spisak proizvoda je u proizvodi.vezbe_7.

# coding: utf-8
import os

delimiter = "|"

def prijava(u_name, pasw):
    with open("korisnici.register", 'r') as f:
        for l in f.readlines():
            if [u_name, pasw] == [s.strip() for s in l.split(delimiter)]:
                return True
        return False

def prikazi_proizvode(fajl):
    with open(fajl, 'r') as p:
            for l in p.readlines():
                print("\t".join([i.strip() for i in l.split(delimiter)]))

def program():
    u_name = input("Unesite korisničko ime: ")
    pasw = input("Unesite lozinku: ")
    if not prijava(u_name, pasw):
        print("Pogrešan par korisničkog imena i lozinke")
        return
    while True:
        naziv = input("Unesite naziv proizvoda: ")
        if naziv == "quit":
            return
        cena = input("Unesite cenu proizvoda: ")
        if cena == "quit":
            return
        kolicina = input("Unesite količinu proizvoda: ")
        if kolicina == "quit":
            return
        # izbacim whitespace karaktere sa kraja i pocetka
        naziv, cena, kolicina = [s.strip() for s in [naziv, cena, kolicina]]
        with open("proizvodi.vezbe_7", 'a') as p:
            p.write(delimiter.join([naziv, cena, kolicina]) + os.linesep)
            # prikazujem sve proizvode
        prikazi_proizvode("proizvodi.vezbe_7")

Program se pokreće sa:

program()

9 Kolekcije podataka

  • Ispis i dodavanje knjiga

    Korišćeni koncepti:

    • zip funkcija koju smo spominjali ranije.
    • String u više linija.

      Ako vam je neka sekvenca građenja stringa preduga:

      s = "a" + "b" + "c" + "d" + "e" + "f" + "g" + "h"
      

      Možete je napisati u više redova na sledeći način:

      s = ("a" "b"
           "c" "d"
           "e" "f"
           "g" "h")
      
    # Ova globalna varijabla simulira fajl u kome se nalaze podaci o knjizi
    knjige = []
    
    def dodaj_knjigu(k):
        knjige.append(k)
    
    def ispisi_sve_knjige():
        # string u više linija
        zaglavlje = ("id" "\t" "naslov" "\t"  "autori"  "\t"  "izdavac"  "\t"
                     "cena"  "\t"  "kolicina"  "\t"  "godina")
        print(zaglavlje)
        print(int(3/2 * len(zaglavlje)) * "-")
        for k, i in zip(knjige, range(len(knjige) + 1)):
            print(str(i + 1) + "\t" + k["naslov"] + "\t" + k["autori"] + "\t" +
                  k["izdavac"] + "\t" + str(k["cena"]) + "\t" + str(k["kolicina"]) +
                  "\t" + str(k["godina"]))
    
    dodaj_knjigu({"naslov": "N1", "autori": "A1, A2", "izdavac": "I1",
                  "cena": 123.23, "kolicina": 12, "godina": 2016})
    dodaj_knjigu({"naslov": "N2", "autori": "A11, A22", "izdavac": "I2",
                  "cena": 123.23, "kolicina": 12, "godina": 2015})
    ispisi_sve_knjige()
    
    id	naslov	autori	izdavac	cena	kolicina	godina
    -------------------------------------------------------------------
    1	N1	A1, A2	        I1	123.23	12	2016
    2	N2	A11, A22	I2	123.23	12	2015
    
  • Isti zadatak ali sa čitanjem i pisanjem u fajl

    Korišćeni koncepti:

    • Opcioni imenovani parametri

      Python ima mogućnost pisanja funkcija koje imaju opcione parametre.

      Opcioni parametri su oni koji funkciji mogu ali ne moraju biti prosleđeni pri pozivu.

      Python pruža tri tipa takvih parametara:

      • Imenovani

        def foo_a(obavezan, opcioni1="nisi prosledio opcioni1",
                  opcioni2="nisi prosledio opcioni1"):
            print(obavezan)
            print(opcioni1)
            print(opcioni2)
        
        foo_a("ovo moram da prosledim")
        
        ovo moram da prosledim
        nisi prosledio opcioni1
        nisi prosledio opcioni1
        
        foo_a("ovo moram da prosledim", "prosleđujem opcioni1")
        
        ovo moram da prosledim
        prosleđujem opcioni1
        nisi prosledio opcioni1
        
        foo_a("ovo moram da prosledim", "prosleđujem opcioni1",
              "prosleđujem opcioni2")
        
        ovo moram da prosledim
        prosleđujem opcioni1
        prosleđujem opcioni2
        
      • Neimenovani

        def foo_b(obavezni, *args):
            print(obavezni)
            print("ostali parametri: ")
            print(*args)
        
        foo_b("ovo mora")
        
        ovo mora
        ostali parametri:
        
        foo_b("ovo mora", 1, 2, 3, "pera", {'zivotinja': 'zec'})
        
        ovo mora
        ostali parametri:
        1 2 3 pera {'zivotinja': 'zec'}
        
      • Ključne reči

        def foo_c(obavezni, **kwargs):
            print(obavezni)
            for (k, v) in kwargs.items():
                print(k, v)
        
        foo_c("ovo mora")
        
        ovo mora
        
        foo_c("ovo mora", ime1="vrednost1", ime2=2, ime3=[1, 2, 3])
        
        ovo mora
        ime2 2
        ime3 [1, 2, 3]
        ime1 vrednost1
        

      Funkcija naravno može od jednom da ima sve navedene tipove opcionih parametara ( ukoliko to ima smisla ).

      *args i **kwargs su samo nazivi. Sve što počinje sa * i ** se tretira kao skup neimenovanih i imenovanih opcionih parametara, respektivno.

      def foo_d(obavezni, *neimenovani_parametri,
                imenovani="default value", **kljucne_reci):
          print(obavezni)
          print("Neimenovani opcioni:")
          print(neimenovani_parametri)
          print("Imenovani parametri:")
          print(imenovani)
          print("Ključne reči:")
          for (k, v) in kljucne_reci.items():
              print(k, v)
      
      foo_d("ovo mora", 1, 2, "neimenovani", imenovani="prosleđujem imenovani",
            kljucna_rec_1="k1", kljucna_rec_2="k2", kljucna_rec_3=33)
      
      ovo mora
      Neimenovani opcioni:
      (1, 2, 'neimenovani')
      Imenovani parametri:
      prosleđujem imenovani
      Ključne reči:
      kljucna_rec_3 33
      kljucna_rec_1 k1
      kljucna_rec_2 k2
      
      foo_d("ovo mora", 1, 2, imenovani="prosleđujem imenovani")
      
      ovo mora
      Neimenovani opcioni:
      (1, 2)
      Imenovani parametri:
      prosleđujem imenovani
      Ključne reči:
      
      foo_d("ovo mora", imenovani="prosleđujem imenovani")
      
      ovo mora
      Neimenovani opcioni:
      ()
      Imenovani parametri:
      prosleđujem imenovani
      Ključne reči:
      
      foo_d("ovo mora", k_rec = "ovo je kljucna rec")
      
      ovo mora
      Neimenovani opcioni:
      ()
      Imenovani parametri:
      default value
      Ključne reči:
      k_rec ovo je kljucna rec
      

      Ne mogu neimenovani argumenti ići posle imenovanih parametara ili posle ključnih reči:

      foo_d("ovo mora", imenovani="prosleđujem imenovani", "opcioni")
      
      File "<stdin>", line 1
      SyntaxError: non-keyword arg after keyword arg
      
      foo_d("ovo mora", k_rec = "ovo je kljucna rec", 12)
      
      File "<stdin>", line 1
      SyntaxError: non-keyword arg after keyword arg
      
    • if u jednom redu
      ako_je_uslov_tačan if uslov else ako_uslov_nije_tačan

      a = 4
      
      a = "jeste veće od 5" if a > 5 else "nije veće od 5"
      
      print(a)
      
      nije veće od 5
      

    Implementacija:

    import os
    
    # ova funkcija koristi imenovane opcione parametre
    def file_to_list(f_name, delimiter='|',
                     numericki=["cena", "kolicina", "godina"]):
        with open(f_name, 'r') as f:
            knjige = []
            # prolazim kroz svaku liniju
            for l in f.readlines():
                # u svakoj liniji je po jedna knjiga
                k = {}
                # liniju splitujem po delimiteru i sklonim whitespace
                # karaktere sa kraja i početka
                podaci_u_liniji = [d.strip() for d in l.split(delimiter)]
                # pogledaj šta je zip ranije
                for osobina, podaci in zip(["naslov", "autori", "izdavac",
                                            "cena", "kolicina", "godina"],
                                           podaci_u_liniji):
                    # koristim if u jednom redu
                    # da pretvodim numeričke podatke u brojeve
                    # i izgrađujem rečnik koji predstavlja jednu knjigu
                    k[osobina] = float(podaci) if osobina in numericki else podaci
    
                # dodam knjigu
                knjige.append(k)
            return knjige
    
    def list_to_file(li, f_name, delimiter="|"):
        with open(f_name, 'a') as f:
            lines = []
            for dic in li:
                # preuzmem sve podatke iz rečnika po standardnom redosledu
                line_data = [dic["naslov"], dic["autori"], dic["izdavac"],
                             dic["cena"], dic["kolicina"], dic["godina"]]
                # napravim liniju za snimanje u fajl
                lines.append(delimiter.join([str(v) for v in line_data]) +
                             os.linesep)
    
            f.writelines(lines)
    
    def dodaj_knjigu(k):
        list_to_file([k], "knjige.data")
    
    def ispisi_sve_knjige(f_name):
        zaglavlje = ("id" "\t" "naslov" "\t"  "autori"  "\t"  "izdavac"  "\t"
                     "cena"  "\t"  "kolicina"  "\t"  "godina")
        print(zaglavlje)
        print(int(3/2 * len(zaglavlje)) * "-")
        knjige = file_to_list(f_name)
        for k, i in zip(knjige, range(len(knjige) + 1)):
            print(str(i + 1) + "\t" + k["naslov"] + "\t" + k["autori"] + "\t" +
                  k["izdavac"] + "\t" + str(k["cena"]) + "\t" + str(k["kolicina"]) +
                  "\t" + str(k["godina"]))
    
    dodaj_knjigu({'naslov': 'Novi', 'kolicina': 12.0, 'godina': 2016,
                  'izdavac': 'Novi Izdavac', 'autori': 'Novi Novic',
                  'cena': 123.23})
    
    ispisi_sve_knjige("knjige.data")
    
    id	naslov	autori	izdavac	cena	kolicina	godina
    -------------------------------------------------------------------
    1	SICP	Sussman	I1	12.0	12.0	1979.0
    2	SICP	Sussman	I1	12.0	12.0	1979.0
    3	Novi	Novi Novic	Novi Izdavac	123.23	12.0	2016.0
    

    S obzirom da ovde knjiga nema eksplicitan id već je on predstavljen indeksom u listi to znači da ne možemo znati da li su SICP i SICP jedna te ista knjiga i zato je moguće da se u spisku knjiga nađe knjiga koja se zove SICP više puta.

    Ako želimo da rešimo ovaj problem onda moramo napraviti funkciju dodaj_knjigu takvu da prima knjigu koja eksplicitno sadrži id. To stvara novi zadatak — potrebno je voditi računa da se u našem spisku knjiga ne nađu dve sa istim id.

10 Dodatak

10.1 Rekurzija

Rekurzija je kada neki pojam definišemo koristeći sam pojam.

U programskim jezicima rekurzija se može postići na nivou funkcija:

def reci_ciao():
    print("Ciao!")
    reci_ciao()
reci_ciao()
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in reci_ciao
  File "<stdin>", line 3, in reci_ciao
  File "<stdin>", line 3, in reci_ciao
  File "<stdin>", line 3, in reci_ciao
  File "<stdin>", line 3, in reci_ciao
  File "<stdin>", line 3, in reci_ciao
  File "<stdin>", line 3, in reci_ciao
  ...
     RuntimeError: maximum recursion depth exceeded while calling a Python object

Računari su konačne prirode pa ne mogu (tek tako) izraziti nešto što je beskonačno, a poziv funkcije reci_ciao je beskonačan.

Kako bi se rekurzija nekada završila moramo joj dati uslov završetka:

def reci_ciao(p):
    # ovo je uslov završetka
    if p < 10:
        print("Ciao!")
        reci_ciao(p + 1)
    else:
        return
reci_ciao(0)
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
reci_ciao(7)
Ciao!
Ciao!
Ciao!
reci_ciao(10)
reci_ciao(15)

10.1.1 Rekurzija kao imperativna petlja

Rekurzija se često može napisati kao imperativna petlja:

def reci_ciao(p):
    for i in range(p, 10):
        print("Ciao!")
reci_ciao(0)
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
Ciao!
reci_ciao(7)
Ciao!
Ciao!
Ciao!
reci_ciao(10)
reci_ciao(15)

Imperativna petlja troši konstantnu količinu memorije. Neoptimizovana rekurzija troši novu memoriju pri svakom pozivu, dakle — više ulazaka u funkciju (poziva) više memorije potrošeno.
Rekurzija u Python-u je neoptimizovana (u svojoj osnovnoj verziji) ali to nije situacija sa svim programskim jezicima.

10.2 Python modularizacija

Svi netrivijalni Python programi su podeljeni u delove.

10.2.1 Moduli

Svaki Python file je ujedno i modul. Svaki modul bi trebao da sadrži kod koji čini neku veću logičku celinu.

Ime svakog modula odgovara imenu fajla bez ekstenzije py. Dakle, ako imamo fajl prijava.py onda se modul zove prijava.

Svaki modul ima ime __main__ ako je pokrenut direktno kao skripta:

python3 prodavnica.py

U svakom fajlu možete doći do imena njegovog modula preko globalne varijable __name__:

print("Ime ovog modula je: " + __name__)
Ime ovog modula je: __main__

… zato što je ovaj modul pokrenut direktno kao skripta.

10.2.2 Paketi

Svaki direktorijum u strukturi Python projekta može biti paket ako sadrži prazan fajl sa nazivom __init__.py.

Subpaketi(podpaketi) nekog modula su takođe direktorijumi koji sadrže prazan __init__.py fajl.

projekat/                          Top-level paket
      __init__.py                  Inicijalizator top-level paketa
      prodavnica.py
      formatiranje/                Subpaket za formatiranje
              __init__.py
              tabela.py
              ascii_slika.py
              ...
      prijava/                     Subpaket za proijavu
              __init__.py
              admin.py
              ostali.py
              ...

Sadržaj fajla projekat/prijava/admin.py je:

def prijavi_se(u_name, pasw):
    return "Korisnik " + u_name + ", " + pasw + " se prijavljuje."

Ako želimo da koristimo funkciju koja je definisana u projekat/prijava/admin.py iz projekat/prodavnica.py onda u projekat/prodavnica.py treba da stoji sledeće:

import prijava.admin

prijava.admin.prijavi_se("pera", "peric")
>>> 'Korisnik pera, peric se prijavljuje.'

Ovakvo ime funkcije, koje u sebi sadrži potpunu informaciju o tome gde je tačno funkcija definisana se zove fully qualified name. Možemo to prevesti sa puno ime funkcije, za funkciju iznad puno ime je:

prijava.admin.prijavi_se

Ili, ako želite da modul za prijavljivanje administratora zovete a_prijava:

import prijava.admin as a_prijava

a_prijava.prijavi_se("pera", "peric")
>>> 'Korisnik pera, peric se prijavljuje.'

a_prijava se još naziva i alias modula.

Možete uključiti funkciju i bez ikakvog aliasa ili punog imena koristeći sledeću konstrukciju:

from putanja.do.modula import funkcija
from prijava.admin import prijavi_se

prijavi_se("pera", "peric")
>>> 'Korisnik pera, peric se prijavljuje.'
  • Kako funkcioniše import? Tako što Python ode na putanju na koju ste ukazali imenom modula i prosto izvrši taj fajl. Obratite pažnju na ovo!

    Ako imao fajl projekat/prijava/admin_pogresan.py:

    def prijavi_se(u_name, pasw):
        return "Korisnik " + u_name + ", " + pasw + " se prijavljuje"
    
    print("Neki potpuno bespotreban print.")
    
... >>> Neki potpuno bespotreban print.

I ako njega uključimo da bismo koristili njegovu funkciju prijavi_se pri tome će nam se ispisati i bespotreban tekst!

import prijava.admin_pogresan

prijava.admin_pogresan.prijavi_se("pera", "peric")
Neki potpuno bespotreban print.

'Korisnik pera, peric se prijavljuje'

10.2.3 Primena modula i paketa

Python fajl se često piše tako da se može koristiti i direktno kao skripta i tako što će biti uključen kao modul.

Za onaj kod koji treba biti izvršen samo kada se fajl pokreće direktno kao skripta koristi se sledeći idiom:

if __name__ == '__main__':
    # kod koji se izvršava kada je fajl pokrenut kao skripta
    print("Pokrenut kao skripta")

Pretpostavimo da imamo fajl prikaz.py i da ga pokrećemo kao skriptu:

python3 prikaz.py
# fajl prikaz.py

def javi_se():
    return "Dobar dan, ja sam modul prikaz"

def za_direktno():
    print("Pokrenut sam direktno kao skripta!")

def pozdravi_se():
    return "Doviđenja, ja sam modul prikaz"

if __name__ == '__main__':
    za_direktno()
>>> ... ... >>> ... ... >>> ... ... >>> ... ... Pokrenut sam direktno kao skripta!

Taj modul takođe možemo uključiti sa import i pri tome se neće ispisati ništa.

10.3 lambda funkcije

Lambda-Calculus_3.png

Lambda funkcije potiču iz formalnog sistema u matematičkoj logici koji se zove lambda račun. Taj sistem je uveo još Alonzo Črč početkom 30-tih godina prošlog veka. U programske jezike je uveden kroz Lisp sredinom 50-tih godina prošlog veka.

Dakle, stvar je prilično stara (za istoriju računarstva) iako se iz nekog čudnog razloga danas smatra novinom u programskim jezicima koji je naknadno uvode.

10.3.1 Šta je to?

Lambda funkcije zovemo još i neimenovanim jer nemaju ime po kome bismo mogli naknadno da ih identifikujemo.

Primer jedne imenovane, "obične" funkcije:

def inc(x):
    return x + 1

Poziv imenovane funkcije:

inc(12)
13

Korišćenjem lambda izraza istu ovu funkciju možemo definisati na sledeći način:

inc_prim = lambda x: x + 1

I pozivati je na potpuno uobičajen način:

inc_prim(12)
13

Dakle, lambda izraz ima sledeću formulaciju:

lambda param1, param2, param3 ... : izraz

Gde će rezultat izraz-a biti vraćen kao rezultat izvršavanja čitave lambda funkcije.

Na primer izraz x^2 + 3*x + 2 možemo zapisati:

lambda x : x**2 + 3 * x + 2
<function <lambda> at 0x7f6de72afae8>

Međutim ovde lambda izraz nismo vezali ni za kakvo ime pa se ne možemo kasnije referisati na njega.

Ako želimo da definišemo funkciju i na istom mestu joj prosledimo parametre i pozovemo je to možemo uraditi na sledeći način:

(lambda x : x**2 + 3 * x + 2)(2)
12

10.3.2 Funkcije višeg reda

Funkcije višeg reda (Higher-order functions) su one funkcije koje ispunjavaju barem jedan od uslova:

  • Uzimaju jednu ili više funkcija za argumente
  • Vraćaju funkciju kao rezultat
  1. Neke funkcije višeg reda

    Neki standardni problemi u Python-u se idiomatski rešavaju korišćenjem funkcija višeg reda.

    • Sortiranje kolekcija:

      k = [{'ime': "Bananica", 'kalorije': 128},
           {'ime': "Jaffa keks", 'kalorije': 178},
           {'ime': "Munchmallow", 'kalorije': 104},
           {'ime': "Plazma keks",'kalorije': 202}]
      
      sorted(k, key=lambda x: x['kalorije'])
      
      [{'kalorije': 104, 'ime': 'Munchmallow'}, {'kalorije': 128, 'ime': 'Bananica'}, {'kalorije': 178, 'ime': 'Jaffa keks'}, {'kalorije': 202, 'ime': 'Plazma keks'}]
      

      [napomena]
      Svakoj funkciji koja prima neku drugu funkciju kao parametar možete proslediti i imenovanu ("običnu") funkciju.

      def sort_funkcija(x):
          return x['kalorije']
      
      sorted(k, key=sort_funkcija)
      
      [{'kalorije': 104, 'ime': 'Munchmallow'}, {'kalorije': 128, 'ime': 'Bananica'}, {'kalorije': 178, 'ime': 'Jaffa keks'}, {'kalorije': 202, 'ime': 'Plazma keks'}]
      

      Razlog zbog koga se u funkcijama višeg reda češće koriste lambda izrazi je zato što se funkcije koje se prosleđuju kao parametri obično neće više nigde koristiti pa nema ni potrebe da im dajemo ime.

    • Filtriranje kolekcija:

      Želimo da dobijemo samo one slatkiše koji imaju više od 110 kalorija:

      filter(lambda x: x['kalorije'] > 110, k)
      
      <filter object at 0x7f6de71ff828>
      

      O! Ne! Šta je ovo?! Gde je lista?!

      filter funkcija vraća lenju sekvencu kao što npr. radi range koju smo spominjali ranije. Da bismo dobili evaluiranu (već formiranu) listu od lenje sekvence možemo je naterati da se pretvori u listu korišćenjem list funkcije:

      list(filter(lambda x: x['kalorije'] > 110, k))
      
      [{'kalorije': 128, 'ime': 'Bananica'}, {'kalorije': 178, 'ime': 'Jaffa keks'}, {'kalorije': 202, 'ime': 'Plazma keks'}]
      
    • Map funkcija

      map funkcija radi nešto slično kao list comprehensions koji smo pominjali ranije samo što je funkcija višeg reda i što vraća lenju sekvencu.

      map(lambda x: x**2, [1, 2, 3])
      
      <map object at 0x7f6de72040b8>
      
      list(map(lambda x: x**2, [1, 2, 3]))
      
      [1, 4, 9]
      
      nums = [1, 2, 3, 4, 5]
      
      list(map(lambda x: x**2, nums)) == [x**2 for x in nums]
      
      True
      

11 Korisne biblioteke

Da biste instalirali nove Python biblioteke potrebno je da prethodno na Vašem sistemu imate alat koji to ume da radi. Standard u Python-u je pip. Ako ste instalirali Python na neki od standardnih načina onda pip već imate instaliran, potrebno je samo da ga upgrade-ujete.

11.1 tabulate

Instalacija:

pip install tabulate

Korišćenje:

from tabulate import tabulate

headers = [["prvo", "drugo", "treće"]]
tabela = [[11, 12, 13], [21, 22, 23], [31, 32, 33]]
print(tabulate(tabela))
--  --  --
11  12  13
21  22  23
31  32  33
--  --  --
print(tabulate(headers + tabela, headers='firstrow'))
prvo    drugo    treće
------  -------  -------
    11       12       13
    21       22       23
    31       32       33
print(tabulate(headers + tabela, headers='firstrow', tablefmt='grid'))
+--------+---------+---------+
|   prvo |   drugo |   treće |
+========+=========+=========+
|     11 |      12 |      13 |
+--------+---------+---------+
|     21 |      22 |      23 |
+--------+---------+---------+
|     31 |      32 |      33 |
+--------+---------+---------+

Author: Novak Boškov

Created: 2016-12-14 сре 12:46

Comments