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)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żą:
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 / area1# 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ń"}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 Nonetyping (Python 3.9+)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}")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 - strPython 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 pass1# 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"}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"}) # OKUwaga: Używaj
Any tylko gdy naprawdę akceptujesz dowolny typ! To jak rezygnacja z type hints.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 >= 01from 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)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))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!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}")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!Nowoczesne IDE (VS Code, PyCharm) automatycznie sprawdzają type hints:
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 pass1# ❌ 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())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 pass1# ✅ 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 passW tej lekcji nauczyłeś/aś się:
| w Python 3.10+)Przed przejściem dalej:
|)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! ✨🎭