Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Type hints - precyzyjna klasyfikacja

Witaj ponownie, @name! Darwin tutaj z lekcją o precyzji i dokumentacji kodu.

W biologii precyzyjna klasyfikacja jest kluczowa - "duże zwierzę" to nie to samo co "Panthera leo (lew afrykański), samiec, dorosły, 190kg". W Pythonie type hints (adnotacje typów) pozwalają na taką samą precyzję w kodzie!

1# Nieprecyzyjne - co to jest?
2def count_animals(animals):
3    return len(animals)
4
5# Precyzyjne - wiadomo co i dlaczego!
6def count_animals(animals: list[str]) -> int:
7    """Zlicz zwierzęta w liście"""
8    return len(animals)

Czym są type hints?

Type hints to adnotacje typów w Pythonie - dodatkowe informacje o typach zmiennych, parametrów funkcji i wartości zwracanych.

WAŻNE: Type hints są opcjonalne i NIE wpływają na działanie programu! Python je ignoruje podczas wykonywania kodu. Służą:

  • 📚 Dokumentacji - wyjaśniają intencje programisty
  • 🔍 IDE - lepsze podpowiedzi i autouzupełnianie
  • 🐛 Narzędziom - mypy, pyright mogą wykrywać błędy typów
  • 🧠 Czytelności - łatwiej zrozumieć kod
1# Bez type hints - musimy zgadywać
2def calculate_density(count, area):
3    return count / area
4
5# Z type hints - wszystko jasne!
6def calculate_density(count: int, area: float) -> float:
7    """Oblicz gęstość populacji (osobniki/km²)"""
8    return count / area

Podstawowe type hints

Zmienne

1# Podstawowe typy
2name: str = "Lew"
3age: int = 5
4weight: float = 190.5
5is_dangerous: bool = True
6
7# Python 3.9+ - małe litery!
8animals: list = ["Lew", "Słoń"]
9habitat_map: dict = {"Lew": "sawanna"}
10
11# Python 3.10+ - jeszcze lepiej z konkretnymi typami
12animals: list[str] = ["Lew", "Słoń", "Żyrafa"]
13populations: dict[str, int] = {"Lew": 500, "Słoń": 300}
14coordinates: tuple[float, float] = (51.5, -0.1)
15unique_species: set[str] = {"Lew", "Słoń"}

Funkcje - parametry i return type

1def greet(name: str) -> str:
2    """Przyjmuje str, zwraca str"""
3    return f"Witaj, {name}!"
4
5def add_numbers(a: int, b: int) -> int:
6    """Przyjmuje dwa inty, zwraca int"""
7    return a + b
8
9def calculate_average(numbers: list[float]) -> float:
10    """Przyjmuje listę floatów, zwraca float"""
11    return sum(numbers) / len(numbers)
12
13def print_info(message: str) -> None:
14    """None oznacza "nic nie zwraca" """
15    print(message)
16    # brak return lub return None

Typy z modułu
typing
(Python 3.9+)

Optional - może być None

1from typing import Optional
2
3def find_species(name: str) -> Optional[str]:
4    """
5    Zwraca gatunek lub None jeśli nie znaleziono
6
7    Optional[str] = str | None
8    """
9    database = {"Lew": "Panthera leo", "Słoń": "Loxodonta africana"}
10    return database.get(name)  # Może zwrócić str albo None
11
12result = find_species("Lew")  # Optional[str]
13if result is not None:
14    print(f"Znaleziono: {result}")

Union - jeden z kilku typów

1from typing import Union
2
3def process_id(animal_id: Union[int, str]) -> str:
4    """
5    Przyjmuje int LUB str
6
7    Python 3.10+: int | str
8    """
9    return f"ID: {animal_id}"
10
11process_id(123)       # OK - int
12process_id("LEO-45")  # OK - str

Python 3.10+: Możesz użyć

|
zamiast
Union
!

