Vzemimo, spet, podatke o kolesarjenju, tiste, s štirimi stolpci. Za zdaj smo ugotovili le, kolikšni sta skupna prevožena razdalja in višina, podatek o kolesu pa kar zanemarili. Lahko bi izračunali, recimo, koliko je prevozil s Canyonom.
= 0
vozenj_canyon = 0
razdalja_canyon = 0
visina_canyon
for vrstica in open("kolesa.txt"):
= vrstica.split(",")
kolo, razdalja, visina if kolo == "Canyon":
+= 1
vozenj_canyon += int(razdalja)
razdalja_canyon += int(visina)
visina_canyon
print("Voženj:", vozenj_canyon)
print("Razdalja:", razdalja_canyon)
print("Višina:", visina_canyon)
Voženj: 28
Razdalja: 2932
Višina: 25630
Zdaj pa isto še za Cube, Nakamura in Stevens. Če damo to v isti program, bomo imeli štirikar po tri spremenljivke.
Primeri pa se lahko še kaj hujšega: v družini nas je pet in imamo osem koles. (Štirje po enega in eden štiri.)
Kako lepo bi bilo, če bi lahko imeli eno samo spremenljivko
vozenj
, ki bi imela "predalčke" Canyon, Cube, Nakamura in
Stevens. In, seveda, spremenljivki razdalja
in
visina
, vsako s štirimi predalčki.
Očitno to obstaja, sicer si tega ne bi želel, ne? Takšnim
spremenljivkam - točneje, taki vrsti spremenljivk rečemo slovar - po
angleško dictionary, ime Pythonovega podatkovnega tipa pa je
dict
.
Za začetek pripravimo nekaj preprostejšega: slovar z višinami hribov. Takšen bi bil:
= {"Triglav": 2864, "Storžič": 2132, "Grintovec": 2558} hribi
Slovar hribi ima predalčke "Triglav"
,
"Storžič"
in "Grintovec"
, vrednosti, shranjene
v teh predalčkih pa so 2864, 2132 in 2558. Kot vidimo, slovar pripravimo
tako, da med zavite oklepaje zapišemo pare z imenom predalčka (ki mu
učeno rečemo ključ, key) in pripadajočimi
vrednostmi (value), ločimo pa jih z dvopičji.
Python nam seveda lahko izpiše celotno vsebino slovarja.
hribi
{'Triglav': 2864, 'Storžič': 2132, 'Grintavec': 2558}
Do posameznih vrednosti v slovarju pridemo tako, da "predalček" podamo v oglatih oklepajih.
"Triglav"] hribi[
2864
"Storžič"] hribi[
2132
Če kak ključ ne obstaja, Python to jasno pove.
"Stol"] hribi[
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
Cell In[6], line 1
----> 1 hribi["Stol"]
KeyError: 'Stol'
Predalčki se vedejo kot spremenljivke. Triglavu lahko priredimo drugo višino
"Triglav"] = 2800 hribi[
hribi
{'Triglav': 2800, 'Storžič': 2132, 'Grintavec': 2558}
Lahko mu tudi prištejemo (zdaj manjkajočih) 64 metrov.
"Triglav"] += 64 hribi[
hribi
{'Triglav': 2992, 'Storžič': 2132, 'Grintavec': 2558}
Skratka, kot katerakoli druga spremenljivka.
Oboroženi z novim znanjem izračunajmo kolesarsko statistiko.
= {"Canyon": 0, "Cube": 0, "Nakamura": 0, "Stevens": 0}
vozenj = {"Canyon": 0, "Cube": 0, "Nakamura": 0, "Stevens": 0}
razdalje = {"Canyon": 0, "Cube": 0, "Nakamura": 0, "Stevens": 0}
visine
for vrstica in open("kolesa.txt"):
= vrstica.split(",")
kolo, razdalja, visina += 1
vozenj[kolo] += int(razdalja)
razdalje[kolo] += int(visina) visine[kolo]
Pa zdaj? No, to kar potrebujemo je v slovarjih.
vozenj
{'Canyon': 28, 'Cube': 43, 'Nakamura': 29, 'Stevens': 0}
razdalje
{'Canyon': 2932, 'Cube': 2939, 'Nakamura': 488, 'Stevens': 0}
visine
{'Canyon': 25630, 'Cube': 70947, 'Nakamura': 1499, 'Stevens': 0}
Kaj pa, če bi radi to lepo izpisali? Recimo podatke za vsako kolo?
Čim rečemo "za vsako" nas to spomni na ... zanko for
.
Zanko for
smo doslej naganjali prek datotekm, ko smo morali
ponoviti kos programa za vsako vrstico datoteke. Kaj se zgodi,
če jo poskusimo spustiti čez slovar?
for bogvekajboto in vozenj:
print(bogvekajboto)
Canyon
Cube
Nakamura
Stevens
Zanka for
daje ključe slovarja. Prav, če imamo ključ,
potem že znamo priti do vrednosti.
for kolo in vozenj:
print(kolo, "-", vozenj[kolo], "vozenj")
Canyon - 28 vozenj
Cube - 43 vozenj
Nakamura - 29 vozenj
Stevens - 0 vozenj
Ker imajo vsi trije slovarji tako ali tako iste ključe, lahko mimogrede izpišemo še skupno prevoženo višino in razdaljo.
for kolo in vozenj:
print(kolo, "-", vozenj[kolo], "vozenj,", razdalje[kolo], "km,", visine[kolo], "m.")
Canyon - 26 vozenj, 2766 km, 26392 m.
Cube - 43 vozenj, 3174 km, 66705 m.
Nakamura - 22 vozenj, 439 km, 1119 m.
Stevens - 9 vozenj, 607 km, 3395 m.
Nadvse imenitno. No, kar imenitno. Nad vse(m) imenitno pa še ni, ker bo postalo še imenitnejše. :)
Program se začne z
vozenj = {"Canyon": 0, "Cube": 0, "Nakamura": 0, "Stevens": 0}
razdalje = {"Canyon": 0, "Cube": 0, "Nakamura": 0, "Stevens": 0}
visine = {"Canyon": 0, "Cube": 0, "Nakamura": 0, "Stevens": 0}
Tule je videti nekaj prostora za izboljšave. Kot prvo: moramo res tipkati vsa ta imena koles in ničle? Ne bi šlo brez?
Kot drugo: da to deluje, moramo vnaprej vedeti, katere znamke koles se bodo pojavile v datoteki. Kaj, če to ni vnaprej znano? Kako bi programirali v tem primeru?
Naučiti se bomo morali tri stvari:
Vse troje se bo izkazalo za čisto preprosto.
Kako sestaviti prazen slovar, zlahka uganemo. Pač ničesar ne zapišemo med oklepaje.
= {} vozenj
vozenj
{}
Kako preveriti, ali slovar vsebuje določen ključ? Besedo
in
smo doslej uporabljali v zanki for
, ko smo
pisali, recimo for kolo in vozenj
. Lahko jo tudi v
if
, oziroma, brez if
, v logičnem izrazu, kot
operator. Operator a in c
vrne True
, če
c
vsebuje a
.
Še vedno imamo pri roki
razdalje
{'Canyon': 2766, 'Cube': 3174, 'Nakamura': 439, 'Stevens': 607}
Vidimo, da slovar vsebuje "Canyon"
, ne pa
"Focus"
. Operator in
se s tem videnjem
strinja.
"Canyon" in razdalje
True
"Focus" in razdalje
False
Odlično, znamo preveriti, ali predalček obstaja. Kljukica pod točko 2.
Zdaj pa še dodajanje predalčka.
"Focus"] = 0 razdalje[
razdalje
{'Canyon': 2766, 'Cube': 3174, 'Nakamura': 439, 'Stevens': 607, 'Focus': 0}
"Šmarna gora"] = 667 hribi[
hribi
{'Triglav': 2992, 'Storžič': 2132, 'Grintavec': 2558, 'Šmarna gora': 667}
Pa imamo boljši program za kolesarsko statistiko.
= {}
vozenj = {}
razdalje = {}
visine
for vrstica in open("kolesa.txt"):
= vrstica.split(",")
kolo, razdalja, visina if not kolo in vozenj:
= razdalje[kolo] = visine[kolo] = 0
vozenj[kolo] += 1
vozenj[kolo] += int(razdalja)
razdalje[kolo] += int(visina)
visine[kolo]
for kolo in vozenj:
print(kolo, "-", vozenj[kolo], "vozenj,", razdalje[kolo], "km,", visine[kolo], "m.")
Nakamura - 22 vozenj, 439 km, 1119 m.
Cube - 43 vozenj, 3174 km, 66705 m.
Canyon - 26 vozenj, 2766 km, 26392 m.
Stevens - 9 vozenj, 607 km, 3395 m.
Ker vsi trije slovarji bodisi vsebujejo bodisi ne vsebujejo kolesa,
preverjamo le, ali je kolo že v slovarju vozenj
. Če ga ni
tam, ga tudi v ostalih ni, torej ga dodamo v vse.
Ker se not kolo in vozenj
bere čudno, obstaja poleg
operatorja in
tudi operator not in
.
"Scott" not in vozenj
True
Ključi gornjega slovarja ("imena predalčkov") so bili nizi, vrednosti
pa števila, int
. Slovarji niso omejeni zgolj na te
tipe.
Vrednosti so lahko karkoli: ne le int
in
float
, temveč tudi nizi, logične vrednosti in vsi
raznorazni tipi, ki jih bomo še spoznali pri predmetu. Slovar lahko
vsebuje celo druge slovarje (uh, celo samega sebe!) kot vrednost.
Ključi so bolj omejeni. Ključi morajo biti nespremenljivi. Za naše potrebe in naše dosedanje znanje: ključ je lahko karkoli razen slovarja. "Prepovedanim" tipom ključev se bosta kmalu pridružila še seznam in množica, drugih prepovedanih tipov ključev pa pri tem predmetu ne boste spoznali. Vsaj ne takšnih, ki bi vas jih zamikalo uporabiti kot ključ.
Pri tem predmetu bodo ključi skoraj vedno nizi, včasih
int
-i in skoraj nikoli nič drugega.
Isti slovar ima lahko različne tipe ključev. In različne tipe vrednosti.
"Ana": 129415, 4: True, 3.14: {}} {
{'Ana': 129415, 4: True, 3.14: {}}
Če se komu zdi to slaba ideja, bo to zato, ker je dejansko slaba. Navadno bodo vsi ključi nekega slovarja in vse vrednosti nekega slovarja enakega tipa. Sicer ... no, kaj pravzaprav shranjujemo v slovarju, če se v njem pojavlja tako raznovrstna šara?!
Vsaka stvar v Pythonu ima metode. Niz, kot smo rekli, ducate. Slovarji jih imajo malo manj, a vseeno jih ne bomo našteli vseh, saj si jih tako ali tako ne bi zapomnili. Bomo že sproti videli, kar bomo potrebovali. (Kdor je neučakan, pa lahko pogleda dokumentacijo).
Vzemimo slovar visine
. Kakšna je skupna višina, ki jo je
prevozil kolesar?
visine
{'Nakamura': 1119, 'Cube': 66705, 'Canyon': 26392, 'Stevens': 3395}
1119 + 66705 + 26392 + 3395
97611
Ne, nisem mislil tako. Ne bomo seštevali ročno. Kar program naj sešteje.
= 0
skupna for kolo in visine:
+= visine[kolo]
skupna
skupna
97611
Če razmislimo, nas ključi tu pravzaprav ne zanimajo. Zanimajo nas le
vrednosti. Tule bi nam prišlo bolj prav, če bi šla zanka
for
prek vrednosti, ne prek ključev.
Ni problema. Slovarji imajo metodo values
, ki vrne
vrednosti.
for visina in visine.values():
print(visina)
1119
66705
26392
3395
In to seveda znamo sešteti.
= 0
skupna for visina in visine.values():
+= visina
skupna
skupna
97611
Prej ko slej boste odkrili, da obstaja funkcija sum
, ki
lahko sešteje, kar ji podamo kot argument. Če ji podamo
visine.values()
bo seštela vse vrednosti v slovarju.
sum(visine.values())
97611
Torej:
print("Število voženj:", sum(vozenj.values()))
print("Skupna razdalja:", sum(razdalje.values()))
print("Skupna višina:", sum(visine.values()))
Število voženj: 100
Skupna razdalja: 6986
Skupna višina: 97611
Slovarji imajo metodo setdefault(kljuc, vrednost)
, ki za
podani ključ nastavi podano vrednost; če ključ že obstaja, pa ne stori
ničesar. V vsakem primeru pa vrne vrednost, ki pripada temu ključu -
bodisi obstoječo (in torej nespremenjeno) vrednost, bodisi pravkar
nastavljeno novo vrednost.
vozenj
{'Nakamura': 22, 'Cube': 43, 'Canyon': 26, 'Stevens': 9}
"Scott", 0) vozenj.setdefault(
0
vozenj
{'Nakamura': 22, 'Cube': 43, 'Canyon': 26, 'Stevens': 9, 'Scott': 0}
"Nakamura", 0) vozenj.setdefault(
22
vozenj
{'Nakamura': 22, 'Cube': 43, 'Canyon': 26, 'Stevens': 9, 'Scott': 0}
To poenostavi naše računanje.
= {}
vozenj = {}
razdalje = {}
visine
for vrstica in open("kolesa.txt"):
= vrstica.split(",")
kolo, razdalja, visina 0)
vozenj.setdefault(kolo, 0)
razdalje.setdefault(kolo, 0)
visine.setdefault(kolo, += 1
vozenj[kolo] += int(razdalja)
razdalje[kolo] += int(visina)
visine[kolo]
for kolo in vozenj:
print(kolo, "-", vozenj[kolo], "vozenj,", razdalje[kolo], "km,", visine[kolo], "m.")
Nakamura - 22 vozenj, 439 km, 1119 m.
Cube - 43 vozenj, 3174 km, 66705 m.
Canyon - 26 vozenj, 2766 km, 26392 m.
Stevens - 9 vozenj, 607 km, 3395 m.
Meh, ali pa tudi ne. Stavek if
in malo prirejanja,
if kolo not in vozenj:
vozenj[kolo] = razdalje[kolo] = visine[kolo] = 0
smo zamenjali s tremi vrsticami setdefault
-ov. No, če bi
imeli en sam slovar, pa je
d.setdefault(x, 0)
gotovo krajše in preprostejše kot
if x not in d:
d[x] = 0
Za metodo get
vam moram povedati preprosto zato, ker je
to ena od stvari, za katere je boljše, da o njej slišite od modrih,
starejših ljudi, kot od vrstnikov.
Metoda get
vrne vrednost, ki pripada podanemu
ključu.
visine
{'Nakamura': 1119, 'Cube': 66705, 'Canyon': 26392, 'Stevens': 3395}
"Cube") visine.get(
66705
Ne počnite tega. V Pythonu se reče
"Cube"] visine[
66705
V vaših programih bom sicer žalostno opazoval takšno uporabo
get
-a. Do nje bo mnogokrat prišlo, ker vam bodo pri
reševanju nalog "pomagali" znanci, ki sicer programirajo v Javi ali
podobno okornih jezikih, ki zaradi določenih omejitev ne morajo do
vsebine slovarjev z oglatimi oklepaji.
Zakaj Python potem sploh ima get
, če naj bi ga ne
uporabljali?
Metodi get
lahko podamo privzeto vrednost, ki naj jo
vrne, če ključ ne obstaja.
"Cube", 0) visine.get(
66705
"Focus", 0) visine.get(
0
Zato in samo zato.
items
Videli smo, da gre zanka for
, ko jo spustimo čez slovar,
prek njegovih ključev. Če jo naženemo prek tega, kar vrne
values
, bo šla prek vrednosti. Slovarji imajo še metodo
items
, ki vrača pare ključev in vrednosti.
Vrnimo se malenkost nazaj. Spomnimo se, kako smo uporabljali rezultat
split
: ker je vračal več stvari, smo ga prirejali več
spremenljivkam. Recimo, da imamo niz z dvema besedama,
"Ana Benjamin"
in ga razdelimo s split
. Dobimo
dve besedi, torej ju moramo shraniti v dve spremenljivki.
= "Ana Benjamin".split() ona, on
ona
'Ana'
on
'Benjamin'
Če metoda items
vrača pare, bo tudi zanka
for
potrebovala dve spremenljivki, da ga shrani.
for kolo, visina in visine.items():
print(kolo, " - ", visina)
Nakamura - 1119
Cube - 66705
Canyon - 26392
Stevens - 3395
Zdaj pa kar brž napišimo program, ki bo povedal, katero kolo se je največ vzpenjalo.
= 0
naj_visina
for kolo, visina in visine.items():
if visina > naj_visina:
= visina
naj_visina = kolo
naj_kolo
print("Največ se je vzpenjal", naj_kolo, "in sicer", naj_visina, "metrov.")
Največ se je vzpenjal Cube in sicer 66705 metrov.
keys
Tudi ta je iz poglavja "boljše, da vam za to povem jaz".
keys
vrne vse ključe slovarja.
for kolo in visine.keys():
print(kolo)
Nakamura
Cube
Canyon
Stevens
To je seveda čisto drugače kot
for kolo in visine:
print(kolo)
Nakamura
Cube
Canyon
Stevens
Aja, ni. Isto je.
Glede metode keys
bi vam rad povedal le to: pozabite, da
obstaja.
Prav, zakaj potem Python sploh ima to metodo?! Pojma nimam. Za hec sem vprašal za mnenje chatGPT. :) Takole me je podučil. (Kogar ne zanima krajši komentar chatGPTja, lahko z branjem tu zaključi.)
In Python, dictionaries (dict
) have a
keys()
method that returns a view object containing the
keys of the dictionary. While it might seem redundant since you can
iterate over the dictionary itself to access its keys, there are some
important distinctions and use cases for using
dict.keys()
:
View Object: dict.keys()
returns a
view object that is a dynamic view on the keys of the dictionary. This
means that any changes made to the dictionary are immediately reflected
in the view, and vice versa. This is not the case when you directly
iterate over the dictionary.
Compatibility: The .keys()
method
is available in Python 2 and Python 3, which makes it more
backward-compatible for code that needs to run on both versions. Direct
iteration over the dictionary may be less compatible.
Explicitness: Using .keys()
can
make your code more explicit and easier to understand. It clearly
signals your intention to work with the keys of the dictionary.
Nadaljeval je s primerom kode, ki je bila nesmiselna. Zaključil jo je s komentarjem:
In this example, the view object key_view reflects changes
made to the dictionary, while iterating directly over the dictionary
does not provide the same dynamic behavior. Depending on your use case
and specific needs, you may choose to use dict.keys()
or
direct iteration, but understanding the differences and advantages of
using dict.keys() can help you make informed decisions in your
code.
Vem, da ne razumete, vendar: napisano nima smisla. Če spremenimo slovarm je slovar spremenjen. Prav tako je spremenjen "key_view". Oboje je "dinamično". "does not provide the same dynamic behavior" je tipičen primer chatGPTjevega flanca. Zadnji stavek pa sploh.
Tiste tri točke so pa zanimive. Tretja je mogoče smiselna in je stvar okusa.
Druga točka je popolnoma mimo. Res je, da imata oba metodo
keys
, vendar ne vrača iste stvari. Ena od največjih razlik
med Pythonom 2 in 3 je prav v tem, kaj vračajo keys
in
podobne metode.
Prva točka je pa zanimiva. S keys
lahko ustvarimo nek
objekt, ki vsebuje le ključe, ne pa tudi vrednosti. Zakaj bi to hoteli,
sicer ne vem, vendar ... no, tu je napisal nekaj smiselnega.