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: int

Po 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.prebivalcev
19
sqrt((kraj1.sirina - kraj2.sirina) ** 2 + (kraj1.dolzina - kraj2.dolzina) ** 2)
0.527436784922135

Lepši je tudi izpis

kraj1
Kraj(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 = kraj1
ime
'Kremenik'

Tipi atributov

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")
kraj
Kraj(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)

kraj
Kraj(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.

Privzete vrednosti

Atributi imajo lahko tudi privzete vrednosti.

from typing import NamedTuple, Optional

class Kraj(NamedTuple):
    ime: str
    sirina: float
    dolzina: float
    prebivalcev: Optional[int] = None
kraj = Kraj("Stara Gora", 46.656897, 15.637519)

kraj
Kraj(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] = None
kraj = Kraj("Sreda")
kraj
Kraj(ime='Sreda', sirina=46.119552, dolzina=14.838005, prebivalcev=None)

Spreminjanje vrednosti

Če imamo nek kraj

kraj
Kraj(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.

kraj
Kraj(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)
kraj
Kraj(ime='Vače', sirina=46.119552, dolzina=14.838005, prebivalcev=368)

Dokumentacija

NamedTuple in namedtuple.