Funkce a třídy

Programování v GIS 2

Jan Caha

2026-03-16

Programovací paradigmata

  • většina jazyků nestojí striktně na jednom paradigmatu, ale využívá jich vícero

  • imperativní (kód popisuje, co se bude dít)

    • procedurální programování (procedury, které se vzájemně volají)
    • objektové programování (objekty - data a operace s nimi asociované)
    • většina jazyků - např. Python, R, C++
  • deklarativní (kód popisuje požadovaný výsledek)

    • funkcionální, logické a reaktivní
    • např. jazyk SQL, ale i funkcionální přístupy v Pythonu a R

Python

  • pro běžné použití kombinujeme obvykle procedurální a objektový přístup
  • procedurální přístup - funkce, které mají parametry
  • objektový přístup - objekty, které mají metody (funkce asociované s objektem) a atributy (proměnné objektu)
  • není problém s kombinací, viz cvičení (GDAL/OGR má objekty, my si k nim píšeme funkce)

Příklad kombinace přístupů

class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y


def distance_from_origin(point: Point) -> float:
    return (point.x**2 + point.y**2) ** 0.5


point = Point(3, 4)
print(distance_from_origin(point))
5.0

Funkce

  • dělí kód na dílčí logické bloky, které mohou být znovu použitelné
  • měly by dělat jednu věc a pouze jednu věc
  • délka by neměla být velká (aby bylo možné tělo funkce logicky přečíst a pochopit)
  • snaha navrhovat funkce spíše obecně než specificky

Příklad

def celsius_to_fahrenheit(celsius: float) -> float:
    return celsius * 9 / 5 + 32


print(celsius_to_fahrenheit(20))
68.0

Funkce příklad

def fn(a: str, b: int, c: float = 0.5) -> float: ...
  • funkce se třemi proměnnými
    • první dvě povinné
    • třetí nepovinná s výchozí hodnotou

Pozor

Proměnné s výchozí hodnotou musí být až za proměnnými bez výchozí hodnoty. Nelze mít proměnnou bez výchozí hodnoty za proměnnou s výchozí hodnotou funkce by nebyla validní a Python by vykazoval chybu při snaze takovou funkce načíst.

  • funkce vrací float
  • mohou existovat funkce bez návratové hodnoty None
  • nezapomínat na typování parametrů a návratové hodnoty

Funkce obecná specifická

# specifické
def vynasob_dvema(a: float) -> float:
    return a * 2


# obecné
def vynasob(a: float, b: float) -> float:
    return a * b

Tvorba upravené funkce (předefinování parametrů)

  • funkce partial z modulu functools
  • vytvoří novou funkci, která má některé parametry přednastavené a uloží ji do proměnné
from functools import partial

# zadáme funkci a potom parametr(y) které chceme předdefinovat
multiply_hodnotu_dvema = partial(vynasob, b=2)
hodnota = multiply_hodnotu_dvema(3)

Objekty

  • třída je šablona objektu, jednotlivý objekt je pak instancí třídy
  • objekt je prvek, který má vlastní informace a se kterým můžeme vykonávat operace
  • metody, vlastnosti (property) a atributy
  • složitost se může různit

Objekty - příklad

class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def move_x(self, move_by: float) -> None:
        self.x = self.x + move_by

    def move_y(self, move_by: float) -> None:
        self.y = self.y + move_by

    def __repr__(self) -> str:
        return f"Bod na souřadnicích ({self.x, self.y})"

Objekty detaily

  • první parametr metody je vždy self (označení instance objektu)
  • atributy i metody mohou být veřejné nebo soukromé (neměly by být přímo dostupné zvenčí třídy) – v Pythonu se to řeší konvencí, soukromé atributy a metody začínají podtržítkem (např. _atribut), tyto metody a atributy nebudou nápovědy v IDE navrhovat a programátor by se měl ideálně jejich použití vyhnout
  • magické metody objektů (např. __init__, __str__, __add__, __eq__ a další) a jejich použití
  • atribut může být statický (přímo uložená hodnota objektu) nebo dynamický přes vlastnost s dekorátorem @property

Objekt - příklad property

class Circle:
    def __init__(self, radius: float):
        self.radius = radius

    @property
    def area(self) -> float:
        return 3.14159 * self.radius**2


circle = Circle(2)
print(f"Průměr kruhu: {circle.radius}")
print(f"Plocha kruhu: {circle.area}")
Průměr kruhu: 2
Plocha kruhu: 12.56636

Metody objektu

  • metody objektu jsou funkce, které jsou součástí objektu, první parametr je vždy self
  • metody mohou být i tzv. statické metody, které nemají přístup k atributům objektu, ale jsou součástí třídy, označují se dekorátorem @staticmethod

Objekt - příklad statické metody

class TextUtils:
    @staticmethod
    def words_count(text: str) -> int:
        return len(text.split())


print(TextUtils.words_count("Python classes and functions"))
4

Objekt - příklad

from typing import List, Tuple


