7. diel - Kopírovanie objektov v Pythone
V minulej lekcii, Odkazy na objekty a Garbage collector v Pythone , sme si vysvetlili ako fungujú referencie na objekty.
V dnešnom tutoriále objektovo orientovaného programovania v Pythone si dôkladne vysvetlíme spôsoby, ktorými je možné vytvoriť kópie objektu. Ukážeme si ich výhody aj nevýhody a tiež riziká, ktoré sú s nimi spojené.
Kopírovanie objektov
V bežnom programovaní často pracujeme s objektmi a manipulujeme s nimi. To zahŕňa aj vytváranie ich kópií. Z predchádzajúcich lekcií už vieme, že keď máme dve referencie na rovnaký objekt a zmeníme jednu, druhá sa zmení tiež, pretože obe referencie ukazujú na rovnakú pamäťovú oblasť. Lenže v niektorých situáciách chceme, aby pôvodné dáta zostali nedotknuté, napríklad keď robíme nejaké dočasné zmeny pre analýzu alebo spracovanie dát. Mať kópiu objektu nám umožní vykonávať zmeny práve len na kópii, zatiaľ čo pôvodné dáta zostanú nezmenené. Nemusíme sa tiež obávať vedľajších efektov spojených so zdieľaním referencií na objekty medzi rôznymi časťami programu. Ako teda môžeme vytvoriť skutočnú kópiu objektu?
Možností, ako kopírovať objekty v Pythone, máme niekoľko:
- Kópia pomocou konštruktora - Vytvorí novú inštanciu objektu pomocou existujúceho konštruktora a odovzdanie potrebných atribútov.
- Plytká kópia pomocou funkcie
copy()
- Rýchle kopírovanie, ktoré vytvára nový objekt, ale v prípade zložených objektov (napr. zoznamov v zozname) kopíruje iba odkazy na vnútorné objekty. - Hlboká kópia pomocou funkcie
deepcopy()
- Vytvára nový objekt a rekurzívne kopíruje všetky vnútorné objekty. Výsledkom je úplne nezávislá kópia pôvodného objektu. - Kópia pomocou metódy
__repr__()
a funkcieeval()
- Metóda__repr__()
vracia textovú reprezentáciu objektu, ktorú je možné potom odovzdať funkciueval()
pre dynamické vytvorenie novej inštancie objektu.
Kópia pomocou konštruktora
Keď chceme vytvoriť kópiu objektu v Pythone, jedným z priamych spôsobov je využiť už existujúci konštruktor triedy. Toto je často intuitívna metóda, pretože ide o vytvorenie novej inštancie objektu s pôvodnými hodnotami.
Uveďme si príklad na základe našej triedy Kostka
. Ak máme
už existujúcu inštanciu s určitým počtom stien, dokážeme ľahko
vytvoriť novú inštanciu tejto triedy, odovzdať jej rovnaký počet stien a
tým pádom vytvoriť jej kópiu:
class Kostka:
def __init__(self, pocet_sten=6):
self._pocet_sten = pocet_sten
def __str__(self):
return str(f"Kostka s {self._pocet_sten} stěnami.")
def vrat_pocet_sten(self):
return self._pocet_sten
def hod(self):
import random as _random
return _random.randint(1, self._pocet_sten)
# Vytvoříme instanci kostky s 6 stěnami
moje_kostka = Kostka(6)
print(moje_kostka)
# Vytvoříme kopii instance moje_kostka pomocí konstruktoru
kopie_kostky = Kostka(moje_kostka.vrat_pocet_sten())
print(kopie_kostky)
Vo výstupe uvidíme:
Kopie objektu pomocí konstruktoru:
Kostka s 6 stěnami.
Kostka s 6 stěnami.
Kópiu inštancie puvodni_kostka
sme vytvorili jednoducho tým,
že sme znovu vytvorili inštanciu triedy Kostka
a odovzdali ju
počet stien z pôvodného objektu. Výsledkom sú dva rôzne objekty s
identickými hodnotami, ale oba objekty sú na sebe úplne nezávislé.
Je dôležité si uvedomiť, že pri kopírovaní objektu týmto spôsobom musíme skopírovať všetky jeho atribúty, aby kópia bola naozaj kompletná a presne zodpovedala originálu.
Plytká kópia pomocou funkcie
copy()
Plytká kópia objektu vytvorí nový objekt, ale nekopíruje vnorené objekty, na ktoré pôvodný objekt odkazuje. V mnohých prípadoch môže byť plytká kópia dostačujúca, avšak je dôležité chápať jej obmedzenia.
V prípade našej triedy Kostka
, ktorá má iba základné
atribúty, bude plytká kópia fungovať bez problémov, pretože neexistujú
žiadne vnorené objekty na zdieľanie.
Tu je ukážka, ako vytvoriť plytkú kópiu objektu triedy
Kostka
pomocou funkcie copy()
z modulu
copy
:
import copy as cp # Vytvoříme instanci kostky s 6 stěnami puvodni_kostka = Kostka(6) print(puvodni_kostka) # Vytvoříme mělkou kopii instance puvodni_kostka melka_kopie_kostky = cp.copy(puvodni_kostka) print(melka_kopie_kostky)
Vo výstupe uvidíme:
Kopie objektu pomocí funkce copy():
Kostka s 6 stěnami.
Kostka s 6 stěnami.
Uveďme si ešte príklad s vnorenými objektmi:
Máme triedu Kostka
a triedu Hrac
, kde hráč bude
mať svoju kocku:
import copy as cp class Kostka: def __init__(self, pocet_sten=6): self._pocet_sten = pocet_sten def __str__(self): return str(f"Kostka s {self._pocet_sten} stěnami.") ... class Hrac: def __init__(self, jmeno, kostka): self.jmeno = jmeno self.kostka = kostka def __str__(self): return f"{self.jmeno}: {self.kostka}"
Teraz vytvoríme hráča s konkrétnou kockou:
kostka = Kostka(6) hrac = Hrac("Pavel", kostka) print(hrac)
Vo výstupe uvidíme:
Kopie objektu pomocí funkce copy():
Pavel: Kostka s 6 stěnami.
Vytvoríme plytkú kópiu hráča a potom zmeníme počet stien. A áno, porušujeme tu pravidlo o privátnych atribútoch, ale len na účely objasnenia mechanizmu kópie:
kopie_hrace = cp.copy(hrac) # Změníme původní kostku kostka._pocet_sten = 8 print(hrac) print(kopie_hrace)
Vo výstupe uvidíme:
Kopie objektu pomocí funkce copy():
Pavel: Kostka s 8 stěnami.
Pavel: Kostka s 8 stěnami.
Z výstupu je zrejmé, že aj keď sme zmenili pôvodnú kocku až po
vytvorení kópie hráča, zmenil sa počet jej stien aj pri tejto kópii.
Dôvodom je to, že plytká kópia síce vytvorila nový objekt triedy
Hrac
, ale vnorený objekt triedy Kostka
zostal
rovnaký a obaja hráči naň odkazujú.
Hlboká kópia pomocou funkcie
deepcopy()
Na vytvorenie kompletnej kópie objektu vrátane všetkých vnorených
objektov máme k dispozícii v module copy
funkciu
deepcopy()
. Táto funkcia prechádza celým objektom a rekurzívne
vytvára kópie všetkých vnorených objektov.
Pozrime sa na náš predchádzajúci príklad s hráčom a kockou:
import copy as cp class Kostka: def __init__(self, pocet_sten=6): self._pocet_sten = pocet_sten def __str__(self): return str(f"Kostka s {self._pocet_sten} stěnami.") ... class Hrac: def __init__(self, jmeno, kostka): self.jmeno = jmeno self.kostka = kostka def __str__(self): return f"{self.jmeno}: {self.kostka}" kostka = Kostka(6) hrac = Hrac("Pavel", kostka) # Vytvoříme hlubokou kopii hráče kopie_hrace = cp.deepcopy(hrac) # Změníme původní kostku kostka._pocet_sten = 8 print(hrac) print(kopie_hrace)
Vo výstupe uvidíme:
Kopie objektu pomocí funkce deepcopy():
Pavel: Kostka s 8 stěnami.
Pavel: Kostka s 6 stěnami.
Ako vidíme, zmena pôvodnej kocky nijako neovplyvnila kocku pri kópii
hráča. To je zásluha hlbokej kópie, ktorá zaisťuje, že všetky vnorené
objekty sú tiež skopírované, a teda sú úplne nezávislé na origináloch.
Tieto vlastnosti robia z deepcopy()
najjednoduchší variant
vytvorenia kópie objektu. Pozor ale na fakt, že metóda
deepcopy()
je veľmi náročná na výkon.
Táto metóda je vhodná najmä v prípadoch, keď pracujeme s komplexnými objektmi s mnohými vnorenými atribútmi alebo objektmi, ktoré môžu byť menené v budúcnosti a chceme sa vyvarovať nežiaducim efektom.
Kópia pomocou metódy
__repr__()
a funkcie eval()
V Pythone existujú dve základné metódy na vytváranie textovej
reprezentácie objektov. Nám už dobre známa metóda __str__()
a
tiež zatiaľ tajomná metóda __repr__()
. Zatiaľ čo
__str__()
je určená pre "peknú" textovú reprezentáciu objektu
pre koncového užívateľa, __repr__()
má za cieľ vytvoriť
"oficiálnu" textovú reprezentáciu objektu. Ako si to predstaviť? Metóda
__repr__()
vracia reťazec, ktorý po odovzdaní funkcie
eval()
vytvorí objekt, ktorý je ekvivalentný pôvodnému
objektu. Teda kópiu:-)
Tu vzniká otázka: je __repr__()
akýmsi "dumpom" časti
pamäte s objektom, uloženej v textovom reťazci? Odpoveď je nie. Namiesto
toho je to skôr kódový výraz, ktorý, keď je vyhodnotený, vráti novú
inštanciu rovnakého objektu s rovnakými dátami. Majme teda triedu, ktorej
__repr__()
metóda vráti kód potrebný na vytvorenie novej
inštancie s tými istými dátami. Funkcia eval()
potom vytvorí
jej novú inštanciu. Výsledný objekt bude mať rovnaké metódy, pretože je
stále inštanciou rovnakej triedy.
Ukážme si príklad:
class Kostka: def __init__(self, pocet_sten=6): self._pocet_sten = pocet_sten def __str__(self): return f"Kostka s {self._pocet_sten} stěnami." ... def __repr__(self): return f"Kostka({self._pocet_sten})" puvodni_kostka = Kostka(8) # 8-stěnná kostka kopie_kostky = eval(repr(puvodni_kostka)) print(puvodni_kostka) print(kopie_kostky)
Vo výstupe uvidíme:
Kopie objektu pomocí metody __repr__():
Kostka s 8 stěnami.
Kostka s 8 stěnami.
Metódu __repr__()
sme pridali tak, aby reprezentovala
inštanciu triedy Kostka
vo formáte, ktorý by bolo možné
použiť na opätovné vytvorenie rovnakej inštancie. V našom prípade metóda
__repr__()
vracia reťazec vo formáte
Kostka(pocet_sten)
, kde pocet_sten
je aktuálny počet
stien kocky.
Táto metóda kopírovania sa zameriava predovšetkým na dáta a nie na zložité správanie objektu alebo vnorené objekty.
Treba však mať na pamäti, že funkcia eval()
je zo svojej
podstaty (tvorba kódu z reťazca) riziková a ľahko sa stane zdrojom vážnych
bezpečnostných problémov.
Nikdy preto nepoužívajme funkciu eval()
na
dátach, pri ktorých si nie sme istí, že pochádza z overených zdrojov.
To je pre túto lekciu všetko:)
V nasledujúcom kvíze, Kvíz - Odkazy na objekty a kopírovanie objektov v Pythone, si vyskúšame nadobudnuté skúsenosti z predchádzajúcich lekcií.
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é 63x (3.46 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python