![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
Python je objektově orientovaný jazyk, který podporuje objektově orientované programování (OOP).
Objektově orientované programování má své kořeny již v šedesátých letech, ale až v osmdesátých letech se stalo hlavním programovacím postupem při tvorbě nového software. Bylo vyvinuto jako nástroj pro zvládnutí zvyšující se velikosti a složitosti softwarových systémů a pro usnadnění jejich pozdějších úprav.
Až dosud jsme psali programy s použitím procedurálních programovacích postupů. Těžištěm procedurálního programování je psaní funkcí neboli procedur, které operují s daty. Při objektově orientovaném programování je pozornost zaměřena na vytváření objektů, které obsahují jak data, tak jejich pojednání.
Datový typ třída je v této chvíli pro nás nový pojem, nikoliv však nová skutečnost, neboť tyto objekty jako vestavěné entity používáme již od počátečních kapitol. Jsme vlastně připraveni vytvářet vlastní uživatelsky definované typy. Začněme typem Point.
Uvažme koncept matematického bodu. Ve dvourozměrném prostoru (rovině) bod reprezentují dvě čísla (souřadnice) s kterými je zacházeno jako s jediným objektem. V matematickém zápisu bodu se tyto dvě souřadnice, oddělené čárkou, uvádějí v závorce. Například, (0, 0) představuje počátek souřadnic, (x, y) představuje bod ve vzdálenosti x ve vodorovném a y ve svislém směru od počátku.V Pythonu rovněž vyjadříme bod pomocí dvou číselných hodot. Otázkou je, jak tyto dvě hodnoty seskupit do složeného objektu. Rychlé a mírně ušmudlané řešení je použití seznamu nebo entice a pro některé aplikace to může být řešení nejlepší.
Alternativou je definovat nový složený typ, obecně označený jako třída (class). Tento přístup se neobejde bez jistého úsilí, ale jeho výhody budou brzy zřejmé.
Definice třídy vypadá následovně:
class Point: pass
Definice třídy se mohou vyskytnout kdekoli v programu, ale obvykle se umisťují poblíž počátku (po příkazech import). Pravidla skladby pro definici třídy jsou stejná jako pro jiné složené příkazy. Záhlaví tohoto typu začíná klíčovým slovem class; následuje jméno_třídy ukončené dvojtečkou.
Uvedená definice vytváří novou třídu s názvem Point. Příkaz pass nic neprovádí, zde je však nezbytný, protože složené příkazy ve svém těle něco mít musí.
Vytvořením třídy Point vytváříme zároveň nový uživatelský typ, rovněž zvaný Point. Příslušníci tohoto typu se nazývají instance nebo objekty typu. Vytvoření nové instance se říká konkretizace (instantiation). Zápis instance připomíná zápis funkce: Point().
>>> type(Point) # (toto je jen vestavěná funkce) <type 'classobj;> >>> p = Point() # přiřazení instance >>> type(p) <type 'instance'>
Proměnné p je přiřazena instance (konkretní případ) třídy Point.
Stejně jako objekty reálného světa, mají i instance obsah a funkci. Tyto vlastnosti určujeme pomocí atributů. Formálně je atributem libovolné jméno za tečkou. Atributy jsou v podstatě dvojího druhu. Datové atributy a funkční atributy. Funkčními atributy jsou názvy funkcí, deklarovaných uvnitř třídy. Datové atributy, stejně jako lokální proměnné, předem deklarovány být nemusí:
>>> p.x = 3 >>> p.y = 4
Tato skladba je podobná skladbě pro výběr proměnné z modulu, např. math.pi nebo string.uppercase. Jak moduly, tak i instance vytvářejí své vlastní jmenné prostory. Přístup k jejich jménům (atributům) je v obou případech stejný. V naší ukázce vytváříme atributy instance p a přiřazujeme jim číselné hodnoty.
Následující schéma ukazuje výsledek těchto přiřazení:

Proměnná p odkazuje na instanci třídy Point která má dva atributy. Každý tento atribut odkazuje na číselnou hodnotu.
Hodnoty těchto atributů můžeme číst s použitím stejné syntaxe:
>>> print p.y 4 >>> x = p.x >>> print x 3
Výraz p.x znamená: "Běž k objektu na nějž p ukazuje a získej hodnotu x". Tuto hodnotu přiřazujeme k proměnné x. Mezi proměnnou x a atributem x není žádný konflikt. K rozlišení proměnných nám slouží tečková notace.
Tečkovou notaci můžeme použít jako součást jakéhokoliv výrazu, takže následující příkazy jsou legální:
print '(%d, %d)' % (p.x, p.y)
distance_squared = p.x * p.x + p.y * p.y
Výstupem prvního řádku je 3, 4, druhý řádek spočítá hodnotu 25.
Protože naše třída Point má představovat matematické body v dvojrozměrném prostoru, všechny její instance by měly mít atributy x a y. Tak tomu však u našich objektů Point není:
>>> p2 = Point()
>>> p2.x = 8 # vytvořili jsme datový atribut
>>> p2.y
Traceback (most recent call last):
File "<stdin>", line 1, in?
AttributeError: Point instance has no attribute 'y'
>>>
Abychom tento problém vyřešili, přidáme do naší třídy inicializační metodu.
class Point: def__init__(self, x=0, y=0): # (konstruktor) self.x = x self.y = y
Instanci můžeme zadat jako parametr obvyklým způsobem:
def print_point(p): print '(%s, %s)' % ((p.x),(p.y))
Funkce print_point přijímá bod jako argument a zobrazí jej ve standardním formátu. Voláme-li print_point(p), dostaneme (3, 4).
Význam slova "stejný" se na první pohled zdá být úplně jasný, ale po krátkém zamyšlení si uvědomíme, že tomu tak docela není.
Řekneme-li například, že "Honza a já máme stejné auto", máme na mysli skutečnost, že jsou to auta stejné značky, ale jde o dvě různá auta. Řekneme-li, "Honza a já máme stejnou matku", nejspíš tím chceme říci, že se jedná o jednu osobu.
Když hovoříme o objektech, setkáváme se s podobnou víceznačností. Řekneme-li například, že dvě instance třídy Point jsou stejné, má to znamenat, že obsahují stejná data (souřadnice), nebo že to jsou skutečně tytéž objekty?
Abychom zjistili, zda dva odkazy ukazují na tentýž objekt, použijeme operátor ==. Například:
>>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = Point() >>> p2.x = 3 >>> p2.y = 4 >>> p1 == p2 False
I když p1 a p2 obsahují stejné souřadnice, nejsou to tytéž objekty. Teprve přiřazením p1 k p2 dostaneme dvakrát alias téhož objektu:
>>> p2 = p1 >>> p1 == p2 True
Tento typ rovnosti nazýváme mělká rovnost, protože srovnává jenom odkazy, nikoliv obsahy objektů.
Pro srovnání obsahů (hluboká rovnost) si napíšeme funkci same_point:
def same_point(p1, p2): return (p1.x == p2.x) and (p1.y == p2.y)
Když nyní vytvoříme dva různé objekty, které obsahují stejná data, můžeme použít funkce same_point ke zjištění, zda představují tentýž bod.
>>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = Point() >>> p2.x = 3 >>> p2.y = 4 >>> samePoint(p1, p2) True
Ovšemže, odkazují-li dvě proměnné ke stejnému objektu, mají jak mělkou, tak hlubokou rovnost.
Řekněmež že chceme, aby třída představovala obdélník. Otázkou je, jakou informaci musíme poskytnout, abychom určili obdélník? Pro zjednodušení předpokládejme, že je obdélník orientován buďto vodorovně nebo svisle, nikdy není pootočen.
Máme několik možností: můžeme určit střed obdélníka (dvě souřadnice) a jeho velikost (šířku a výšku), nebo můžeme určit jeden z jeho rohů a velikost, nebo můžeme určit polohu dvou protilehlých bodů. Zvolíme si levý horní roh obdélníka a jeho velikost.
Opět budeme definovat novou třídu:
class Rectangle: pass
A konkretizovat ji:
box = Rectangle() box.width = 100.0 box.height = 200.0
Tento kód vytvořil nový objekt box se dvěma atributy. Pro určení horního levého rohu vložíme do daného objektu další atribut, který je zároveň instancí třídy Point !
box.corner = Point() box.corner.x = 0.0 box.corner.y = 0.0
Vzájemné vztahy objektů vidíme na následujícím obrázku:

Instance může být výstupní hodnotou funkce. Například, funkce find_center přijímá instanci třídy Rectangle jako argument a vrací instanci třídy Point, která obsahuje souřadnice středu zadaného obdélníka:
def find_center(box): p = Point() p.x = box.corner.x + box.width/2.0 p.y = box.corner.y + box.height/2.0 return p
Funkci voláme tak, že zadáme box jako argument a výsledek přiřadíme k proměnné:
>>> center = find_center(box) >>> print_point(center) (50.0, 100.0)
Změnu objektu můžeme provést změnou některého z jeho atributů a to přiřazením. Abychom například změnili velikost obdélníka bez změny jeho polohy, změníme hodnoty width a height:
box.width = box.width + 50 box.height = box.height + 100
Užívání aliasů může učinit program méně přehledným, protože změny provedené na jednom místě se mohou nečekaně projevit na místě jiném. Je obtížné sledovat všechny proměnné, které se mohou vztahovat k danému objektu.
Kopírování objektu je často vhodnou alternativou k aliasování. Modul copy obsahuje funkci copy, která vyrobí duplikát libovolného objektu:
>>> import copy >>> p1 = Point() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = copy.copy(p1) >>> p1 == p2 False >>> same_point(p1, p2) True
Poté, co importujeme modul copy, můžeme použít funkci copy k vytvoření nového objektu třídy Point. Instance p1 a p2 nejsou týmž bodem, ale obsahují stejná data.
Takto vytvořené kopii říkáme mělká kopie a je postačující pro objekty, které neobsahují žádné vnořené objekty.
Pro objekt třídy Rectangle, který obsahuje atribut corner, jenž je objektem třídy Point, není funkce copy příliš vhodná. Kopíruje totiž jen odkaz na objekt třídy Point, takže jak starý objekt p1, tak nový objekt p2 odkazují k jediné instanci třídy Point.
Vytvoříme-li obdélník b1 a jeho kopii b2 funkcí copy, potom zobrazení vnitřní struktury popsané operace vypadá takto:

To zcela určitě není to, co jsme si přáli. Invokace grow_rect pro obdélník b1 nebude mít vliv na obdélník b2, ale invokace move_rect pro jeden z obdélníků ovlivní oba! Toto chování je zmatečné a náchylné k chybám.
Naštěstí modul copy obsahuje metodu deepcopy, která kopíruje i vnořené objekty. Jistě nás nepřekvapí když se dozvíme, že tato operace se nazývá hluboká kopie.
>>> b2 = copy.deepcopy(b1)
Nyní jsou b1 a b2 zcela oddělené objekty.
Můžeme použít funkci deepcopy k přepsání grow_rect tak, aby místo úpravy stávajícího objektu box třídy Rectangle vytvořila nový objekt new_box se stejnou polohou jako ten starý, ale s novými rozměry:
def growRect(box, dwidth, dheight) : import copy new_box = copy.deepcopy(box) new_box.width = new_box.width + dwidth new_box.height = new_box.height + dheight return new_box
| |
| |
| |
| |
| |
| |
| |
| |
| |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |