Kako uporabljamo module

Doslej smo spoznali le prgišče funkcij - input, len, sqrt. V resnici pa skupaj s Pythonom dobimo tisoče funkcij za vse mogoče reči, od pošiljanja pošte in kriptografije do predvajanja zvoka in brskanja po datotekah .zip. Še večja gora funkcij obstaja na spletu: karkoli si zamislite napisati, skoraj gotovo je nekdo - če le gre za dovolj splošno reč - že potreboval kaj takšnega ter to tudi napisal in vam dal na razpolago. Da se ne bi izgubili v gori funkcij (in drugih reči), jih moramo nekako urediti.

Modul je zbirka funkcij (in omenjenih drugih reči). Veliko modulov dobimo že kar ob namestitvi Pythona, druge poberemo z interneta. Da bi lahko uporabljali funkcije iz modula, moramo modul najprej uvoziti, kar storimo z ukazom import, ki mu sledijo imena enega ali več modulov. S Pythonom dobimo, recimo, modul z matematičnimi funkcijami, ki se imenuje math in ga uvozimo takole:

import math

To praviloma storimo na začetku programa. Ko je modul uvožen, kličemo njegove funkcije. Do funkcije, ki se nahaja v modulu, pridemo tako, da napišemo ime modula, piko, in ime funkcije.

>>> math.sqrt(2)
1.4142135623730951
>>> math.log(2.71828)
0.99999932734728203
>>> math.log10(100)
2.0

Primer druge reči, ki je v modulu, so konstante.

>>> math.pi
3.1415926535897931

Drug primer modula je, recimo, modul s funkcijami, ki vračajo naključna števila (in počnejo tudi druge naključne reči), random. Ta ima, recimo, funkcijo randint(a, b), ki vrne naključno število med podanima vrednostima a in b (vključno z b! Tu pravilo o tem, da b ni vključen, ne bi imelo veliko smisla). Še dve zanimivi funkciji sta choice, ki vrne naključno izbrani element seznama, in shuffle, ki naključno premeša elemente seznama.

>>> import random
>>> random.randint(10, 20)
17
>>> random.randint(10, 20)
16
>>> random.randint(10, 20)
10
>>> l = ["Ana", "Berta", "Cilka", "Dani", "Ema", "Fanci"]
>>> random.choice(l)
'Ana'
>>> random.choice(l)
'Dani'
>>> random.shuffle(l)
>>> l
['Ana', 'Fanci', 'Dani', 'Cilka', 'Ema', 'Berta']

random.gauss(mu, sigma) vrača naključna števila iz Gaussove distribucije s podanim poprečjem mu in varianco sigma. Tule je pet števil iz distribucije N(10, 3):

>>> for i in range(5):
... 	print(random.gauss(10, 3), end=" ")
...
8.96174816507 8.79299353551 8.75687382602 9.49106109252 8.21589651224

Poglejmo še en modul, os. Ta vsebuje goro funkcij, povezanih z datotekami in direktoriji (podatki o datotekah, preimenovanje, brisanje), programi, ki tečejo na računalniku in drugo. Tako recimo funkcija os.listdir vrne seznam vseh datotek v direktoriju, katerega ime podamo kot argument.

>>> os.listdir("/Users/janez/Dropbox/Pedagosko/P1/2014/03 seznami")
['blagajna.py', 'palindrom.py', 'stetje.py', 'domaca', 'razpakiranje.py',
'zapiski.html', 'fibo.py', 'seznami.py', 'zip.py']

Funkcija os.remove pobriše datoteko s podanim imenom. Če bi želeli v direktoriju c:\d\kontrabant pobrisati vse datoteke s končnico .pyc, bi to storili takole:

for fname in os.listdir("/Users/janez/Dropbox/Pedagosko/P1/2014/03 seznami"):
    if fname[-4:] == ".py":
        os.remove("/Users/janez/Dropbox/Pedagosko/P1/2014/03 seznami" + fname)

Ob tej priliki povejmo, da ima v normalnih operacijskih sistemih vsak program (bolj učeno: proces) nek "trenutni direktorij". Kadar imena direktorija ne začnemo z / (ali \ ali c: ali čim podobnim na Windowsih), se ime nanaša na datoteke oz. direktorije znotraj tega, trenutnega direktorija. Kakšen je trenutni direktorij, nam pove funkcija getcwd() (get current working directory), spremenimo pa ga z os.chdir. Kar smo počeli zgoraj, bi se lahko napisalo tudi

Vse skupaj bi bilo morda lepše, če bi predtem zamenjali trenutni direktorij.

os.chdir("/Users/janez/Dropbox/Pedagosko/P1/2014/03 seznami")
for fname in os.listdir("."):
    if fname[-4:] == ".py":
        os.remove(fname)

Ko smo ravno pri Windowsih, potarnajmo še, da ima znak \ v nizih v večini programskih jezikov poseben pomen. Danes povejmo le, da moramo, če hočemo dobiti \, napisati \\. Se pravi, ko bomo hoteli reči "c:\Users\janez", bomo morali napisati "c:\\Users\\janez". Na srečo lahko tudi na Windowsih že dolgo uporabljamo sicer običajnejši /.

Če bi datoteke raje preimenovali kot brisali - recimo tako, da bi k njihovem imenu dodali .bak, to storimo z os.rename, ki ji podamo staro in novo ime datoteke.