1def process_id(animal_id: int | str) -> str:
2    """Nowsza składnia - Python 3.10+"""
3    return f"ID: {animal_id}"
4
5# Optional też można zapisać krócej
6def find_species(name: str) -> str | None:
7    """str | None = Optional[str]"""
8    pass

List, Dict, Tuple, Set - z konkretnymi typami

1# Python 3.9+ - używaj małych liter!
2def process_animals(names: list[str]) -> dict[str, int]:
3    """Lista stringów → słownik string:int"""
4    return {name: len(name) for name in names}
5
6# Tuple - stała liczba elementów z określonymi typami
7def get_coordinates() -> tuple[float, float]:
8    """Zwraca (latitude, longitude)"""
9    return (51.5074, -0.1278)
10
11# Tuple - zmienna liczba elementów tego samego typu
12def get_observations() -> tuple[int, ...]:
13    """Tuple z dowolną liczbą intów"""
14    return (12, 45, 23, 67, 89)
15
16# Set
17def get_unique_habitats() -> set[str]:
18    """Zbiór unikalnych siedlisk"""
19    return {"sawanna", "dżungla", "góry"}

Any - dowolny typ

1from typing import Any
2
3def log_data(data: Any) -> None:
4    """Przyjmuje cokolwiek - używaj oszczędnie!"""
5    print(f"Logging: {data}")
6
7log_data(123)           # OK
8log_data("text")        # OK
9log_data([1, 2, 3])     # OK
10log_data({"key": "val"})  # OK

Uwaga: Używaj

Any
tylko gdy naprawdę akceptujesz dowolny typ! To jak rezygnacja z type hints.

Type hints dla klas

1class Species:
2    """Gatunek z type hints"""
3
4    # Atrybuty klasowe
5    kingdom: str = "Animalia"
6    all_species: list['Species'] = []  # Forward reference
7
8    def __init__(
9        self,
10        scientific_name: str,
11        common_name: str,
12        population: int,
13        endangered: bool = False
14    ) -> None:
15        """Konstruktor z type hints"""
16        self.scientific_name: str = scientific_name
17        self.common_name: str = common_name
18        self.population: int = population
19        self.endangered: bool = endangered
20        self.observations: list[dict[str, Any]] = []
21
22        Species.all_species.append(self)
23
24    def add_observation(
25        self,
26        location: str,
27        count: int,
28        date: str | None = None
29    ) -> None:
30        """Dodaj obserwację - None return type"""
31        obs: dict[str, str | int] = {
32            "location": location,
33            "count": count
34        }
35        if date:
36            obs["date"] = date
37        self.observations.append(obs)
38
39    def get_population_trend(self) -> float:
40        """Zwraca float"""
41        if len(self.observations) < 2:
42            return 0.0
43
44        first = self.observations[0]["count"]
45        last = self.observations[-1]["count"]
46        return (last - first) / first * 100
47
48    @classmethod
49    def get_endangered_species(cls) -> list['Species']:
50        """Zwraca listę Species - forward reference!"""
51        return [s for s in cls.all_species if s.endangered]
52
53    @staticmethod
54    def is_valid_population(pop: int) -> bool:
55        """Zwraca bool"""
56        return pop >= 0

Zaawansowane type hints

Callable - funkcje jako typy

1from typing import Callable
2
3def apply_filter(
4    species_list: list[Species],
5    filter_func: Callable[[Species], bool]
6) -> list[Species]:
7    """
8    Filtruj listę przez funkcję
9
10    Callable[[Species], bool] = funkcja przyjmująca Species, zwracająca bool
11    """
12    return [s for s in species_list if filter_func(s)]
13
14# Użycie
15def is_endangered(species: Species) -> bool:
16    return species.endangered
17
18endangered = apply_filter(all_species, is_endangered)

TypeAlias - aliasy dla złożonych typów

