Print, delo z datotekami, funkcije, moduli

Funkcija print

print predstavlja eno od najbolj pogosto uporabljenih funkcij. Vse podane argumente izpiše, mednje da presledek, na koncu pa gre v novo vrstico.

Poglejmo primer:

In [79]:
print('prvi argument', 'drugi', '=', 5.)
prvi argument drugi = 5.0

Za bolj splošno uporabo glejmo dokumentacijo:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False),

kjer so argumenti:

  • *objects predstavlja argumente, ki jih želimo izpisati (celovitost zvezdice bo jasna pri obravnavi funkcij, spodaj,
  • sep predstavlja delilni niz (angl. separator),
  • end predstavlja zaključni niz,
  • file predstavlja datoteko izpisa (sys.stdout predstavlja standard output, v konkretnem primeru to pomeni zaslon oz ukazna vrstica; pozneje bomo to spremenili),
  • flush v primeru True izpis zaključi (sicer lahko zaradi optimizacije ostane v medpolnilniku, angl. buffer).

Pri argumentu end smo zgoraj uporabili t. i. izhodni znak (angl. escape character): \.

Namen izhodnega znaka je, da sledečemu znaku da poseben pomen, nekatere pogoste uporabe so:

  • \n vstavi prelomi vrstico,
  • \t vstavi tabulator,
  • \b izbriše zadnji znak backspace,
  • \' izpiše enojni narekovaj ',
  • \" izpiše dvojni narekovaj ",
  • \\ prikaže izhodni znak (angl. backslash),
  • \ nova vrstica v večvrstičnem nizu.

Primer (kako ponavadi ne programiramo):

In [2]:
print('pet','et', sep='-', end='=')
print('p:)')
pet-et=p:)

In še primer z izhodnimi znaki:

In [3]:
print('Nizi v Pythonu (po PEP8) naj ne bi bili daljši od 80 znakov. \
Razlog je v tem, da nam ni treba pomikati okna levo in desno. \
Da pa vseeno lahko napišemo nize, ki so daljši od 80 znakov \
Uporabimo enojni delilni znak \\ (bodite pozorni na to, \
kako se izpiše: \\)',)
Nizi v Pythonu (po PEP8) naj ne bi bili daljši od 80 znakov. Razlog je v tem, da nam ni treba pomikati okna levo in desno. Da pa vseeno lahko napišemo nize, ki so daljši od 80 znakov Uporabimo enojni delilni znak \ (bodite pozorni na to, kako se izpiše: \)

Oblikovanje nizov

Pri oblikovanju/formatiranju nizov imamo na voljo več orodij:

Poglejmo si primere:

In [4]:
a = 3.14
print(f'pi = {a}')                # f-niz
print('pi = {a}'.format(a = a))   # .format
print('pi = %(a)3.2f' % {'a': a}) # % 
pi = 3.14
pi = 3.14
pi = 3.14

f-niz je implementiran od Python verzije 3.6 naprej (PEP 498) in predstavlja najbolj enostaven način oblikovanja. Ostala načina navajamo zgolj zato, da se bralec seznani z njimi in da naredimo povezavo na dokumentacijo.

f-niz se vedno začne s f pred prvim narekovajem in omogoča zelo splošno oblikovanje (glejte dokumentacijo).

Vrednost, ki jo želimo znotraj niza oblikovati, damo v zavite oklepaje:

f'besedilo... {ime_arg1:fmt1} ... besedilo...'

V zavitih oklepajih je:

  • pred : podamo ime vrednosti, ki jo želimo oblikovati
  • za : oblikujemo izpis spremenljivke; najpogosteje bomo uporabili fmt oblike:
    • w.df - predstavitev s plavajočo vejico (float)
    • w.de - predstavitev z eksponentom (exponent)
    • w.dg - splošni format (general)
    • ws - niz znakov (string).

w predstavlja (minimalno) skupno širino, d pa število mest za decimalno piko.

Poglejmo si primer:

In [5]:
višina = 1.84
ime = 'Marko'
tekst = f'{ime} je visok {višina} m ali tudi: {višina:e} m, {višina:7.3f} m.'
tekst
Out[5]:
'Marko je visok 1.84 m ali tudi: 1.840000e+00 m,   1.840 m.'

Podobno bi lahko naredili z metodo format in se sklicali na indeks argumenta:

In [6]:
tekst = '{1} je visok {0} m ali tudi: {0:e} m, {0:7.3f} m.'.format(višina, ime)
tekst
Out[6]:
'Marko je visok 1.84 m ali tudi: 1.840000e+00 m,   1.840 m.'

Primer z uporabo slovarja:

