Mikuláš je tu! Získaj 90 % extra kreditov ZADARMO s promo kódom CERTIK90 pri nákupe od 1 199 kreditov. Len do nedele 7. 12. 2025! Zisti viac:
NOVINKA: Najžiadanejšie rekvalifikačné kurzy teraz s 50% zľavou + kurz AI ZADARMO. Nečakaj, táto ponuka dlho nevydrží! Zisti viac:

7. diel - Odkazy na objekty a Garbage collector v Pythone

V predchádzajúcom cvičení, Riešené úlohy k 4.-6. lekcii OOP v Pythone, sme si precvičili získané skúsenosti z predchádzajúcich lekcií.

V tomto tutoriáli objektovo orientovaného programovania v Pythone sa zameriame na odkazy (referencie) na objekty, ich kopírovanie a predstavíme si Garbage collector.

Aplikácie a pamäť

Aplikácia (resp. jej vlákno) má operačným systémom pridelenú pamäť v podobe tzv. zásobníka (stack). Ide o veľmi rýchlu pamäť s priamym prístupom. Jej veľkosť aplikácie nemôže ovplyvniť, pretože prostriedky sú prideľované operačným systémom.

Vytvorme si novú konzolovú aplikáciu a v nej jednoduchú triedu User, ktorá bude reprezentovať používateľov nejakého systému. Pre názornosť vypustíme komentáre a nebudeme riešiť zapuzdrenie:

class User:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return str(self.name)

Trieda má dva jednoduché verejné atribúty, metódu konštruktora a metódu __str__(), aby sme mohli užívateľov jednoducho vypisovať. Do programu pridajme vytvorenie inštancie tejto triedy:

u = User("James Brown", 28)

Premenná u teraz obsahuje odkaz na objekt.

Zásobník a halda

Pozrime sa na situáciu v pamäti:

Zásobník a halda v pamäti počítača - Objektovo orientované programovanie v Pythone

Zásobník aj halda sa nachádzajú v pamäti RAM. Rozdiel je v prístupe a veľkosti. Halda je prakticky neobmedzená pamäť, ku ktorej je však prístup zložitejší a tým pádom pomalší. Naopak zásobník je pamäť rýchla, ale veľkostne obmedzená.

Objekty sú v pamäti uložené vlastne na dvakrát, raz v zásobníku a raz v halde. V zásobníku je ale uložená iba tzv. referencia, teda odkaz do haldy, kde sa potom nachádza skutočný objekt.

Môžeme sa pýtať, prečo je to takto urobené. Dôvodov je hneď niekoľko, poďme si niektoré vymenovať:

  1. Miesto v stacku je obmedzené.
  2. Keď budeme chcieť použiť objekt viackrát (napr. ho odovzdať ako parameter do niekoľkých metód), nemusíme ho v programe odovzdávať ako kópiu. Odovzdáme iba referenciu na objekt namiesto toho, aby sme všeobecne pamäťovo náročný objekt kopírovali. To si o chvíľu ukážeme.

Správanie dát v pamäti

Vytvorme si dve premenné typu User:

u = User("James Brown", 28)
v = User("Jack White", 32)

Situácia v pamäti bude takáto:

Odkazy na objekt v Pythone v pamäti počítača - Objektovo orientované programovanie v Pythone

Teraz skúsme priradiť do premennej u premennú v. Pri objekte sa skopíruje iba odkaz na objekt, ale objekt máme stále iba jeden. V kóde teda vykonáme toto:

u = User("James Brown", 28)
v = User("Jack White", 32)
u = v

V pamäti bude celá situácia vyzerať takto:

Odkazy na objekt v pamäti počítača - Objektovo orientované programovanie v Pythone

Presvedčme sa o tom, aby sme videli, že to naozaj tak je 🙂 Najprv si necháme obidve premenné vypísať pred a po zmene. Navyše si vypíšeme aj id objektov cez funkciu id(). Pretože budeme výpis volať viackrát, napíšeme si ho trochu úspornejšie. Upravme teda kód:

# variable declaration
u = User("James Brown", 28)
v = User("Jack White", 32)
print(f"u: {u}\nv: {v}")
print(f"u: {id(u)}\nv: {id(v)}\n")

# assignment
u = v
print(f"u: {u}\nv: {v}")
print(f"u: {id(u)}\nv: {id(v)}\n")

Poďme zmeniť meno používateľa v premennej v a podľa našich predpokladov by sa mala zmena prejaviť aj v premennej u. K programu pripíšeme:

# change
v.name = "John Doe"
print(f"u: {u}\nv: {v}")
print(f"u: {id(u)}\nv: {id(v)}\n")

Zmenili sme objekt v premennej v a znova vypíšeme u a v. Spolu so zmenou v sa zmení aj u, pretože premenné ukazujú na ten istý objekt. Pripomeňme si situáciu v pamäti ešte raz a zamerajme sa na Jamesa Browna:

Užívatelia ako odkazy na objekt v Pythonu - Objektovo orientované programovanie v Pythone

Čo sa s ním stane? "Zožerie" ho tzv Garbage collector.

Garbage collector - Objektovo orientované programovanie v Pythone

Garbage collector a dynamická správa pamäte

Pamäť môžeme v programoch alokovať staticky. To znamená, že v zdrojovom kóde vopred určíme, koľko ju budeme používať. Čoskoro sa ale budeme stretávať s aplikáciami (a už sme sa vlastne aj stretli), kedy nebudeme pred spustením presne vedieť, koľko pamäte budeme potrebovať. V takom prípade hovoríme o dynamickej správe pamäte.