1from typing import TypeAlias
2
3# Alias dla czytelności
4PopulationData: TypeAlias = dict[str, list[int]]
5Coordinates: TypeAlias = tuple[float, float]
6SpeciesDict: TypeAlias = dict[str, Species]
7
8def analyze_populations(data: PopulationData) -> float:
9    """Typ PopulationData jest bardziej czytelny niż dict[str, list[int]]"""
10    all_counts: list[int] = []
11    for counts in data.values():
12        all_counts.extend(counts)
13    return sum(all_counts) / len(all_counts)
14
15def get_location(name: str) -> Coordinates:
16    """Coordinates jasno określa co zwracamy"""
17    locations: dict[str, Coordinates] = {
18        "Serengeti": (-2.3333, 34.8333),
19        "Masai Mara": (-1.5, 35.1667)
20    }
21    return locations.get(name, (0.0, 0.0))

Generic - klasy generyczne

1from typing import Generic, TypeVar
2
3T = TypeVar('T')  # Typ generyczny
4
5class Stack(Generic[T]):
6    """Stos generyczny - może przechowywać dowolny typ"""
7
8    def __init__(self) -> None:
9        self._items: list[T] = []
10
11    def push(self, item: T) -> None:
12        """Dodaj element typu T"""
13        self._items.append(item)
14
15    def pop(self) -> T:
16        """Usuń i zwróć element typu T"""
17        return self._items.pop()
18
19    def is_empty(self) -> bool:
20        return len(self._items) == 0
21
22# Użycie
23string_stack: Stack[str] = Stack[str]()
24string_stack.push("Lew")
25string_stack.push("Słoń")
26animal: str = string_stack.pop()  # IDE wie, że to str!
27
28int_stack: Stack[int] = Stack[int]()
29int_stack.push(123)
30int_stack.push(456)
31number: int = int_stack.pop()  # IDE wie, że to int!

Safari przykład - kompletny system z type hints

