Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

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 funkcie eval() - Metóda __repr__() vracia textovú reprezentáciu objektu, ktorú je možné potom odovzdať funkciu eval() 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

 

Predchádzajúci článok
Odkazy na objekty a Garbage collector v Pythone
Všetky články v sekcii
Objektovo orientované programovanie v Pythone
Preskočiť článok
(neodporúčame)
Kvíz - Odkazy na objekty a kopírovanie objektov v Pythone
Článok pre vás napísal Karel Zaoral
Avatar
Užívateľské hodnotenie:
Ešte nikto nehodnotil, buď prvý!
Karel Zaoral
Aktivity