9. Objekty

Objekty slouží ke spojení souvisejících dat a často i operací na těchto datech. Třída (class) je předpis (šablona) pro vytváření nových objektů nějakého typu. Nejjednodušším případem jsou tzv. struktury (záznamy), sloužící pouze ke spojení souvisejících dat (např. souřadnic jednoho bodu). Některé programovací jazyky mají speciální typ pro definici struktury obsahující několik datových položek, v Pythonu se k tomu využívají jednoduché třídy s metodu __init__, která nastaví (inicializuje) všechny požadované atributy struktury. Syntaxe pro vytvoření struktury je následující:

Třída může obsahovat i další metody (funkce pracující na datech objektu). Objekt pak má nějaký stav (data, atributy) a navíc i chování (metody). Prvním parametrem metody je vždy objekt, na kterém došlo k volání této metody, silnou konvencí je nazývat tento parametr self.

S objekty jsme pracovali již mnohokrát. Například seznamy jsou objekty s metodami jako append, sort, atd. A třeba knihovna turtle obsahuje třídu Turtle pro reprezentaci kreslící želvy. Nový objekt želvy se vytvoří voláním turtle1 = Turtle().

Další konvencí Pythonu je pojmenovávat atributy, ke kterým by nemělo být přistupováno z vnějšku přímo, ale pouze pomocí metod (tzv. soukromé atributy), s podtržítkem na začátku (např. _name).

9.1. Struktury

9.1.1. Barevné kruhy

Vytvořte třídu pro reprezentaci barevného kruhu na daných souřadnicích.

class Circle:

    def __init__(self, x: float, y: float, radius: float, color: str) -> None:
        pass

def test_circle() -> None:
    circle = Circle(4, 5, 10, 'red')
    assert circle.y == 5
    assert circle.color == 'red'

9.1.2. Obdélník

Napište třídu Rectangle pro reprezentaci obdélníku o dané šířce a výšce a funkci pro výpočet obsahu předaného obdélníku.

class Rectangle:
    pass

def area(rectangle: Rectangle) -> float:
    pass

def test_rectangle() -> None:
    rectangle = Rectangle(4, 3)
    assert rectangle.width == 4
    assert rectangle.height == 3
    assert area(rectangle) == 12

9.1.3. Bod

Napište třídu pro reprezentaci bodu v rovině (parametry x a y). Potom implementujte funkci, která vypočítá vzdálenost dvou bodů.

9.1.4. Kniha

Napište třídu pro reprezentaci knihy (s atributy název, autor, ISBN a cena). Dále napište funkce print_info pro výpis informací o knize a draw_cover pro vykreslení obálky knihy.

Výstup může vypadat třeba nějak takto (nemusíte si ale hrát se zarovnáním do středu):

name:   Cooking for Geeks
author: Jeff Potter
isbn:   0596805888
price:  $22

+-------------------------+
|                         |
|                         |
|                         |
|    Cooking for Geeks    |
|                         |
|                         |
|                         |
|       Jeff Potter       |
|                         |
|                         |
|                         |
|                         |
+-------------------------+

9.2. Objekty s metodami

9.2.1. Kruh

Napište třídu pro reprezentaci kruhu s daným středem a poloměrem. Implementujte metody pro výpočet obvodu, obsahu a vrácení informačního řetězce (__str__).

class Circle:

    def __init__(self, center: float, radius: float) -> None:
        pass

    def get_perimeter(self) -> float:
        pass

    def get_area(self) -> float:
        pass

    def __str__(self) -> str:
        pass

def test_circle() -> None:
    circle = Circle((-130, -130), 100)
    assert str(circle) == "Circle at (-130, -130) with radius 100"
    assert circle.get_area() == 31415.926535897932

9.2.2. Želvy

Prozkoumejte následující třídu pro reprezentaci kreslící želvy a vyzkoušejte si její použití.

class Turtle:
    def __init__(self) -> None:
        self.x = 0
        self.y = 0
        self.direction = 0
        self.lines = []

    def left(self, angle: float) -> None:
        self.direction -= angle

    def right(self, angle: float) -> None:
        self.direction += angle

    def forward(self, distance: float) -> None:
        nx = self.x + distance * math.cos(self.direction * math.pi / 180)
        ny = self.y + distance * math.sin(self.direction * math.pi / 180)

        self.lines.append((self.x, self.y, nx, ny))
        self.x, self.y = nx, ny

    def get_lines_svg(self) -> str:
        svg = ""
        for x1, y1, x2, y2 in self.lines:
            svg += """\t<line x1='{}' y1='{}' x2='{}' y2='{}'
                style='stroke: red; stroke-width:2' />\n""".format(x1, y1, x2, y2)
        return svg

    def save(self, filename: str) -> None:
        with open(filename, "w") as f:
            f.write('<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink= "http://www.w3.org/1999/xlink">\n')
            f.write(self.get_lines_svg())
            f.write("\n</svg>")
  • Přidejte želvě metodu pro vykreslení mnohoúhelníka.

  • Upravte želvu tak, aby začínala na náhodné pozici (třeba v intervalu 0-500).

  • Upravte želvu tak, aby uměla kreslit i jinou barvou než černou (např. můžete přidat metodu set_color(self, color: str) -> None).

  • Přidejte želvě metodu, která ji posune o danou vzdálenost náhodným směrem (random_step(self, distance: float) -> None).

  • Napište funkci chase(steps: int = 10) -> None. Funkce vytvoří 2 želvy, jedna bude honit druhou. První (černá) bude daný počet kroků náhodně chodit (třeba o 100), druhá (červená) bude vždy chodit směrem k první (taky třeba o 100). Nakonec funkce uloží obě želvy do jednoho svg (pozn.: není možné přímo použít metodu save()), můžete využít metodu:

    def turn_towards(self, other_turtle: 'Turtle') -> None:
        self.direction = math.atan2(other_turtle.y - self.y,
                            other_turtle.x - self.x) / math.pi * 180
    
  • Napište funkci multichase(steps: int = 10, turtles: int = 10) -> None, která vytvoří požadovaný počet želv a uloží je do seznamu; v každém kroku každá ze želv udělá krok (třeba 10) k nejbližší želvě (může se hodit metoda typu find_nearest(self, turtles: List['Turtle']) -> 'Turtle', která vrátí nejbližší želvu z daného seznamu).

  • Zkuste změnit želvy na tanky, které po sobě budou střílet.