1from typing import Optional, TypeAlias
2from datetime import date
3from enum import Enum
4
5# === TYPE ALIASES ===
6
7SpeciesID: TypeAlias = str
8Coordinates: TypeAlias = tuple[float, float]
9ObservationData: TypeAlias = dict[str, str | int | date]
10
11# === ENUMS ===
12
13class ConservationStatus(Enum):
14    """Status ochrony gatunku"""
15    EXTINCT = "extinct"
16    EXTINCT_IN_WILD = "extinct_in_wild"
17    CRITICALLY_ENDANGERED = "critically_endangered"
18    ENDANGERED = "endangered"
19    VULNERABLE = "vulnerable"
20    NEAR_THREATENED = "near_threatened"
21    LEAST_CONCERN = "least_concern"
22
23class Habitat(Enum):
24    """Typ siedliska"""
25    SAVANNA = "sawanna"
26    JUNGLE = "dżungla"
27    MOUNTAINS = "góry"
28    DESERT = "pustynia"
29    WETLANDS = "mokradła"
30
31# === MAIN CLASS ===
32
33class Species:
34    """
35    Klasa gatunku z pełnymi type hints
36
37    Precyzyjna klasyfikacja jak w biologii!
38    """
39
40    # Atrybut klasowy
41    _registry: dict[SpeciesID, 'Species'] = {}
42
43    def __init__(
44        self,
45        scientific_name: str,
46        common_name: str,
47        population: int,
48        habitat: Habitat,
49        status: ConservationStatus,
50        dangerous: bool = False
51    ) -> None:
52        """
53        Inicjalizacja gatunku
54
55        Args:
56            scientific_name: Nazwa naukowa (np. "Panthera leo")
57            common_name: Nazwa zwyczajowa (np. "Lew")
58            population: Liczba osobników
59            habitat: Typ siedliska (enum)
60            status: Status ochrony (enum)
61            dangerous: Czy niebezpieczny dla ludzi
62        """
63        self.id: SpeciesID = scientific_name
64        self.scientific_name: str = scientific_name
65        self.common_name: str = common_name
66        self.population: int = population
67        self.habitat: Habitat = habitat
68        self.status: ConservationStatus = status
69        self.dangerous: bool = dangerous
70
71        # Lista obserwacji
72        self.observations: list[ObservationData] = []
73
74        # Lokalizacje
75        self.locations: set[Coordinates] = set()
76
77        # Rejestruj
78        Species._registry[self.id] = self
79
80    def add_observation(
81        self,
82        location: str,
83        coordinates: Coordinates,
84        count: int,
85        observation_date: date | None = None
86    ) -> None:
87        """
88        Dodaj obserwację gatunku
89
90        Args:
91            location: Nazwa lokalizacji
92            coordinates: (latitude, longitude)
93            count: Liczba zaobserwowanych osobników
94            observation_date: Data obserwacji (opcjonalna)
95        """
96        obs: ObservationData = {
97            "location": location,
98            "count": count,
99            "date": observation_date or date.today()
100        }
101        self.observations.append(obs)
102        self.locations.add(coordinates)
103
104    def get_total_observed(self) -> int:
105        """Zwróć łączną liczbę zaobserwowanych osobników"""
106        return sum(
107            int(obs["count"])
108            for obs in self.observations
109        )
110
111    def get_observation_locations(self) -> list[str]:
112        """Zwróć listę unikalnych lokalizacji obserwacji"""
113        locations: set[str] = {
114            str(obs["location"])
115            for obs in self.observations
116        }
117        return sorted(locations)
118
119    def is_threatened(self) -> bool:
120        """Sprawdź czy gatunek jest zagrożony"""
121        threatened_statuses: set[ConservationStatus] = {
122            ConservationStatus.CRITICALLY_ENDANGERED,
123            ConservationStatus.ENDANGERED,
124            ConservationStatus.VULNERABLE
125        }
126        return self.status in threatened_statuses
127
128    def get_risk_assessment(self) -> dict[str, str | int | bool]:
129        """
130        Ocena ryzyka dla ekspedycji
131
132        Returns:
133            Słownik z oceną ryzyka
134        """
135        risk_level: int = 0
136
137        if self.dangerous:
138            risk_level += 5
139
140        if self.population < 100:
141            risk_level += 3  # Rzadkie - trudne do znalezienia
142
143        if self.habitat == Habitat.JUNGLE:
144            risk_level += 2  # Trudny teren
145
146        return {
147            "species": self.common_name,
148            "risk_level": min(10, risk_level),
149            "dangerous": self.dangerous,
150            "rare": self.population < 100,
151            "recommendation": "Extreme caution" if risk_level >= 7 else "Normal protocol"
152        }
153
154    @classmethod
155    def get_by_id(cls, species_id: SpeciesID) -> Optional['Species']:
156        """
157        Znajdź gatunek po ID
158
159        Returns:
160            Species lub None jeśli nie znaleziono
161        """
162        return cls._registry.get(species_id)
163
164    @classmethod
165    def get_by_habitat(cls, habitat: Habitat) -> list['Species']:
166        """Zwróć wszystkie gatunki z danego siedliska"""
167        return [
168            species for species in cls._registry.values()
169            if species.habitat == habitat
170        ]
171
172    @classmethod
173    def get_endangered(cls) -> list['Species']:
174        """Zwróć wszystkie zagrożone gatunki"""
175        return [
176            species for species in cls._registry.values()
177            if species.is_threatened()
178        ]
179
180    @staticmethod
181    def calculate_biodiversity_index(
182        species_list: list['Species']
183    ) -> float:
184        """
185        Oblicz indeks bioróżnorodności Simpson'a
186
187        Args:
188            species_list: Lista gatunków do analizy
189
190        Returns:
191            Indeks (0.0 - 1.0, wyższy = większa różnorodność)
192        """
193        if not species_list:
194            return 0.0
195
196        total: int = sum(s.population for s in species_list)
197        if total == 0:
198            return 0.0
199
200        sum_squares: float = sum(
201            (s.population / total) ** 2
202            for s in species_list
203        )
204
205        return 1.0 - sum_squares
206
207    def __str__(self) -> str:
208        return f"{self.common_name} ({self.population} osobników)"
209
210    def __repr__(self) -> str:
211        return (f"Species(scientific_name='{self.scientific_name}', "
212                f"population={self.population}, "
213                f"habitat={self.habitat.value})")
214
215# === FUNKCJE POMOCNICZE ===
216
217def generate_report(species: Species) -> str:
218    """
219    Wygeneruj raport o gatunku
220
221    Args:
222        species: Gatunek do raportu
223
224    Returns:
225        Sformatowany raport tekstowy
226    """
227    threatened: str = "⚠️ TAK" if species.is_threatened() else "✓ Nie"
228    risk: dict[str, str | int | bool] = species.get_risk_assessment()
229
230    report: str = f"""
231╔══════════════════════════════════════════════════╗
232  {species.common_name.upper()}
233╠══════════════════════════════════════════════════╣
234  Nazwa naukowa: {species.scientific_name}
235  Populacja: {species.population} osobników
236  Siedlisko: {species.habitat.value}
237  Status: {species.status.value}
238  Zagrożony: {threatened}
239  Niebezpieczny: {"⚠️ TAK" if species.dangerous else "✓ Nie"}
240  Poziom ryzyka: {risk['risk_level']}/10
241  Obserwacje: {len(species.observations)}
242  Lokalizacje: {', '.join(species.get_observation_locations()) or 'Brak'}
243╚══════════════════════════════════════════════════╝
244    """.strip()
245
246    return report
247
248def filter_by_population(
249    species_list: list[Species],
250    min_pop: int,
251    max_pop: int | None = None
252) -> list[Species]:
253    """
254    Filtruj gatunki po populacji
255
256    Args:
257        species_list: Lista gatunków
258        min_pop: Minimalna populacja
259        max_pop: Maksymalna populacja (opcjonalna)
260
261    Returns:
262        Przefiltrowana lista
263    """
264    filtered: list[Species] = [
265        s for s in species_list
266        if s.population >= min_pop
267    ]
268
269    if max_pop is not None:
270        filtered = [s for s in filtered if s.population <= max_pop]
271
272    return filtered
273
274# === DEMONSTRACJA ===
275
276print("=== KATALOG GATUNKÓW Z TYPE HINTS ===\n")
277
278# Tworzenie gatunków
279lion = Species(
280    scientific_name="Panthera leo",
281    common_name="Lew",
282    population=120,
283    habitat=Habitat.SAVANNA,
284    status=ConservationStatus.VULNERABLE,
285    dangerous=True
286)
287
288rhino = Species(
289    scientific_name="Diceros bicornis",
290    common_name="Nosorożec czarny",
291    population=45,
292    habitat=Habitat.SAVANNA,
293    status=ConservationStatus.CRITICALLY_ENDANGERED,
294    dangerous=True
295)
296
297elephant = Species(
298    scientific_name="Loxodonta africana",
299    common_name="Słoń afrykański",
300    population=450,
301    habitat=Habitat.SAVANNA,
302    status=ConservationStatus.ENDANGERED,
303    dangerous=False
304)
305
306# Dodaj obserwacje
307lion.add_observation("Serengeti", (-2.3333, 34.8333), 12)
308lion.add_observation("Masai Mara", (-1.5, 35.1667), 8)
309rhino.add_observation("Ngorongoro", (-3.1792, 35.5500), 3)
310elephant.add_observation("Amboseli", (-2.6527, 37.2606), 35)
311
312# Raporty
313print(generate_report(lion))
314print()
315print(generate_report(rhino))
316
317# Filtrowanie
318print("\n=== GATUNKI ZAGROŻONE ===")
319endangered: list[Species] = Species.get_endangered()
320for species in endangered:
321    print(f"  ⚠️  {species.common_name}: {species.status.value}")
322
323# Filtrowanie po populacji
324print("\n=== GATUNKI Z POPULACJĄ < 100 ===")
325rare: list[Species] = filter_by_population(
326    list(Species._registry.values()),
327    min_pop=0,
328    max_pop=99
329)
330for species in rare:
331    print(f"  - {species.common_name}: {species.population} osobników")
332
333# Indeks bioróżnorodności
334biodiversity: float = Species.calculate_biodiversity_index(
335    list(Species._registry.values())
336)
337print(f"\nIndeks bioróżnorodności: {biodiversity:.3f}")