V minulosti, hlavne v dobách jazykov C, Pascal a C++, sa na tento účel používali tzv. pointery, čiže priame ukazovatele do pamäte. Napospol to fungovalo tak, že sme požiadali operačný systém o kus pamäte o určitej veľkosti. On ju pre nás vyhradil a dal nám jej adresu. Na toto miesto sme mali v pamäti pointer, cez ktorý sme s pamäťou pracovali. Problém bol, že nikto nestrážil, čo do pamäte dávame (ukazovateľ smeroval na začiatok vyhradeného priestoru). Keď sme tam dali niečo väčšie, skrátka sa to aj tak uložilo a prepísali sa dáta za naším priestorom, ktoré patrili napríklad inému programu alebo operačnému systému (v tom prípade by našu aplikáciu OS asi zabil – zastavil). Často sme si však v pamäti prepísali nejaké ďalšie dáta nášho programu a program sa začal správať chaoticky. Predstavme si, že si uložíme používateľa do poľa a v tej chvíli sa zrazu zmení farba používateľského prostredia, teda niečo, čo s tým vôbec nesúvisí. Hodiny strávime kontrolou kódu pre zmenu farby. A potom zistíme, že chyba je vo vytvorení užívateľa, kedy dôjde k pretečeniu pamäte a prepísaniu hodnôt farby.

Keď naopak nejaký objekt prestaneme používať, musíme po ňom miesto sami uvoľniť. Ak to neurobíme, pamäť zostane blokovaná. Ak to robíme napr. v nejakej metóde a zabudneme pamäť uvoľňovať, naša aplikácia začne padať, prípadne zasekne celý operačný systém. Taká chyba sa opäť zle hľadá. Prečo program prestane po niekoľkých hodinách fungovať? Kde tú chybu v niekoľkých tisícoch riadkov kódu vôbec hľadať? Nemáme jedinú stopu, nemôžeme sa ničoho chytiť, musíme prejsť celý program riadok po riadku. Brrr. Podobný problém nastane, keď si niekde pamäť uvoľníme a následne pointer opäť použijeme (zabudneme, že je uvoľnený, čo sa môže ľahko stať). Ten ale povedie niekam, kde je už uložené niečo iné. Tieto dáta budú opäť prepísané. Povedie to k nekontrolovateľnému správaniu našej aplikácie a môže to dopadnúť aj takto:

Blue Screen Of Death – BSOD vo Windows - Objektovo orientované programovanie v Pythone

Až na malú skupinu géniov, ľudí prestalo baviť riešiť neustále a nezmyselné chyby. Za cenu mierneho zníženia výkonu vznikli riadené jazyky (managed) s tzv. garbage collectorom, jedným z nich je aj Python. C++ sa samozrejme naďalej používa, ale iba na špecifické programy, napr. časti operačného systému alebo 3D enginy komerčných hier, kde je potrebné z počítača dostať maximálny výkon. Na 99 % všetkých ostatných aplikácií sa viac hodí Python alebo iný riadený jazyk. Okrem iného práve kvôli automatickej správe pamäte.

Garbage collector - Objektovo orientované programovanie v Pythone
Garbage collector je vlastne program, ktorý beží paralelne s našou aplikáciou v samostatnom vlákne. Občas sa aktivuje a pozrie sa, na ktoré objekty už v pamäti nevedú žiadne referencie. Tie potom odstránia. Strata výkonu je minimálna a značne to zníži percento samovrážd programátorov, ladiacich po večeroch rozbitých pointerov. Zapnutie a vypnutie GC môžeme dokonca z kódu ovplyvniť, aj keď to nie je v 99 % prípadov vôbec potrebné. Pretože je jazyk riadený a nepracujeme s priamymi pointermi, nie je vôbec možné pamäť nejako narušiť, nechať ju pretiecť a podobne. O pamäť sa automaticky stará interpreter.

Hodnota None

Posledná vec, ktorú spomenieme, je tzv. hodnota None. Referenčné typy môžu, na rozdiel od hodnotových, nadobúdať špeciálne hodnoty None. None je kľúčové slovo a označuje, že referencie neukazuje na žiadne dáta. Keď nastavíme premennú v na None, zrušíme iba referenciu. Ak na náš objekt existuje ešte nejaká referencie, bude aj naďalej existovať. Ak nie, bude uvoľnený GC. Zmeňme ešte posledné riadky nášho programu na:

# another change
v.name = "John Doe"
v = None
print(f"u: {u}\nv: {v}")
print(f"u: {id(u)}\nv: {id(v)}\n")

Vidíme, že objekt stále existuje a ukazuje na neho premenná u, zatiaľ čo v premennej v už nie je referencia. None sa využíva napr. pri testovaní príslušnosti pomocou kľúčového slova is, ale o tom niekedy inokedy.

V nasledujúcej lekcii, Kopírovanie objektov v Pythone, sa zameriame na spôsoby, ktorými je možné kopírovať objekty.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 51x (1.26 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python

 

Predchádzajúci článok
Riešené úlohy k 4.-6. lekcii OOP v Pythone
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Kopírovanie objektov v Pythone
Článok pre vás napísal gcx11
Avatar
Užívateľské hodnotenie:
118 hlasov
(^_^)
Aktivity