Recimo, da delamo nekaj s kraji, opisanimi z imenom, zemljepisnimi koordinatami in številom prebivalcev. Če je teh krajev več, jih nočemo spravljati v posamične spremenljivke, temveč bo vsak kraj opisan s terko.
kraj1 = ("Kremenik", 46.091158, 14.201702, 19)
kraj2 = ("Vogrsko", 45.905166, 13.708147, 908)To je neprikladno: če želimo izračunati razdaljo med tema krajema (kot vemo, je Zemlja ploščata, zato smemo v ta namen uporabiti Pitagorov izrek), moramo pisati
from math import sqrt
sqrt((kraj1[1] - kraj2[1]) ** 2 + (kraj1[2] - kraj2[2]) ** 2)0.527436784922135
Do imena kraja pridemo s kraj1[0], do števila
prebivalcev s kraj1[3].
kraj1[0]'Kremenik'
kraj1[3]19
Neugledno, nerodno, nepraktično, nevarno, ... skratka ne. Veliko raje
bi pisali - kot v vseh normalnih jezikih - kraj1.ime,
kraj1.sirina, kraj1.dolzina in
kraj1.prebivalcev.
Način, na katerega to storimo, se je v Pythonu sčasoma izboljševal. Pokazal bom aktualnega, čeprav bo vključeval uporabo razredov, ki se jih bomo učili šele v zadnjih tednih predavanj.
from typing import NamedTuple
class Kraj(NamedTuple):
ime: str
sirina: float
dolzina: float
prebivalcev: intPo novem shranimo podatke o krajih tako:
kraj1 = Kraj("Kremenik", 46.091158, 14.201702, 19)
kraj2 = Kraj("Vogrsko", 45.905166, 13.708147, 908)Zdaj imamo, kot smo želeli,
kraj1.ime'Kremenik'
kraj1.prebivalcev19
sqrt((kraj1.sirina - kraj2.sirina) ** 2 + (kraj1.dolzina - kraj2.dolzina) ** 2)0.527436784922135
Lepši je tudi izpis
kraj1Kraj(ime='Kremenik', sirina=46.091158, dolzina=14.201702, prebivalcev=19)
Če nas pomuči nostalgija, pa sta to še vedno tudi terki:
kraj1[0]'Kremenik'
kraj1[3]19
ime, sir, vis, preb = kraj1ime'Kremenik'
Poleg imen atributov smo našteli tudi njihove tipe. Snovalci Pythona vestno poudarjajo - in to obljubo bodo težko požrli - da bo označevanje tipov v Pythonu vedno namenjeno le knjižnicam in okoljem, ki nam pomagajo loviti napake, Python sam pa ujemanja tipov nikoli ne bo preverjal.
kraj = Kraj(3.14, True, None, "malo")krajKraj(ime=3.14, sirina=True, dolzina=None, prebivalcev='malo')
Pythonu je vseeno. Naš problem.
Vseeno je lepo, da se tudi mi držimo obljub; če obljubimo, da bo
nekaj int, naj bo int.
Kaj pa, če je lahko int ali pa je neznano, kar bi radi
označili z None?
from typing import NamedTuple, Optional
class Kraj(NamedTuple):
ime: str
sirina: float
dolzina: float
prebivalcev: Optional[int]kraj = Kraj("Stara Gora", 46.656897, 15.637519, None)
krajKraj(ime='Stara Gora', sirina=46.656897, dolzina=15.637519, prebivalcev=None)
Kako povemo, da bo neka vrednost seznam nizov? Ali slovar, katerega ključi bodo cela števila, vrednosti pa, huh, seznami nizov? Označevanje tipov je lahko predmet čisto ločene "lekcije". Na hitro pa le povejmo, kako je s tem. Kot prvo, povemo lahko, da bo nekaj seznam, ne da bi se izjaznili glede tipa elementov.
imena: listČe hočemo povedati, da bo vseboval nize, bomo rekli
imena: list[str]Slovar, katerega ključi so int, vrednosti pa
str, bi bil
po_starosti: dict[int, str]Če so vrednosti seznami nizov, pa
po_starosti: dict[int, list[str]]Samo toliko, na hitro.
Atributi imajo lahko tudi privzete vrednosti.
from typing import NamedTuple, Optional
class Kraj(NamedTuple):
ime: str
sirina: float
dolzina: float
prebivalcev: Optional[int] = Nonekraj = Kraj("Stara Gora", 46.656897, 15.637519)
krajKraj(ime='Stara Gora', sirina=46.656897, dolzina=15.637519, prebivalcev=None)
from typing import NamedTuple, Optional
class Kraj(NamedTuple):
ime: str
sirina: float = 46.119552
dolzina: float = 14.838005
prebivalcev: Optional[int] = Nonekraj = Kraj("Sreda")krajKraj(ime='Sreda', sirina=46.119552, dolzina=14.838005, prebivalcev=None)
Če imamo nek kraj
krajKraj(ime='Sreda', sirina=46.119552, dolzina=14.838005, prebivalcev=None)
in želimo spremeniti katero od njegovih lastnosti, nas bo to spomnilo, da imamo opravka s terkami.
kraj.prebivalcev = 368---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[30], line 1
----> 1 kraj.prebivalcev = 368
AttributeError: can't set attribute
Terke so nespremenljive. Pač pa
kraj._replace(prebivalcev=367)Kraj(ime='Sreda', sirina=46.119552, dolzina=14.838005, prebivalcev=367)
kraj._replace(ime="Vače", prebivalcev=368)Kraj(ime='Vače', sirina=46.119552, dolzina=14.838005, prebivalcev=368)
Terka je še vedno nespremenljiva in zato nespremenjena: kraj je še vedno Sreda.
krajKraj(ime='Sreda', sirina=46.119552, dolzina=14.838005, prebivalcev=None)
Metoda _replace ima nekoliko zavajajoče ime, saj ničesar
ne spremeni, temveč sestavi novo terko. Če bi torej v resnici hoteli
"spremeniti" terko kraj, bi morali -- podobno kot pri
nizih, kjer smo pisali ime = ime.replace("n", "r") --
napisati
kraj = kraj._replace(ime="Vače", prebivalcev=368)krajKraj(ime='Vače', sirina=46.119552, dolzina=14.838005, prebivalcev=368)
NamedTuple in namedtuple.