Narzędzia do sprawdzania typów

mypy - statyczne sprawdzanie typów

1# Instalacja
2pip install mypy
3
4# Sprawdź plik
5mypy script.py
6
7# Przykład błędu
8def add(a: int, b: int) -> int:
9    return a + b
10
11result: str = add(5, 10)  # mypy ERROR: incompatible types!

Type checking w IDE

Nowoczesne IDE (VS Code, PyCharm) automatycznie sprawdzają type hints:

  • ✅ Autocomplete - podpowiedzi bazują na typach
  • ✅ Error detection - wykrywanie błędów typów
  • ✅ Refactoring - bezpieczniejsze zmiany
  • ✅ Documentation - szybki podgląd sygnatur

Dobre praktyki type hints

1. Zawsze dodawaj type hints do publicznych API

1# ✅ Dobre - API publiczne z type hints
2class Species:
3    def add_observation(self, location: str, count: int) -> None:
4        pass
5
6# ❌ Złe - publiczne API bez typów
7class Species:
8    def add_observation(self, location, count):
9        pass

2. Używaj konkretnych typów zamiast Any

1# ❌ Złe - zbyt ogólne
2def process_data(data: Any) -> Any:
3    return data
4
5# ✅ Dobre - konkretne typy
6def process_data(data: dict[str, int]) -> list[int]:
7    return list(data.values())