In [7]:
parametri = {'visina': 5., 'gostota':100.111, 'ime uporabnika': 'Janko'}
In [8]:
f'višina = {parametri["visina"]:7.3f}, gostota = {parametri["gostota"]:7.3e}'
Out[8]:
'višina =   5.000, gostota = 1.001e+02'

Oblikovanje s fmt (angl. Format Specification Mini-Language) je izredno bogato. Del za dvopičjem je v splošnem (glejte dokumentacijo):

[[fill]align][sign][#][0][width][grouping_option][.precision][type]

kjer so v oglatih oklepajih opcijski parametri (vsi so opcijski):

  • fill je lahko katerikoli znak,
  • align označuje poravnavo, možnosti: "<" | ">" | "=" | "^",
  • sign označuje predznak, možnosti: "+" | "-" | " ",
  • width definira minimalno skupno širino,
  • grouping_option možnosti združevanja, možnosti: "_" | ","
  • precision definira število števk po decimalnem ločilu,
  • type definira, kako naj bodo podatki prikazani, možnosti: "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | ""o" | "s" | "x" | "X" | "%"

Besedili poravnavamo levo z "<", desno z ">" in sredinsko "^". Primer sredinske/desne poravnave (širina 20 znakov):

In [108]:
f'{višina:*^20}' # pred ^ je znak s katerim zapolnimo levo in desno stran
Out[108]:
'********1.84********'
In [10]:
f'{parametri["visina"]:_>20}'
Out[10]:
'_________________5.0'
In [11]:
c = 4-2j
f'Kompleksno število {c} je sestavljeno iz realnega ({c.real})\
in imaginarnega ({c.imag}) dela.'
Out[11]:
'Kompleksno število (4-2j) je sestavljeno iz realnega (4.0)in imaginarnega (-2.0) dela.'

Oblikovanje datuma, ure si lahko pogledate v dodatku spodaj.

Funkcije

V Pythonu je veliko funkcij že vgrajenih (glejte dokumentacijo); funkcije pa lahko tudi napišemo sami ali jih uvozimo iz t. i. modulov (zgoraj smo uvozili datetime, več si bomo pogledali pozneje).

Generični primer funkcije je (dokumentacija):

def ime_funkcije(parametri):
    '''docstring'''
    [koda]
    return vsebina

Pri tem izpostavimo:

  • def označuje definicijo funkcije,
  • ime_funkcije imena funkcije po PEP8 pišemo z malo, večbesedna povežemo s podčrtajem,
  • parametri parametri funkcije, več bomo povedali spodaj,
  • docstring (opcijsko) dokumentacija funkcije,
  • [koda] koda funkcije,
  • return (opcijsko) ukaz za izhod iz funkcije, kar sledi se vrne kot rezultat (če ni return ali po return ni ničesar, se vrne None)

Primer preproste funkcije:

In [12]:
def površina(dolžina, širina):
    S = dolžina * širina
    return S
In [13]:
površina(1, 20)
Out[13]:
20

Posredovanje argumentov v funkcije

Python pozna dva načina posredovanja argumentov:

  • glede na mesto (positional)
  • glede na ime.

Poglejmo si oba načina na primeru:

In [14]:
površina(3, 6) # pozicijsko
Out[14]:
18
In [15]:
površina(širina = 3, dolžina = 6) # glede na ime
Out[15]:
18

Priporočeno je, da argumente posredujemo z imenom; s tem zmanjšamo možnost napake!

Če ne podamo imena, potem se privzame ime glede na vrstni red argumentov; primer:

In [16]:
površina(3, širina=4)
Out[16]:
12

V kolikor mešamo pozicijsko in poimensko posredovanje argumentov, morajo najprej biti navedeni pozicijski argumenti, šele nato poimenski. Pri tem poimenski ne sme ponovno definirati pozicijskega; ker je dolžina prvi argument, bi klicanje take kode:

površina(3, dolžina=4)

povzročilo napako.

Primer dobre prakse pri definiranju argumentov

Pogosto funkcije dopolnjujemo in dodajamo nove argumente ali pa število argumentov funkciji sploh ne moremo vnaprej definirati!

Python s tem nima težav. Argumente, ki niso eksplicitno definirani obvladamo z:

  • *[ime0] terka [ime0] vsebuje vse pozicijske argumente, ki niso eksplicitno definirani,
  • **[ime1] slovar [ime1] vsebuje vse poimenske argumente, ki niso eksplicitno definirani.

Poglejmo primer:

In [17]:
def neka_funkcija(student, izpit = True):
    ''' Prva verzija funkcije'''
    print(f'Študent {student} se je prijavil na izpit: {izpit}.')
In [18]:
neka_funkcija('Janez', izpit=True)
Študent Janez se je prijavil na izpit: True.

Sedaj pa pripravimo drugo, nadgrajeno, verzijo (omogoča enako uporabo kot prva verzija):

In [19]:
def neka_funkcija(*terka, **slovar):
    ''' Druga verzija funkcije '''
    student = terka[0]
    izpit = slovar.get('izpit', True) # True je privzeta vrednost
    izpisi_sliko = slovar.get('izpisi_sliko', False) # False je privzeta vrednost
    
    print(f'Študent {student} se je prijavil na izpit: {izpit}.')
    if izpisi_sliko:
        print('slika:)')
In [20]:
neka_funkcija('Janez', izpit=True)
Študent Janez se je prijavil na izpit: True.
In [21]:
neka_funkcija('Janez', izpit=True, izpisi_sliko=True, neobstoječi_argument='ni')
Študent Janez se je prijavil na izpit: True.
slika:)