9.2.3. Příšera

Doplňte následující kostru třídy a vyzkoušejte její funkčnost. Poté přidejte příšeře další metody, např. is_defeated, která vrací True, pokud už příšera nemá žádné životy.

9.2.4. Zásobník

Vytvořte třídu pro reprezentaci zásobníku podporující operace přidání prvku, odebrání prvku a test na prázdnost.

class Stack:
    pass


def test_stack() -> None:
    stack = Stack()
    stack.push(5)
    stack.push(10)
    assert stack.pop() == 10
    assert not stack.is_empty()
    assert stack.pop() == 5
    assert stack.is_empty()

9.2.5. Fronta

Vytvořte třídu pro reprezentaci fronty podporující operace přidání prvku, odebrání prvku, test na prázdnost, seřazení prvků ve frontě a konverzi fronty na zásobník.

class Queue:
    pass

def test_queue() -> None:
    queue = Queue()
    queue.enqueue(42)
    queue.enqueue(10)
    queue.enqueue(5)
    assert not queue.is_empty()
    assert queue.dequeue() == 42

    queue.sort()

    stack = queue.to_stack()
    assert stack.pop() == 10
    assert queue.dequeue() == 5

9.2.6. Knihovna

Vytvořte třídu Library pro reprezentaci kolekce knih s metodami pro přidání knihy, odebrání knihy, nalezení knihy podle názvu nebo ISBN, nalezení všech knih daného autora, nalezení všech knih s cenu pod zadanou mez. Pro reprezentaci knih využijte třídu Book ze cvičení Kniha.

9.2.7. Geometrické tvary

Napište třídy pro několik jednoduchých geometrických útvarů (čtverec, kruh, rovnostranný trojúhelník). Každá třída bude mít metody pro výpočet obvodu, výpočet obsahu a vrácení informačního řetězce (__str__). V následující ukázce si všimněte dynamické (pozdní) vazby jmen metod (např. get_perimeter na řádku 10) na jejich definice (tj. skutečně volaná metoda se různí podle toho, na jaký objekt zrovna ukazuje proměnná shape).

9.2.8. Vektory

Python umožňuje přetížit mnoho operátorů (např. +, * atp.) skrze tzv. magické funkce (např. __add__ pro přetížení operátoru +). S využitím následující kostry implementujte třídu reprezentující vektor

class Vector:
    def __init__(self, x: float, y: float) -> None:
        self.x = x
        self.y = y

    def __str__(self) -> str:
        return "(" + str(self.x) + ", " + str(self.y) + ")"

    def __add__(self, vector: 'Vector') -> 'Vector':
        """Return vector 'self'+'vector'."""
        pass  # TODO

    def __rmul__(self, c: float) -> 'Vector':
        """Return vector 'self'*c."""
        pass  # TODO

def test_vector() -> None:
    u = Vector(1, 3)
    v = Vector(2, 7)

    w = u + v
    assert w.x == 3
    assert w.y == 10

    w = 3 * u
    assert w.x == 3
    assert w.y == 9

9.2.9. Piškvorky

Vytvořte třídu Grid pro reprezentaci čtverečkovaného pole s rozehranou partií piškvorek. Využijte pak tuto třídu pro hru piškvorek mezi 2 hráči (střídají se 2 hráči (symboly ‘X’ a ‘O’), tah (poloha symbolu) se načítá jako vstup od uživatele, vyhrává hráč, který má 5 svých znaků v řadě.

class Grid:
    def __init__(self, size: int) -> None:
        pass

    def in_grid(self, coords: Tuple[int, int]) -> bool:
        """Return true iff corrds=(x,y) is in the grid."""
        pass

    def add_symbol(self, symbol: str, coords: Tuple[int, int]) -> bool:
        """
        Add symbol 'symbols' to grid at position 'coords' (if the coordine
        is valid).
        Returns True if successful, False otherwise.
        """
        pass

    def __str__(self) -> str:
        pass

    def check_five(self) -> bool:
        """Return true iff there are 5 same symbols in a row in a grid
        (vertically or horizonally or diagonally).
        """
        pass

    def generate_move(self, symbol: str = 'X') -> Optional[Tuple[int, int]]:
        """
        Generates random move so the move does not help an enemy to win
        and self player wins, if currennly possible.
        """
        pass

    def is_full(self) -> bool:
        """Return true iff the contains no empty fields."""
        pass

9.3. Doplňující zdroje

(Námět: napište funkci, která dostane seznam (barevných kruhů) a nějakým způsobem je vykresli – např. pomocí želví grafiky, knihovny Image, nebo uložením vektorového obrázku ve formátu SVG.)

Next Section - 10. Rekurze