Třídy a objekty¶
Python je objektově - orientovaný programovací jazyk (OOP) i když jeho objektové vlastnosti nemusíte moc využívat, sem tam na vás vykouknou a dobré o tom něco vědět.
Téma objektově orientovaného programovaní dosti přesahuje smysl tohoto tutoriálu, budeme se snažit jím projít co možná po povrchu, abychom byli schopni pracovat s knihovnami pro prostorová data.
Objekty¶
Když se řekne objekt, znamená v Pythonu všechno co můžete uložit do proměnné (tedy i funkce, pole, data). V Pythonu není rozdíl mezi objektem a hodnotou.
Objekt je „něco“, co má nějaké vlastnosti (data) a nějaká chování (metody), které s daty pracují. Objekty spojují data a funkčnost do jednoho celku.
Jak zjistíte, jaké má objekt vlastnosti (atributy) a funkce (metody)? Samozřejmě
z dokumentace, např. pomocí programu pydoc
:
$ pydoc list
Help on class list in module __builtin__:
class list(object)
| list() -> new empty list
| list(iterable) -> new list initialized from iterable's items
|
| Methods defined here:
|
| __add__(...)
| x.__add__(y) <==> x+y
|
| __contains__(...)
| x.__contains__(y) <==> y in x
|
| __delitem__(...)
...
Rychlá pomoc je funkce dir()
, která vrátí všechny atributy a metody daného
objektu
>>> dir([])
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__',
'__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__',
'__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']
z tohoto výpisu sice nepoznáte, jedná-li se o data (atributy) nebo metody (funkce) daného objektu, ale pro základní přehled to stačí a odhadnout se to podle názvu většinou dá. K podtržítkám v názvech některých vlastností se dostaneme níže.
Pole prvků jako objekt¶
Pojďme se podívat v rychlosti na objekt, který už známe, akorát jsme nevěděli, že je to objekt, na seznam prvků. Jaké má vlastnosti zjistíme pomocí funkce dir():
dir([]) # vypíše vlastnosti prázdného pole
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__',
'__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__',
'__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__',
'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop',
'remove', 'reverse', 'sort']
Pomiňme pro chvíli vlastnosti začínající dvěma podtržítky __ a všimněme si těch, které začínají od append. Podíváme-li se do dokumentace pomocí nástroje pydoc (pydoc list), zjistíme, že
- append(objekt)
přidá nový element na konec pole
- count(hodnota)
vrátí počet prvků, které odpovídají dané hodnotě
- extend(iterable)
přidá další seznam na konec stávajícího seznamu
- index(hodnota)
vrátí první index zadané hodnoty
- insert(index, objekt)
vloží nový prvek na zadaný index (např. pole.insert(0, ‚elece pelce‘) vloží nový prvek na první pozici pole
- pop()
odebere poslední provek z pole
- remove(hodnota)
smaže první prvek odpovídající zadané hodnotě
- reverse()
otočí pořadí elementů v poli
- sort()
seřadí elementy v poli
Příklad použití:
>>> pole = [10, 5, 'x', 'kecy v kleci', [7, 'a'], '+', -10]
>>> print(pole)
[10, 5, 'x', 'kecy v kleci', [7, 'a'], '+', -10]
>>> pole.sort()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unorderable types: str() < int()
>>> pole.append('jeste neco')
>>> print(pole)
[10, 5, 'x', 'kecy v kleci', [7, 'a'], '+', -10, 'jeste neco']
>>> pole.insert(3, 'neco na 4tou pozici')
>>> print(pole)
[10, 5, 'x', 'neco na 4tou pozici', 'kecy v kleci', [7, 'a'], '+', -10, 'jeste neco']
>>>
A takto se s objekty pracuje - používáme jejich metody (v praxi funkce) a atributy (data).
Objekt Point z modulu shapely¶
Podíváme-li se do dokumentace třídy shapely.geometry.Point, uvidíme, že má následující metody (vybírám ty zajímavé - použijte pydoc shapely.geometry.Point):
- almost_equals()
porovnává souřadnice až do přiměřeného počtu desetinných míst
- buffer()
vrátí buffer podle zadaných parametrů
- contains()
obsahuje jinou gemetrii
- crosses()
kříží se s jinou geometrií
- difference()
vrátí rozdíl s jinou geometrií
- distance()
počítá vzdálenost k jiné geometrii
overlaps, intersects, …
- simplify()
generalizeace
- to_wkb()
vrátí well known binary representaci geometrie
- to_wkt()
vrátí well known text representaci geometrie
…
Vybral jsem pouze některé metody, ale pojďme je trochu vyzkoušet:
from shapely.geometry.import Point
bod1 = Point(10, 20) # vyrobíme nový bod, předpis pro parametry je ve funkci
# __init__
print(bod1)
bod2 = Point(10, 10)
print(bod2)
# měly by vzniknout 2 kruhy, které se vzájemně dotýkají svých středů
buffer1 = bod1.buffer(10)
buffer2 = bod2.buffer(10)
# a jdeme si hrát
buffer1.almost_equals(buffer2) # vrátí False
myunion = None # inicializace prázdné proměnné
if buffer1.intersects(buffer2): # pokud spolu geometrie souvisí
myunion = buffer1.union(buffer2) # udělej jejich spojení
# ověření, že střed je střed
centroid = buffer2.centroid()
centroid.almost_equals(bod2) # -> True
Vlastní třídy (po povrchu)¶
Jak si vytvářet vlastní objekty vytvoříme tak, že nejprve definujeme třídu a následně budeme vytvářet její instance.
Třída je jakýsi předpis pro objekt. Jeho instance je od třídy odvozený konkrétní objekt.
Příkladem třídy může být Clovek, který má atributy (vlastnosti) ruka, noha, zuby, jmeno a metody (funkce) mluv(), jez(), podej(). Od této třídy pak můžeme odvodit instanci konkrétního člověka jménem „Patrik Jouda“, který bude mít ruku, nohu, několik zubů a funkce že „mluví“, „jí“ a je schopen něco podat.
Nebo v případě rastrových souborů se shodneme na tom, že je potřeba mít třídu rastr, která bude předepisovat co všechno takový rastrový objekt má mít - Transformační matici (což je instance jiného objektu), počet řádků, počet sloupců, rozlišení a metodu pro čtení a zápis vlastních dat.
Už jsme se seznámili se třídou Point, která reprezentuje obecnou bodovou geometrii, která má vlastnosti (souřadnice) bodu a spoustu metod pro práci s geometriemi.
Vlastní třídu vytvoříme tak, ji uvedeme kláčovým slovem class
a případně jako
parametr přidáme „rodičovský objekt“ (třídy od sebe mohou navzájem vlastnosti
dědit, např. člověk může dědit vlastnosti od třídy Živočich).
Pokud musíme nastavit nějaké proměnné hned na počátku inicializace třídy,
použijeme k tomu speciální metodu __init__
, které můžeme předat inicializační
parametry (např. v případě našeho bodu Point jsme předávali souřadnice x a
y‘).
V rámci třídy musíte odkazovat slovem self na vlastnosti dané třídy. self obsahuje odkaz „sám na sebe“.
Vyzkoušejme si malý příklad vlastní třídy:
# definice vlastní třídy
class MyClass():
pass # 'pass' znamená "nedělej nic"
moje_trida = MyClass()
print(moje_trida)
Tento krátý program vytiskne adresu v paměti počítače instance třídy MyClass, kterou jsme uložili do proměnné moje_trida:
<__main__.MyClass object at 0x7f5b72e0fd30>
Pokusme se naši třídu trochu rozšířit - a pojďme si vyrobit vlastní model rastrového souboru:
class Raster(object): # je vhodné odvodit třídu od základního objektu 'object'
"""Třída reprezentující rastrová data
"""
def __init__(self, columns, rows, transformation=None):
"""Konstruktor třídy - tato funkce se zavolá při vzniku nové
instance třídy. Máme zde dva povinné parametry:
columns - počet sloupců
rows - počet řádků
a jeden nepovinný parametr
transformation - transformační matice, jak ji známe např. z formátu
GeoTIFF. Výchozí hodnota tohoto parametru je nastavena na
None, můžeme ho při zadání přeskočit
"""
# nastavíme atributy (data) třídy
self.columns = columns
self.rows = rows
self.transforamtion = transformation
# inicializace prázdného pole 'data'
self.data = None
def set_data(self, data):
"""Metoda (funkce), která nám umožní nastavit data,
data jsou očekávána jako pole polí - pole řádků, obsahující pole
sloupců
"""
self.data = data # jsme důvěřiví a nebudeme ověřovat, že vstupní
# data jsou opravdu sloupce a řádky
def averadge_filter(self, size):
"""Funkce, která vrátí nový rastr (pouze data), obsahující původní
hodnoty, které prošly filtrem o velikosti 3x3, kdy prostřední
hodnota odpovídá průměru všech buněk okolo sum(matice)/(size * size)
size = 3
+---+---+---+ +---+---+---+
| 1 | 2 | 4 | | | | |
+---+---+---+ +---+---+---+
| 1 | 3 | 2 | --> | |2.3| |
+---+---+---+ +---+---+---+
| 2 | 3 | 3 | | | | |
+---+---+---+ +---+---+---+
"""
# size musí být liché číslo
if size%2 == 0:
size += 1
# tohle bude komplikovanější a už na to napsali funkce jiní, zkusíme
# si to ale naiplementovat sami. Až budete něco dělat s maticemi,
# použijte dostupné funkce z balíčku numpy
result = [ ]
for row in range(self.data):
if row < int(size/2):
continue
elif row > len(self.data)-int(size/2):
continue
result.append([])
for col in range(self.data[row]):
if col < int(size/2):
continue
elif col > len(self.data)-int(size/2):
continue
for # TODO - pro kazdou bunku
Slovo o podtržítkách¶
Některé vlastnosti objektů mají název uvozený dvěma podtržíky, např. __sizeof__ a některé ne, jaký je v tom rozdíl? Pokud nějaká vlastnost objektu začíná jedním podtržítkem, znamená to, že se jedná o privátní vlastnost.
Pokud je podtržítko na začátku jenom jedno, neměli byste tuto funkci používat (ale technicky vzato je to povoleno a občas člověk musí v kódu trochu „zaprasit“).
Pokud vlastnost začíná dvěma podtržítky, je zcela privátní a použít nejde. Zkusíme to oddemonstrovat na následujícím příkladu a pozor, vytvoříme naši první třídu (objekt našeho vlastního typu):
>>> # nova trida
>>> class MyClass(object):
...
... def __init__(self):
... """Konstruktor třídy - funkce, která se zavolá pouze při
... inicializaci třídy"""
... self.atribut = "veřejná vlastnost"
... self._private_atribute = "privátní vlastnost"
... self.__super_private_attribute = "na tohle už si nešáhneme
...
... def _castecne_privatni(self):
... """Částečně privátní metoda - není "vidět", ale můžeme ji použít
... """
...
... return "Castecne privatni funkce"
...
... def __uplne_privatni(self):
... """Zcela privátní metoda - z "venku" objektu není spustitelná
... """
...
... return "Uplne privatni funkce"
...
... def uplne_verejna(self):
... """Úplně veřejná metoda - můžeme ji spouštět
... """
...
... # "uvnitř" objektu na privátní funkce dosáhneme
... return "Uplne verejna funkce a vola privatni: " + self.__uplne_privatni()
>>> # a ted ji pouzijeme
>>> moje_trida = MyClass()
>>> c._castecne_privatni()
Castecne privatni funkce
>>> c.__uplne_privatni()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'MyClass' object has no attribute '__uplne_privatni'
>>> c.uplne_verejna()
Uplne verejna funkce a vola privatni: Uplne privatni funkce