3. Używaj Optional dla wartości None

1# ✅ Dobre - jasno określone
2def find_species(name: str) -> Species | None:
3    pass
4
5# ❌ Mylące - czy zwraca None?
6def find_species(name: str) -> Species:
7    pass

4. Dokumentuj złożone typy przez TypeAlias

1# ✅ Czytelne
2ObservationData: TypeAlias = dict[str, str | int | date]
3
4def add_observation(data: ObservationData) -> None:
5    pass
6
7# ❌ Nieczytelne
8def add_observation(data: dict[str, str | int | date]) -> None:
9    pass

Podsumowanie

W tej lekcji nauczyłeś/aś się:

  • ✅ Czym są type hints i po co ich używać
  • ✅ Podstawowych adnotacji: int, str, float, bool, list, dict
  • ✅ Optional i Union (oraz składni
    |
    w Python 3.10+)
  • ✅ Type hints dla funkcji (parametry i return types)
  • ✅ Type hints dla klas i metod
  • ✅ Zaawansowanych technik: Callable, TypeAlias, Generic
  • ✅ Enumów dla bezpiecznych wartości
  • ✅ Narzędzi jak mypy
  • ✅ Dobrych praktyk type hints

Checkpoint

Przed przejściem dalej:

  • [ ] Rozumiesz różnicę między type hints a runtime behavior
  • [ ] Potrafisz dodać type hints do funkcji
  • [ ] Znasz Optional i Union (oraz
    |
    )
  • [ ] Wiesz jak używać list[T], dict[K, V]
  • [ ] Rozumiesz TypeAlias i Generic

Analogia Safari: Type hints to precyzyjna klasyfikacja biologiczna - zamiast "duży kot" mówimy "Panthera leo, samiec, 5 lat, 190kg" - wszystko jasne! 📝🔍

W następnej lekcji Darwin nauczy Cię dekora torów - potężnego narzędzia do modyfikowania zachowań funkcji i klas! ✨🎭

Ir a CodeWorlds