class Polygon:
    def __init__(self, vertices: List[Tuple[float, float]]):
        self.vertices = vertices

    def add_vertex(self, vertex: Tuple[float, float]) -> None:
        """Přidá vrchol do polygonu."""
        self.vertices.append(vertex)

    @staticmethod
    def is_closed(vertices: List[Tuple[float, float]]) -> bool:
        """Vrací True, pokud je polygon uzavřený (první a poslední vrchol jsou stejné)."""
        return vertices[0] == vertices[-1]

    def close(self) -> None:
        """Pokud polygon není uzavřený, uzavře jej přidáním prvního vrcholu na konec."""
        if self.is_closed(self.vertices):
            return
        self.vertices.append(self.vertices[0])

    def perimeter(self) -> float:
        """Spočítá obvod polygonu."""
        if len(self.vertices) < 2:
            return 0.0

        perimeter_value = 0.0
        for index in range(len(self.vertices) - 1):
            x1, y1 = self.vertices[index]
            x2, y2 = self.vertices[index + 1]
            perimeter_value += ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
        return perimeter_value

    @property
    def vertex_count(self) -> int:
        return len(self.vertices)

    @property
    def bounds(self) -> Tuple[float, float, float, float]:
        """Vrací obálku polygonu ve formátu (min_x, min_y, max_x, max_y)."""
        x_values = [x for x, _ in self.vertices]
        y_values = [y for _, y in self.vertices]
        return min(x_values), min(y_values), max(x_values), max(y_values)

    def __repr__(self) -> str:
        return f"Polygon s {self.vertex_count} vrcholy. Hranice: {self.bounds}"

Použití objektu

poly = Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
print(f"Polygon je uzavřený: {Polygon.is_closed(poly.vertices)}")
poly.close()
print(poly)
print(f"Počet vrcholů polygonu: {poly.vertex_count}")
print(f"Obvod polygonu: {poly.perimeter():.2f}")
print(f"Obálka polygonu: {poly.bounds}")
Polygon je uzavřený: False
Polygon s 5 vrcholy. Hranice: (0, 0, 1, 1)
Počet vrcholů polygonu: 5
Obvod polygonu: 4.00
Obálka polygonu: (0, 0, 1, 1)

Dědičnost

  • třída může dědit od jiné třídy - přebírá její atributy a metody
  • lze přidat nové atributy a metody nebo přepsat existující
  • v praxi se s tím setkáváme běžně, např. GeoDataFrame dědí z DataFrame (přidává sloupec geometrie a prostorové metody)
  • syntaxe: class Potomek(Rodic):
  • funkce super() umožňuje volat metody rodičovské třídy
  • alternativní konstruktory lze tvořit pomocí @classmethod

Dědičnost - příklad

class Rectangle(Polygon):
    def __init__(self, x: float, y: float, width: float, height: float):
        vertices = [
            (x, y),
            (x + width, y),
            (x + width, y + height),
            (x, y + height),
            (x, y),
        ]
        super().__init__(vertices)
        self.width = width
        self.height = height

    @classmethod
    def from_opposite_corners(
        cls,
        point_a: Tuple[float, float],
        point_b: Tuple[float, float],
    ) -> "Rectangle":
        x1, y1 = point_a
        x2, y2 = point_b

        min_x = min(x1, x2)
        min_y = min(y1, y2)
        width = abs(x2 - x1)
        height = abs(y2 - y1)

        return cls(min_x, min_y, width, height)

    def area(self) -> float:
        """Vrací obsah obdélníku."""
        return self.width * self.height

    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

    def __repr__(self) -> str:
        return f"Obdélník {self.width}x{self.height}, hranice: {self.bounds}"

Dědičnost - použití

rect = Rectangle(0, 0, 5, 3)
print(rect)
print(f"Obsah obdélníku: {rect.area()}")
print(f"Obvod obdélníku: {rect.perimeter()}")
# Rectangle je zároveň i Polygon
print(f"Je polygon: {isinstance(rect, Polygon)}")
print(f"Je obdélník: {isinstance(rect, Rectangle)}")

rect_from_points = Rectangle.from_opposite_corners((2, 5), (-1, 1))
print(rect_from_points)
print(f"Obsah obdélníku z bodů: {rect_from_points.area()}")
Obdélník 5x3, hranice: (0, 0, 5, 3)
Obsah obdélníku: 15
Obvod obdélníku: 16
Je polygon: True
Je obdélník: True
Obdélník 3x4, hranice: (-1, 1, 2, 5)
Obsah obdélníku z bodů: 12

Docstringy

  • dokumentační řetězce uvnitř funkcí, tříd a modulů
  • umístěny hned za definicí funkce/třídy jako první řádek
  • slouží jako dokumentace, kterou lze programově číst (např. help(funkce))
  • IDE je zobrazuje jako nápovědu při najetí myší
  • extenze autoDocstring ve VS Code generuje šablonu

Docstring - příklad

def rectangle_area(width: float, height: float) -> float:
    """Return area of rectangle in square units."""
    return width * height


print(rectangle_area(5, 3))
help(rectangle_area)
15
Help on function rectangle_area in module __main__:

rectangle_area(width: float, height: float) -> float
    Return area of rectangle in square units.

Dotazy?