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áli 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.
V nasledujúcich kapitolách lekcie sa pozrieme na každú z týchto možností podrobnejšie, aby sme lepšie pochopili ich funkciu a vhodné použitie.
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 RollingDie
. 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 RollingDie:
def __init__(self, sides_count=6):
self._sides_count = sides_count
def __str__(self):
return str(f"A rolling die with {self._sides_count} sides.")
def get_sides_count(self):
return self._sides_count
def roll(self):
import random as _random
return _random.randint(1, self._sides_count)
# Create an instance of the rolling die with 6 sides
my_die = RollingDie(6)
print(my_die)
# Create a copy of the my_die instance using the constructor
copy_of_die = RollingDie(my_die.get_sides_count())
print(copy_of_die)
Vo výstupe uvidíme:
Copy of the object using the constructor:
A rolling die with 6 sides.
A rolling die with 6 sides.
Kópiu inštancie my_die
sme vytvorili jednoducho tým, že sme
znovu vytvorili inštanciu triedy RollingDie
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 RollingDie
, 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
RollingDie
pomocou funkcie copy()
z modulu
copy
:
import copy as cp # Create an instance of the rolling die with 6 sides original_die = RollingDie(6) print(original_die) # Create a shallow copy of the original_die instance shallow_copy_of_die = cp.copy(original_die) print(shallow_copy_of_die)
Vo výstupe uvidíme:
Copy of the object using the copy() function:
A rolling die with 6 sides.
A rolling die with 6 sides.
Uveďme si ešte príklad s vnorenými objektmi:
Máme triedu RollingDie
a triedu Player
, kde hráč
bude mať svoju kocku:
import copy as cp class RollingDie: def __init__(self, sides_count=6): self._sides_count = sides_count def __str__(self): return str(f"A rolling die with {self._sides_count} sides.") ... class Player: def __init__(self, name, die): self.name = name self.die = die def __str__(self): return f"{self.name}: {self.die}"
Teraz vytvoríme hráča s konkrétnou kockou:
die = RollingDie(6) player = Player("Paul", die) print(player)
Vo výstupe uvidíme:
Copy of the object using the copy() function:
Paul: A rolling die with 6 sides.
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:
copy_of_player = cp.copy(player) # Modify the original die die._sides_count = 8 print(player) print(copy_of_player)
Vo výstupe uvidíme:
Copy of the object using the copy() function:
Paul: A rolling die with 8 sides.
Paul: A rolling die with 8 sides.
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
Player
, ale vnorený objekt triedy RollingDie
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 RollingDie: def __init__(self, sides_count=6): self._sides_count = sides_count def __str__(self): return str(f"A rolling die with {self._sides_count} sides.") ... class Player: def __init__(self, name, die): self.name = name self.die = die def __str__(self): return f"{self.name}: {self.die}" die = RollingDie(6) player = Player("Paul", die) # Create a deep copy of the player copy_of_player = cp.deepcopy(player) # Modify the original die die._sides_count = 8 print(player) print(copy_of_player)
Vo výstupe uvidíme:
Copy of the object using the deepcopy() function:
Paul: A rolling die with 8 sides.
Paul: A rolling die with 6 sides.
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í funkcii
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 RollingDie: def __init__(self, sides_count=6): self._sides_count = sides_count def __str__(self): return f"A rolling die with {self._sides_count} sides." ... def __repr__(self): return f"RollingDie({self._sides_count})" original_die = RollingDie(8) # 8-sided die copy_of_die = eval(repr(original_die)) print(original_die) print(copy_of_die)
Vo výstupe uvidíme:
Copy of the object using the repr() method:
A rolling die with 8 sides.
A rolling die with 8 sides.
Metódu __repr__()
sme pridali tak, aby reprezentovala
inštanciu triedy RollingDie
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
RollingDie(sides_count)
, kde sides_count
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é 0x (3.29 kB)
Aplikácia je vrátane zdrojových kódov v jazyku Python