for fname in os.listdir("."):
    if fname[-4:] == ".py":
        os.rename(fname, fname + ".bak")

Ob modulu os se lahko naučimo še nečesa zanimivega: modul lahko vsebuje celo druge module. Tako modul os vsebuje modul path. Modula path nam ni potrebno uvoziti, dovolj je, da uvozimo os. Lahko pa ga uvozimo tudi takole

import os.path

Razlike pravzaprav (skoraj) ni.

Modul os.path vsebuje različne funkcije povezane z imeni datotek in njihovimi lastnostmi. Zanimiva je, denimo, os.path.splitext, ki ji podamo ime datoteke in vrne terko z dvema elementoma - osnovo imena in končnico. Pri tem ni potrebno, da datoteka v resnici obstaja.

>>> os.path.splitext("/Users/janez/datoteka.txt")
('/Users/janez/datoteka', '.txt')

Ker vemo, da lahko terko razpakiramo v dve spremenljivki, bomo pogosto rekli kar

>>> osnova, koncnica = os.path.splitext("/Users/janez/datoteka.txt")

Gornji programček bi se torej še bolj lepo napisalo takole.

for fname in os.listdir("."):
    if os.path.splitext(fname)[1] == ".py":
        os.rename(fname, fname + ".bak")

Modul os.path ima še kup zanimivih funkcij, recimo funkcijo, ki pove, ali določena datoteka oz. direktorij obstaja (os.path.exists), funkcije, ki povedo ali določeno ime predstavlja datoteko (os.isfile), direktorij (os.isdir), povezavo (os.islink) ali kaj drugega, kar je znano na Unixu, v Windowsih pa ne.

Uvažanje v globalni imenski prostor

Kaj je "imenski prostor" vam še ne nameravam, kaj "globalni", pa še ne morem povedati. Vseeno se lahko naučimo, kako uvažamo vanj.

Stvar je namreč jako preprosta. Morda se mi ne da stalno pisati math.sin, math.cos, math.pi. Če bi raje pisal le sin, cos in pi, bom namesto z

import math

modul uvozil z

from math import sin, cos, pi

S tem povem dve reči. Prva: zanimajo me le tri reči, sin, cos in pi. Ostalih, recimo sqrt, ne potrebujem in naj jih ne uvaža. (Dejanski mehanizem je malo drugačen, tudi sqrt je v resnici uvožen, vendar ga ne vidim, a to ni pomembno.) Druga: nočem, da se funkcije "skrivajo" v modulu math, hočem jih "tu", torej, hočem jih klicati, ne da bi moral prej pisati math..

Pogosto smo leni ali, še pogosteje, ne vemo točno, katere vse funkcije bomo potrebovali, zato rečemo kar

from math import *

Zvezdica pomeni "vse funkcije" in hočemo jih tu, ne v math. Za to obliko uvoza sem vam, da nam je bilo enostavneje, pokazal, ko smo začeli streljati s topom.

Temu načinu uvažanja se načelno izogibamo. Pogosto ga uporabljamo pri modulu math, iz drugih modulov pa uvažamo le posamične funkcije (from random import randint) ali pa pustimo modul kot modul (import random).

Kako pišemo module

Napišimo program, ki bo vseboval konstanto odgovor, ki bo imela vrednost 42, in funkcijo, ki računa Fibonaccijeva števila.

odgovor = 42

def fibonacci(n):
    a = b = 0
    for i in range(n):
        a, b = b, a+b
    return a
Program shranimo pod imenom fibo.py.

To je to. V drugem programu lahko rečemo

import fibo
print("Odgovor je", fibo.odgovor)
print("Deseto Fibonaccijevo število pa je", fibo.fibonacci(10))

Tudi vse ostale finte, na primer, from fibo import odgovor, delujejo.

Modul ni nič drugega kot program, ki ga uvozimo.

Tudi vsi drugi programi, ki ste jih napisali doslej, so hkrati moduli: lahko jih uvozite. Pazite le na tole: ko modul uvozimo, se ta v resnici izvede, čisto tako, kot bi se izvajal program. Vse, kar se v tem programu-modulu definira, ostane definirano in se nahaja v modulovem imenskem prostoru. Vendar se zgodi tudi vse ostalo, kar piše v modulu: če pri izvajanju naleti na print("Foo") se bo ob uvozu modula izpisalo Foo. Če program-modul vsebuje klic funkcije (in ne le definicij), se bo ta funkcija poklicala tudi ob uvozu.

Kje Python išče module? Navadno v trenutnem direktoriju, poleg tega pa še v drugih. Na Windowsih v, recimo, c:\Python34\lib\site-packages. Več o tem si lahko preberete v dokumentaciji.

Tudi sicer ne bomo rinili prav globoko v module, samo še eno stvar omenimo, da vas ne bo presenetila. Ko boste prvič uvozili modul, se bo poleg datoteke s končnico .py pojavila še ena, za enakim imenom, a končnico .pyc. Python si vanjo "prevede" (pravzaprav bi smeli pisati brez narekovajev, temu se v resnici reče prevajanje) v obliko, v kateri ga bo lahko naslednjič hitreje uvozil. Pri manjših modulih se to ne pozna, pri velikem številu velikih modulov pa. Če vas moti, jo lahko pobrišete; drugič se bo pač spet pojavila ponovno.