Posredovanje funkcij kot argument

Tukaj bi želeli izpostaviti, da so argumenti lahko tudi druge funkcije.

Poglejmo si primer:

In [22]:
def oblika_a(vrednost):
    return f'Prikaz vrednosti: {vrednost}'

def oblika_b(vrednost):
    return f'Prikaz vrednosti: {vrednost:3.2f}'

def izpis(oblika, vrednost):
    print(oblika(vrednost))

Imamo torej dve funkciji oblika_a in oblika_b, ki malenkost drugače oblikujeta numerično vrednost.

Poglejmo uporabo:

In [23]:
izpis(oblika_a, 3)
Prikaz vrednosti: 3
In [24]:
izpis(oblika_b, 3)
Prikaz vrednosti: 3.00

Docstring funkcije

docstring je neobvezen del definicije funkcije, ki dokumentira funkcijo in njeno uporabo. Iz tega stališča je zelo priporočeno, da ga uporabimo.

Dokumentacija za docstring navaja bistvene elemente:

  • prva vrstica naj bo kratek povzetek funkcije,
  • če je vrstic več, naj bo druga prazna (da se vizualno loči prvo vrstico od preostalega teksta),
  • uporabimo tri narekovaje (dvojne " ali enojne '), ki označujejo večvrstični niz črk.

Primer dokumentirane funkcije:

In [25]:
def površina(dolžina = 1, širina = 10):
    """ Izračun površine pravokotnika
    
    Funkcija vrne vrednost površine.
    
    Argumenti:
    dolžina: dolžina pravokotnika
    širina: dolžina pravokotnika
    """
    return dolžina * širina

Primer klica s privzetimi argumenti (med klicem funkcije lahko s pritiskom [shift]+[tab] dostopamo do pomoči):

In [26]:
površina()
Out[26]:
10

Lokalna/globalna imena

Preprosto vodilo pri funkcijah je: funkcija vidi ven, drugi pa ne vidijo noter. Funkcija ima notranja imena, ki se ne prekrivajo z istimi imeni v drugih funkcijah.

Poglejmo si primer:

In [27]:
zunanja = 3
def prva():
    notranja = 5
    print(f'Tole je zunanja vrednost: {zunanja}, tole pa notranja: {notranja}')
In [28]:
prva()
Tole je zunanja vrednost: 3, tole pa notranja: 5

Ker ime notranja zunaj funkcije prva ni definirano, bi klicanje:

notranja

zunaj funkcije vrnilo napako (v funkcijo ne vidimo).

Tukaj velja omeniti, da je posredovanje zunanjih imen v funkcijo mimo argumentov funkcije odsvetovano.

return

Ukaz return vrne vrednosti iz funkcije. Doslej smo spoznali rezultate v obliki ene spremenljivke; ta spremenljivka pa je lahko tudi terka.

Poglejmo si primer:

In [29]:
def vrnem_terko():
    return 1, 2, 3 # to je ekvivalenten zapis za terko: (1, 2, 3)
In [30]:
vrnem_terko()
Out[30]:
(1, 2, 3)

Rezultat funkcije lahko ujamemo tako:

In [31]:
rezultat = vrnem_terko()
rezultat
Out[31]:
(1, 2, 3)

Ali tako, da vrednosti razpakiramo (to sicer velja v splošnem za terke):

In [32]:
i1, i2, i3 = vrnem_terko()
i1 # izpis samo ene vrednosti
Out[32]:
1

Anonimna funkcija/izraz

Anonimna funkcija (glejte dokumentacijo) je funkcija, kateri ne damo imena (zato je anonimna), ampak jo definiramo z ukazom lambda. Tipična uporaba je:

lambda [seznam parametrov]: izraz

lambda funkcijo si bomo pogledali na primeru razvrščanja:

In [33]:
seznam = [-4, 3, -8, 6]

od najmanjše do največje vrednosti. To lahko izvedemo z vgrajeno metodo sort (dokumentacija):

sort(*, key=None, reverse=False)

Metoda ima dva opcijska argumenta:

  • key ključ, po katerem razvrstimo elemente
  • reverse, če je False, se med vrednostmi ključa uporabi < sicer >.

Opomba: list.sort() izvede razvrščanje na obstoječem seznamu in ne vrne seznama. Funkcija sorted() (glejte dokumentacijo) pa vrne seznam urejenih vrednosti.

Poglejmo primer:

In [34]:
seznam = [-4, 3, -8, 6]
seznam.sort()
seznam
Out[34]:
[-8, -4, 3, 6]

Sedaj uporabimo ključ, kjer se bo za primerjavo vrednosti razvrščanja uporabila kvadratna vrednost:

In [35]:
seznam.sort(key = lambda a: a**2)
seznam
Out[35]:
[3, -4, 6, -8]

Delo z datotekami

Delo z datotekami je pomembno, saj podatke pogosto shranjujemo v datoteke ali jih beremo iz njih. Datoteko, iz katere želimo brati ali vanjo pisati, moramo najprej odpreti. To izvedemo z ukazom open (dokumentacija):

open(file, mode='r', buffering=-1, encoding=None,
    errors=None, newline=None, closefd=True, opener=None)

Od vseh argumentov je nujen samo file, ki definira pot do datoteke.

Argument mode je lahko:

  • 'r' za branje (read, privzeto),
  • 'w' za pisanje (write, datoteka se najprej pobriše),
  • 'x' za ekskluzivno pisanje; če datoteka že obstaja vrne napako,
  • 'a' za dodajanje na koncu obstoječe datoteke (append),
  • 'b' binarna oblika zapisa,
  • 't' tekstovna oblika zapisa (privzeto),
  • '+' datoteka se odpre za branje in pisanje.

Zadnji trije zanki ('b', 't', '+') se uporabljajo v kombinaciji s prvimi tremi ('r', 'w', 'x'). Primer: mode='r+b' pomeni branje in pisanje binarne datoteke.

Podrobneje bomo spoznali samo branje in pisanje tekstovnih datotek.

Ostali (opcijski) argumenti funkcije open so podrobno opisani v dokumentaciji.

Privzeto je mode='r' (oz. 'rt', ker je privzet tekstovni način), kar pomeni, da se datoteko odpre za branje v tekstovni obliki.

Poglejmo si najprej pisanje v (tekstovno) datoteko (mode='w'):

In [36]:
datoteka = open('data/prikaz vpisa.txt', mode='w')

'data/prikaz vpisa.txt' predstavlja relativno pot (glede na mesto, kjer se poganja Jupyter notebook) do datoteke.

Zapišimo prvo vrstico s pomočjo metode write:

In [37]:
datoteka.write('test prve vrstice\n')
Out[37]:
18

Metoda write vrne število zapisanih zankov. Če boste v tem trenutku pogledali vsebino datoteke, je velika možnost, da je še prazna. Razlog je v tem, da je vsebina še v medpolnilniku, ki se izprazni na disk samo občasno ali ob zaprtju datoteke (razlog za tako delovanje je v hitrosti zapisa na disk; za podrobnosti glejte dokumentacijo argumenta buffering funkcije open).

Zapišimo drugo vrstico:

In [38]:
datoteka.write('druga vrstica\n', )
Out[38]:
14

Vpišimo sedaj še nekaj številčnih vrednosti:

In [39]:
for d in range(5):
    datoteka.write(f'{d:7.2e}\t {d:9.4e}\n')

Datoteko zapremo:

In [40]:
datoteka.close()

Sedaj datoteko odpremo za branje in pisanje? mode='r+':

In [41]:
datoteka = open('data/prikaz vpisa.txt', mode='r+')

Preverimo vse vrstice s for zanko; v spodnji kodi nam datoteka v vsakem klicu vrne eno vrstico:

In [42]:
for line in datoteka:
    print(line, end='')
test prve vrstice
druga vrstica
0.00e+00	 0.0000e+00
1.00e+00	 1.0000e+00
2.00e+00	 2.0000e+00
3.00e+00	 3.0000e+00
4.00e+00	 4.0000e+00

Pri uvodu v funkcijo print smo omenili argument file, ki definira datoteko, v katero se piše (privzet je standardni izhod oz. zaslon). Če kot argument file posredujemo datoteko, ki je odprta za pisanje:

In [43]:
print('konec', file=datoteka)
datoteka.close()

se vsebina zapiše v datoteko. Rezultat lahko preverite v datoteki!

Tukaj je lepa priložnost, da spoznamo stavek with (dokumentacija). Celovitost stavka with presega namen tega predmeta, je pa zelo preprosta uporaba pri delu z datotekami, zato si poglejmo primer:

In [44]:
with open('data/prikaz vpisa.txt') as datoteka:
    for line in datoteka:
        print(line, end='')
test prve vrstice
druga vrstica
0.00e+00	 0.0000e+00
1.00e+00	 1.0000e+00
2.00e+00	 2.0000e+00
3.00e+00	 3.0000e+00
4.00e+00	 4.0000e+00
konec

Stavek with rezultat funkcije open shrani v (as) ime datoteka, potem pa za vsako vrstico izpišemo vrednosti. Pri izhodu iz stavka with se samodejno pokliče metoda datoteka.close(). Stavek with nam torej omogoča pisanje pregledne in kompaktne kode.

Obravnavanje izjem

Pri programiranju se relativno pogosto pojavijo izjeme; na primer: deljenje z nič.

1/0

---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-50-05c9758a9c21> in <module>()
----> 1 1/0

ZeroDivisionError: division by zero

V primeru izjeme Python opozori na to in prekine izvajanje. Ni dobro, da se program prekine ali zruši; iz tega razloga bi zgornjo situacijo programer začetnik verjetno reševal s stavkom if. Takšen pristop pa ni preveč uporaben niti splošen in zato je v večini modernih programskih jezikih uveljavljeno obravnavanje izjem.

Pogledali si bomo nekatere osnove, ki bodo študentom predvsem olajšale branje in razumevanje kod drugih avtorjev.

Izjeme obvladujemo s stavkom try:

try:
    [koda0]
except:
    [koda1]
finally:
    [koda2]

V stavki try se except izvede, če se v kodi [koda0] pojavi izjema, finally je opcijski in se izvede v vsakem primeru ob izhodu iz stavka try.

Poglejmo primer, ko enostavno nadaljujemo z izvajanjem:

In [45]:
try:
    1/0
except:
    pass

Ukaz pass uporabimo, ko sintaksa zahteva stavek (kodo) za izvajanje, vendar ne želimo nobenega ukrepa (glejte dokumentacijo).

S pomočjo except lahko lovimo tudi različne izjeme, to naredimo tako:

In [46]:
try:
    1/0 
except ZeroDivisionError:
    print('Deljenje z ničlo!') # ali kakšna druga akcija
except:
    print('Nepričakovana napaka.') # odsvetovano; dobro je predvideti napako!
Deljenje z ničlo!

Sedaj uporabimo isto kodo za primer deljenja števila z nizom:

In [47]:
try:
    1/'to pa ne gre'
except ZeroDivisionError:
    print('Deljenje z ničlo!') # ali kakšna druga akcija
except:
    print('Nepričakovana napaka.') # odsvetovano; dobro je predvideti napako!
Nepričakovana napaka.

Proženje izjem

Izjeme pa lahko tudi sami prožimo; to naredimo z ukazom raise (dokumentacija).

Primer proženja napake je:

raise Exception('Opis izjeme')

preprost, vendar celovit, primer, ko bi pričakovali, da je ime_osebe tipa str:

In [48]:
ime_osebe = 1
try:
    if type(ime_osebe) != str:
        raise Exception(f'Ime ni pričakovanega tipa `str`.')
except Exception as izjema:
    print(izjema)
Ime ni pričakovanega tipa `str`.

Izpeljevanje seznamov

Pri programiranju se pogosto srečujemo s tem, da moramo izvajati operacije na posameznem elementu seznama. Predhodno smo že spoznali zanko for, ki jo lahko uporabimo v takem primeru.

Oglejmo si primer, kjer izračunamo kvadrat števil:

In [49]:
seznam = [1, 2, 3] # izvorni seznam
rezultat = [] # pripravimo prazen seznam v katerega bomo dodajali rezultate
for element in seznam:
    rezultat.append(element**2)
rezultat
Out[49]:
[1, 4, 9]

Mnogo enostavneje lahko isti rezultat dosežemo s t. i. izpeljevanjem seznamov (angl. list comprehensions, glejte dokumentacijo).

Zgornji primer bi bil:

In [50]:
seznam = [1, 2, 3] # izvorni seznam
rezultat = [element**2 for element in seznam] # <<< na desni strani = je izpeljevanje seznamov
rezultat
Out[50]:
[1, 4, 9]

Izpeljevanje seznamov predstavlja koda [element**2 for element in seznam], ki v bistvu pove to, kar je napisano: izračunaj kvadrat za vsak element v seznamu. Da je rezultat seznam, nakazujejo oglati oklepaji.

Izpeljevanje seznamov ima sledečo sintakso:

[koda for element in seznam]

in ima predvsem dve prednosti:

  • preglednost / kompaktnost kode in
  • malo hitrejše izvajanje.

Preglednost/kompaktnost smo lahko že presodili. Kako preverimo hitrost? Najlažje to naredimo s t. i. magičnim ukazom %%timeit, torej meri čas (glejte dokumentacijo); spodaj bomo uporabili blokovni magični ukaz (označuje ga %%):

%%timeit -n1000

kjer parameter -n1000 omejuje merjenje časa na 1000 ponovitev.

Najprej daljši način:

In [51]:
seznam = range(1000)
In [52]:
%%timeit -n1000
rezultat = []
for element in seznam:
    rezultat.append(element**2)
232 µs ± 7.77 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Nato izpeljevanje seznamov (bolj pregledno in malenkost hitreje):

In [53]:
%%timeit -n1000
rezultat = [element**2 for element in seznam]
204 µs ± 4.66 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Poglejmo si še dva uporabna primera:

  • uporabo izraza if za izračun nove vrednosti,
  • uporabo izraza if, če se nova vrednost sploh izračuna.

Pri izračunu nove vrednosti izraz if vstavimo v del pred for:

In [54]:
seznam = [1, 2, 3]
[el+10 if el<2 else el**2 for el in seznam]
Out[54]:
[11, 4, 9]

Pogosto pa za določene elemente izračuna sploh ne želimo izvesti, v tem primeru izraz if vstavimo za seznam:

In [55]:
[el**2 for el in seznam if el >1]
Out[55]:
[4, 9]

Osnove modulov

Z moduli lahko na enostaven način uporabimo že napisano kodo. Preprosto povedano, moduli nam v Pythonu omogočajo relativno enostavno ohranjanje preglednosti in reda. Ponavadi v modul zapakiramo neko zaključeno funkcijo, skupino funkcij ali npr. objekt (pozneje bomo spoznali, kaj je objekt).

Za podroben opis uvažanja modulov glejte dokumentacijo.

V mapi moduli se nahaja datoteka prvi_modul.py, ki ima sledečo vsebino:

def kvadrat(x=1):
    return x**2

Pomembno: pogosto module hierarhično razporejamo po direktorijih. Samo če je v določenem direktoriju datoteka (lahko tudi prazna) __init__.py, potem se tak direktorij obnaša kot modul. V zgornjem primeru taka datoteka obstaja in posledično se mapa moduli obnaša kot modul, v tem modulu je pa podmodul prvi_modul (datoteka pa je prvi_modul.py).

Module tipično uvažamo na dva načina:

Prvi način:

from moduli import prvi_modul

v tem primeru uvozimo imenski prostor prvi_modul; to pomeni, da funkcijo v kvadrat() kličemo tako: prvi_modul.kvadrat().

Drugi način uvažanja modula:

import moduli

Uvozimo imenski prostor moduli; to pomeni, da funkcijo kvadrat() kličemo tako: moduli.prvi_modul.kvadrat().

V kolikor želimo uvoziti funkcije v modulu prvi_modul, to naredimo tako:

In [56]:
from moduli import prvi_modul

Sedaj lahko funkcijo kvadrat kličemo tako:

In [57]:
prvi_modul.kvadrat(5)
Out[57]:
25

Z ukazom from lahko uvozimo samo del modula. Primer:

In [58]:
from moduli.prvi_modul import kvadrat

Sedaj kličemo neposredno funkcijo kvadrat:

In [59]:
kvadrat(6)
Out[59]:
36

Funkcije ali module lahko tudi preimenujemo. Primer:

In [60]:
from moduli.prvi_modul import kvadrat as sqr
sqr(7)
Out[60]:
49

Kje Python išče module?

  1. V trenutni mapi (to je tista, v kateri ste prožili jupyter notebook),
  • v mapah, definiranih v PYTHONPATH,
  • v mapah, kjer je nameščen Python (poddirektorij site-packages).

Uvoz modulov in Jupyter notebook

V tej knjigi bomo, zaradi pedagoških razlogov, module vedno uvažali takrat, ko jih bomo prvič potrebovali. Pravila dobre prakse in pregledne kode priporočajo, da se uvoze vseh modulov izvede na vrhu Jupyter notebooka!

Modul pickle

Modul pickle bomo velikokrat uporabljali za zelo hitro in kompaktno shranjevanje in branje podatkov. pickle vrednosti spremenljivke shrani v taki obliki, kot so zapisane v spominu. V primeru racionalnih števil tako ne izgublja na natančnosti (zapis v tekstovno obliko izgublja natančnost, saj definiramo število izpisanih števk, ki pa je lahko manjše od števila decimalnih mest v spominu). Poleg tega pa je zapis v spominu ponavadi manj zahteven kakor zapis v tekstovni obliki. Ker vrednosti ni treba pretvarjati v/iz tekstovno/e obliko/e, je pisanje/branje tudi zelo hitro! Tukaj si bomo pogledali osnovno uporabo, celovito je pickle opisan v dokumentaciji.

pickle ima dve pomembni metodi:

  • dump(obj, file, protocol=None, *, fix_imports=True) za shranjevanje objekta obj,
  • load(file, *, fix_imports=True, encoding="ASCII", errors="strict") za brane datoteke file.

Uporabo si bomo pogledali na primeru. Najprej uvozimo modul:

In [61]:
import pickle

Zapišimo vrednosti slovarja a v datoteko data/stanje1.pkl:

In [62]:
a = {'število': 1, 'velikost': 10}

with open('data/stanje1.pkl', 'wb') as datoteka:
    pickle.dump(a, datoteka, protocol=-1) # pazite: uporabite protocol=-1, da boste uporabili zadnjo verzijo

Opazimo, da so datoteko odprli za pisanje v binarni obliki wb. Pomembno je tudi, da uporabimo zadnjo verzijo protokola (parameter protocol); privzeta ni optimirana za hitrost in ne zapiše v binarni obliki.

Sedaj vrednosti preberimo nazaj v b in ga prikažemo:

In [63]:
with open('data/stanje1.pkl', 'rb') as datoteka:
    b = pickle.load(datoteka)

b
Out[63]:
{'število': 1, 'velikost': 10}

Zgoraj smo trdili, da je pristop zelo hiter; preverimo to.

Najprej pripravimo podatke (seznam 50000 vrednosti tipa ind):

In [64]:
data = list(range(50000))

Pisanje v tekstovni obliki

In [65]:
%%timeit
datoteka = open('data/data.txt', mode='w')
for d in data:
    datoteka.write(f'{d:7.2e}\n' )
datoteka.close()
78.5 ms ± 3.61 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

Pisanje s pomočjo pickle:

In [66]:
%%timeit
with open('data/data.pkl', 'wb') as datoteka:
    pickle.dump(data, datoteka, protocol=-1) 
1.37 ms ± 157 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Gre skoraj za 20 kratno pohitritev!

Nekaj vprašanj za razmislek!

  1. Odprite datoteko za zapis in vanjo vpišite formatirani (skupaj 7 mest, 3 decimalna mesta) seznam v dveh stolpcih. Razločevalni znak med stolpci naj bo prazen znak ' '.
  2. Odprite pripeto datoteko zapis_labview.lvm (gre za zapis iz programa LabView) in preberite glavo v obliki slovarja ('ime polja': vrednost).
  3. V datoteki iz prejšnje točke preberite podatke (uporabite funkcijo replace() za zamenjavo decimalne vejice v piko in nato pretvorite niz v število. Najdite ustrezno dokumentacijo/help).
  4. Na poljubnem seznamu besed prikažite uporabo izpeljevanja seznamov tako, da zamenjate poljuben samoglasnik.
  5. Napišite funkcijo. Če se v funkcijo vstavi seznam besed, potem naj vrne dolžino besed. Če se v funkcjo vstavi seznam numeričnih vrednosti, potem naj vrne njihovo vrednost povečano za ena.
  6. Zgornjo funkcijo nadgradite: če se v funkcijo vstavi prazen seznam (dolžine 0), potem naj sproži izjemo Exception z ustreznim opisom.
  7. Pripravite novo funkcijo, ki bo klicala funkcijo iz točke 6 in lovila izjeme.
  8. Za obe funkciji pripravite docstring.
  9. Prikažite uporabo argumentov s privzetimi vrednostmi.
  10. Prikažite uporabo argumentov glede na mesto in glede na ime.
  11. V funkcijo pošljite nepredvidene vrednosti (nize in numerične vrednosti).
  12. V funkcijo pošljite nepredvidene poimenske vrednosti.
  13. Definirajte lambda funkcijo in jo uporabite v povezavi s funkcijo max ali min.
  14. Zgornji funkciji shranite v modul poljubnega imena (npr.: prve_funkcije.py).
  15. Uvozite samo eno od funkcij kot funkcijo z novim imenom (drugačno ime od izvornega).
  16. Poljuben slovar shranite in odprite s pickle.

Dodatno

Oblikovanje datuma in časa

Najprej uvozimo modul za delo z datumom in časom datetime in prikažemo trenutni čas z datumom (uporabimo funkcijo now()):

In [67]:
import datetime
sedaj = datetime.datetime.now()
sedaj
Out[67]:
datetime.datetime(2020, 10, 11, 12, 53, 14, 735309)

Podatek o času oblikujemo:

In [68]:
f'{sedaj:%Y-%m-%d %H:%M:%S}'
Out[68]:
'2020-10-11 12:53:14'

Oblikovanje datuma in ure sledi metodam:

  • strftime (dokumentacija) se uporablja za pretvorbo iz datetime v niz črk,
  • strptime (dokumentacija) se uporablja za pretvorbo iz niza črk v tip datetime.

Kako izpisati ustrezno obliko (torej kako uporabiti znake %Y, %m itd.) je prav tako podrobno pojasnjeno v dokumentaciji.

Poglejmo si primer pretvorbe tipa datetime v niz (glede na zgoraj smo dodali še %a, da se izpiše ime dneva):

In [69]:
sedaj.strftime('%Y-%m-%d %H:%M:%S %a')
Out[69]:
'2020-10-11 12:53:14 Sun'

Sedaj v obratno smer, torej iz niza v datetime:

In [70]:
datetime.datetime.strptime('2017-09-28 05:31:45', '%Y-%m-%d %H:%M:%S')
Out[70]:
datetime.datetime(2017, 9, 28, 5, 31, 45)

Uporabljajte www.stackoverflow.com!

Nekateri moduli

  1. openpyxl za pisanje in branje Excel xlsx/xlsm datotek: vir,
  • Regularni izrazi (zelo močno orodje za iskanje po nizih): vir.

Modul sys

Python ima veliko število vgrajenih modulov (in še veliko se jih lahko prenese s spleta, npr. tukaj), ki dodajo različne funkcionalnosti.

Modul sys omogoča dostop do objektov, ki jih uporablja interpreter.

Poglejmo si primer; najprej uvozimo modul:

In [71]:
import sys

Mape, kjer se iščejo moduli, lahko preverimo z ukazom sys.path (dokumentacija):

In [72]:
sys.path
Out[72]:
['c:\\_j\\Work Janko\\Pedagosko delo\\_predavanja\\Programiranje in numerične metode\\pypinm',
 'c:\\_j\\work janko\\pedagosko delo\\_predavanja\\programiranje in numerične metode\\pypinm\\venv\\scripts\\python38.zip',
 'c:\\Users\\slavic\\AppData\\Local\\Programs\\Python\\Python38\\DLLs',
 'c:\\Users\\slavic\\AppData\\Local\\Programs\\Python\\Python38\\lib',
 'c:\\Users\\slavic\\AppData\\Local\\Programs\\Python\\Python38',
 'c:\\_j\\work janko\\pedagosko delo\\_predavanja\\programiranje in numerične metode\\pypinm\\venv',
 '',
 'c:\\_j\\work janko\\pedagosko delo\\_predavanja\\programiranje in numerične metode\\pypinm\\venv\\lib\\site-packages',
 'c:\\_j\\work janko\\pedagosko delo\\_predavanja\\programiranje in numerične metode\\pypinm\\venv\\lib\\site-packages\\win32',
 'c:\\_j\\work janko\\pedagosko delo\\_predavanja\\programiranje in numerične metode\\pypinm\\venv\\lib\\site-packages\\win32\\lib',
 'c:\\_j\\work janko\\pedagosko delo\\_predavanja\\programiranje in numerične metode\\pypinm\\venv\\lib\\site-packages\\Pythonwin',
 'c:\\_j\\work janko\\pedagosko delo\\_predavanja\\programiranje in numerične metode\\pypinm\\venv\\lib\\site-packages\\IPython\\extensions',
 'C:\\Users\\slavic\\.ipython']

Modul os

Modul os je namenjen delu z operacijskim sistemom in skrbi za združljivost z različnimi operacijskimi sistemi (če npr. napišete program na sistemu Windows, bo delal tudi na sistemu linux).

Uvozimo modul:

In [73]:
import os

Pogljemo trenutno mapo:

In [74]:
os.path.curdir
Out[74]:
'.'

ali trenutno mapo v absolutni obliki:

In [75]:
os.path.abspath(os.path.curdir)
Out[75]:
'c:\\_j\\Work Janko\\Pedagosko delo\\_predavanja\\Programiranje in numerične metode\\pypinm'

Kako najdemo vse datoteke in mape v direktoriju?

In [76]:
seznam = os.listdir()

Prikažimo prve štiri s končnico ipynb:

In [78]:
i = 0
for ime in seznam:
    if os.path.isfile(ime):
        if ime[-5:] == 'ipynb':
            print(f'Našel sem datoteko: {ime:s}')
            i +=1
        if i>3:
            break
Našel sem datoteko: NM2020.ipynb
Našel sem datoteko: PiNM2019-20.ipynb
Našel sem datoteko: Predavanje 01 - Uvod v Python.ipynb
Našel sem datoteko: Predavanje 02 - Print, delo z datotekami, funkcije, moduli.ipynb

Za več glejte dokumentacijo.