Witaj ponownie, @name! Darwin tutaj, gotowy do głębszego zagłębienia w OOP.
W poprzedniej lekcji poznałeś/aś koncepcję klas i obiektów. Teraz czas nauczyć się jak tworzyć własne klasy od podstaw - tak jak biologowie klasyfikują nowe gatunki w terenie!
Klasa składa się z kilku kluczowych elementów:
1class Animal: # 1. Nazwa klasy (PascalCase)
2 """Dokumentacja klasy - opis co reprezentuje""" # 2. Docstring
3
4 species_count = 0 # 3. Atrybut klasowy (współdzielony)
5
6 def __init__(self, name, age): # 4. Konstruktor
7 """Inicjalizuje nowy obiekt"""
8 self.name = name # 5. Atrybuty instancji
9 self.age = age
10 Animal.species_count += 1 # Zwiększ licznik
11
12 def make_sound(self): # 6. Metoda instancji
13 """Zwierzę wydaje dźwięk"""
14 return f"{self.name} wydaje dźwięk!"
15
16 @classmethod # 7. Metoda klasowa
17 def get_species_count(cls):
18 """Zwróć liczbę stworzonych zwierząt"""
19 return cls.species_count
20
21 @staticmethod # 8. Metoda statyczna
22 def is_adult(age):
23 """Sprawdź czy dorosłe (bez dostępu do self/cls)"""
24 return age >= 2
25
26# Tworzenie obiektów
27lion = Animal("Simba", 3)
28elephant = Animal("Dumbo", 5)
29
30print(lion.name) # "Simba" - atrybut instancji
31print(Animal.get_species_count()) # 2 - metoda klasowa
32print(Animal.is_adult(3)) # True - metoda statycznaclassSkładnia:
class NazwaKlasy:Konwencje nazewnictwa:
Species, AnimalBehavior, DataProcessorLion ✅, nie RunsLion ❌1# ✅ Dobre nazwy
2class Species:
3 pass
4
5class ExpeditionTeam:
6 pass
7
8class GeneticAnalyzer:
9 pass
10
11# ❌ Złe nazwy
12class species: # małe litery
13 pass
14
15class data: # zbyt ogólne
16 pass
17
18class doSomething: # camelCase + czasownik
19 pass__init__Konstruktor (
__init__) to specjalna metoda wywoływana automatycznie przy tworzeniu obiektu.1class Species:
2 def __init__(self, name, habitat):
3 """
4 Konstruktor - inicjalizuje nowy gatunek
5
6 Args:
7 name: Nazwa gatunku
8 habitat: Środowisko naturalne
9 """
10 print(f"Tworzę nowy gatunek: {name}")
11 self.name = name # Atrybut instancji
12 self.habitat = habitat
13 self.discovered_date = "2024-01-01" # Domyślna wartość
14
15# Wywołanie konstruktora
16lion = Species("Lew", "sawanna")
17# Automatycznie wywołuje: Species.__init__(lion, "Lew", "sawanna")
18# Drukuje: "Tworzę nowy gatunek: Lew"
19
20print(lion.name) # "Lew"
21print(lion.habitat) # "sawanna"self
to referencja do aktualnego obiektu. Python automatycznie przekazuje obiekt jako pierwszy argument!self
1class Animal:
2 def __init__(self, name):
3 self.name = name # self = aktualny obiekt
4 # self.name oznacza "atrybut 'name' TEGO obiektu"
5
6 def greet(self):
7 # self pozwala dostać się do atrybutów obiektu
8 return f"Cześć, jestem {self.name}!"
9
10lion = Animal("Simba")
11# Wywołanie: lion.greet()
12# Python automatycznie: Animal.greet(lion) ← lion staje się self!
13
14elephant = Animal("Dumbo")
15# elephant.greet() → Animal.greet(elephant) ← elephant to self!
16
17print(lion.greet()) # "Cześć, jestem Simba!"
18print(elephant.greet()) # "Cześć, jestem Dumbo!"UWAGA: Możesz użyć innej nazwy niż
self, ale jest to silna konwencja Pythona!1# ✅ Standardowe (używaj tego!)
2def __init__(self, name):
3 self.name = name
4
5# 😕 Techniczne poprawne, ale NIE RÓB TAK
6def __init__(this, name): # Jak w Java/C#
7 this.name = name
8
9def __init__(obj, name): # Mylące
10 obj.name = nameAtrybuty instancji należą do konkretnego obiektu. Każdy obiekt ma własne wartości!
1class Species:
2 def __init__(self, name, count):
3 self.name = name # Atrybut instancji
4 self.count = count # Każdy obiekt ma własną wartość
5
6lion = Species("Lew", 500)
7elephant = Species("Słoń", 300)
8
9print(lion.count) # 500 - osobna wartość
10print(elephant.count) # 300 - osobna wartość
11
12lion.count = 550 # Zmienia TYLKO lwa
13print(lion.count) # 550
14print(elephant.count) # 300 - bez zmian!Atrybuty klasowe należą do klasy, są współdzielone przez wszystkie obiekty!
1class Species:
2 total_species = 0 # Atrybut klasowy - współdzielony!
3
4 def __init__(self, name):
5 self.name = name # Atrybut instancji
6 Species.total_species += 1 # Zwiększ współdzielony licznik
7
8lion = Species("Lew")
9elephant = Species("Słoń")
10python = Species("Pyton")
11
12print(Species.total_species) # 3 - klasa wie o wszystkich!
13print(lion.total_species) # 3 - można też przez instancję
14print(elephant.total_species) # 3 - ta sama wartość dla wszystkichKiedy używać którego?
| Typ atrybutu | Kiedy używać | Przykład | |--------------|--------------|----------| | Instancji | Dane unikalne dla obiektu |
self.name, self.age |
| Klasowy | Dane współdzielone | liczniki, stałe, konfiguracja |1class Species:
2 # Atrybuty klasowe
3 kingdom = "Animalia" # Stała dla wszystkich
4 species_count = 0 # Licznik
5
6 def __init__(self, name, population):
7 # Atrybuty instancji
8 self.name = name # Unikalny dla każdego
9 self.population = population
10 Species.species_count += 1
11
12lion = Species("Lew", 500)
13elephant = Species("Słoń", 300)
14
15print(Species.kingdom) # "Animalia" - współdzielone
16print(lion.name) # "Lew" - unikalny
17print(elephant.name) # "Słoń" - innyMetody instancji działają na konkretnym obiekcie, mają dostęp do
self.1class Species:
2 def __init__(self, name, population):
3 self.name = name
4 self.population = population
5 self.observations = []
6
7 def add_observation(self, location, count):
8 """Dodaj obserwację - metoda instancji"""
9 self.observations.append({
10 "location": location,
11 "count": count
12 })
13 print(f"✓ Obserwacja {self.name}: {count} w {location}")
14
15 def get_total_observed(self):
16 """Oblicz łączną liczbę obserwacji"""
17 return sum(obs["count"] for obs in self.observations)
18
19 def is_endangered(self):
20 """Sprawdź czy zagrożony (< 100 osobników)"""
21 return self.population < 100
22
23# Użycie
24lion = Species("Lew", 500)
25lion.add_observation("Serengeti", 25)
26lion.add_observation("Masai Mara", 18)
27
28print(f"Łącznie zaobserwowano: {lion.get_total_observed()}") # 43
29print(f"Zagrożony? {lion.is_endangered()}") # False@classmethodMetody klasowe działają na całej klasie, nie na konkretnym obiekcie. Mają dostęp do
cls.1class Species:
2 all_species = [] # Rejestr wszystkich gatunków
3
4 def __init__(self, name):
5 self.name = name
6 Species.all_species.append(self)
7
8 @classmethod
9 def get_species_count(cls):
10 """Zwróć liczbę wszystkich gatunków"""
11 return len(cls.all_species)
12
13 @classmethod
14 def find_by_name(cls, name):
15 """Znajdź gatunek po nazwie - metoda fabrykująca"""
16 for species in cls.all_species:
17 if species.name == name:
18 return species
19 return None
20
21 @classmethod
22 def clear_registry(cls):
23 """Wyczyść rejestr"""
24 cls.all_species.clear()
25
26# Użycie
27lion = Species("Lew")
28elephant = Species("Słoń")
29python = Species("Pyton")
30
31print(Species.get_species_count()) # 3 - metoda klasowa
32
33found = Species.find_by_name("Słoń")
34print(found.name) # "Słoń"Różnica
vs self
:cls
self = konkretny obiekt (instancja)cls = klasa (typ)@staticmethodMetody statyczne NIE mają dostępu ani do
self, ani do cls. To zwykłe funkcje w przestrzeni nazw klasy.1class Species:
2 @staticmethod
3 def is_valid_name(name):
4 """Sprawdź czy nazwa gatunku jest poprawna"""
5 # Brak self, brak cls - tylko argumenty!
6 return (
7 len(name) > 0 and
8 name[0].isupper() and
9 " " not in name
10 )
11
12 @staticmethod
13 def calculate_biodiversity_index(species_count, area_km2):
14 """Oblicz indeks bioróżnorodności"""
15 return species_count / area_km2 if area_km2 > 0 else 0
16
17# Użycie - nie potrzeba obiektu!
18print(Species.is_valid_name("Lew")) # True
19print(Species.is_valid_name("lew")) # False - mała litera
20print(Species.is_valid_name("")) # False - puste
21
22index = Species.calculate_biodiversity_index(150, 1000)
23print(f"Indeks bioróżnorodności: {index}") # 0.15Kiedy używać metod statycznych?
1class Species:
2 species_count = 0 # Atrybut klasowy
3
4 def __init__(self, name):
5 self.name = name
6 Species.species_count += 1
7
8 # 1. Metoda instancji - ma self
9 def describe(self):
10 """Dostęp do self.name"""
11 return f"Gatunek: {self.name}"
12
13 # 2. Metoda klasowa - ma cls
14 @classmethod
15 def get_count(cls):
16 """Dostęp do cls.species_count"""
17 return cls.species_count
18
19 # 3. Metoda statyczna - bez self/cls
20 @staticmethod
21 def is_valid_name(name):
22 """Tylko argumenty, brak dostępu do klasy/obiektu"""
23 return len(name) > 0
24
25lion = Species("Lew")
26
27# 1. Metoda instancji - potrzebuje obiektu
28print(lion.describe()) # OK - działa na obiekcie
29# print(Species.describe()) # BŁĄD - brak obiektu!
30
31# 2. Metoda klasowa - działa na klasie
32print(Species.get_count()) # OK - działa na klasie
33print(lion.get_count()) # OK - też działa (Python przekaże klasę)
34
35# 3. Metoda statyczna - działa zawsze
36print(Species.is_valid_name("Lew")) # OK
37print(lion.is_valid_name("Lew")) # OK - też działa1class SpeciesCatalog:
2 """
3 Kompletny system katalogowania gatunków Safari
4
5 Демонструє всі концепції: atrybuty, metody, konstruktor
6 """
7
8 # Atrybut klasowy - rejestr wszystkich gatunków
9 all_species = {} # {nazwa: obiekt Species}
10 next_id = 1
11
12 def __init__(self, scientific_name, common_name, habitat, diet, dangerous=False):
13 """
14 Konstruktor - tworzy nowy gatunek w katalogu
15
16 Args:
17 scientific_name: Nazwa naukowa (np. "Panthera leo")
18 common_name: Nazwa zwyczajowa (np. "Lew")
19 habitat: Środowisko ("sawanna", "dżungla", "góry", etc.)
20 diet: Dieta ("mięsożerca", "roślinożerca", "wszystkożerca")
21 dangerous: Czy niebezpieczny dla ludzi
22 """
23 # Atrybuty instancji - unikalny dla każdego gatunku
24 self.id = SpeciesCatalog.next_id
25 SpeciesCatalog.next_id += 1
26
27 self.scientific_name = scientific_name
28 self.common_name = common_name
29 self.habitat = habitat
30 self.diet = diet
31 self.dangerous = dangerous
32
33 self.observations = [] # Lista obserwacji
34 self.population_estimate = 0
35 self.conservation_status = "Unknown" # Nieznany domyślnie
36
37 # Dodaj do rejestru klasowego
38 SpeciesCatalog.all_species[scientific_name] = self
39
40 # === METODY INSTANCJI (działają na konkretnym gatunku) ===
41
42 def add_observation(self, date, location, count, notes=""):
43 """Dodaj obserwację gatunku w terenie"""
44 observation = {
45 "date": date,
46 "location": location,
47 "count": count,
48 "notes": notes
49 }
50 self.observations.append(observation)
51 self.population_estimate += count
52 return f"✓ Dodano obserwację: {count}x {self.common_name}"
53
54 def get_total_observed(self):
55 """Zwróć łączną liczbę zaobserwowanych osobników"""
56 return sum(obs["count"] for obs in self.observations)
57
58 def update_conservation_status(self):
59 """Aktualizuj status ochrony na podstawie populacji"""
60 total = self.get_total_observed()
61
62 if total == 0:
63 self.conservation_status = "Not observed"
64 elif total < 50:
65 self.conservation_status = "Critically Endangered"
66 elif total < 250:
67 self.conservation_status = "Endangered"
68 elif total < 1000:
69 self.conservation_status = "Vulnerable"
70 else:
71 self.conservation_status = "Least Concern"
72
73 return self.conservation_status
74
75 def get_risk_level(self):
76 """Oblicz poziom ryzyka dla zespołu (0-10)"""
77 if not self.dangerous:
78 return 0
79
80 # Bazowy poziom ryzyka dla niebezpiecznych
81 risk = 5
82
83 # Zwięsz ryzyko jeśli są głodne (mięsożerne)
84 if self.diet == "mięsożerca":
85 risk += 3
86
87 # Zmniejsz jeśli populacja mała (rzadko spotykane)
88 if self.get_total_observed() < 50:
89 risk -= 2
90
91 return max(0, min(10, risk)) # Ogranicz do 0-10
92
93 def generate_report(self):
94 """Wygeneruj raport o gatunku"""
95 self.update_conservation_status()
96
97 report = f"""
98┌─────────────────────────────────────────────────────────┐
99│ ID: {self.id:03d} - {self.common_name.upper()}
100├─────────────────────────────────────────────────────────┤
101│ Nazwa naukowa: {self.scientific_name}
102│ Siedlisko: {self.habitat}
103│ Dieta: {self.diet}
104│ Niebezpieczny: {'⚠️ TAK' if self.dangerous else '✓ Nie'}
105│ Poziom ryzyka: {self.get_risk_level()}/10
106├─────────────────────────────────────────────────────────┤
107│ OBSERWACJE:
108│ - Łączna liczba: {self.get_total_observed()} osobników
109│ - Liczba obserwacji: {len(self.observations)}
110│ - Status ochrony: {self.conservation_status}
111└─────────────────────────────────────────────────────────┘
112 """.strip()
113 return report
114
115 # === METODY KLASOWE (działają na całym katalogu) ===
116
117 @classmethod
118 def get_total_species(cls):
119 """Zwróć liczbę wszystkich zarejestrowanych gatunków"""
120 return len(cls.all_species)
121
122 @classmethod
123 def find_by_name(cls, scientific_name):
124 """Znajdź gatunek po nazwie naukowej"""
125 return cls.all_species.get(scientific_name)
126
127 @classmethod
128 def get_by_habitat(cls, habitat):
129 """Zwróć wszystkie gatunki z danego siedliska"""
130 return [
131 species for species in cls.all_species.values()
132 if species.habitat == habitat
133 ]
134
135 @classmethod
136 def get_dangerous_species(cls):
137 """Zwróć wszystkie niebezpieczne gatunki"""
138 return [
139 species for species in cls.all_species.values()
140 if species.dangerous
141 ]
142
143 @classmethod
144 def get_most_observed(cls, n=5):
145 """Zwróć n najczęściej obserwowanych gatunków"""
146 sorted_species = sorted(
147 cls.all_species.values(),
148 key=lambda s: s.get_total_observed(),
149 reverse=True
150 )
151 return sorted_species[:n]
152
153 @classmethod
154 def clear_catalog(cls):
155 """Wyczyść cały katalog (ostrożnie!)"""
156 cls.all_species.clear()
157 cls.next_id = 1
158 return "✓ Katalog wyczyszczony"
159
160 # === METODY STATYCZNE (narzędzia pomocnicze) ===
161
162 @staticmethod
163 def validate_scientific_name(name):
164 """Waliduj nazwę naukową (format: "Genus species")"""
165 parts = name.split()
166 if len(parts) != 2:
167 return False
168
169 genus, species = parts
170 # Genus z wielką, species z małą
171 return genus[0].isupper() and species[0].islower()
172
173 @staticmethod
174 def calculate_biodiversity_index(species_list):
175 """Oblicz indeks różnorodności Simpson'a"""
176 if not species_list:
177 return 0.0
178
179 total = sum(s.get_total_observed() for s in species_list)
180 if total == 0:
181 return 0.0
182
183 # Indeks Simpson'a: 1 - Σ(ni/N)²
184 sum_squares = sum(
185 (s.get_total_observed() / total) ** 2
186 for s in species_list
187 )
188 return 1 - sum_squares
189
190 @staticmethod
191 def format_scientific_name(genus, species):
192 """Formatuj nazwę naukową (kursywa-style dla konsoli)"""
193 return f"{genus.capitalize()} {species.lower()}"
194
195# === DEMONSTRACJA UŻYCIA ===
196
197print("=== TWORZENIE KATALOGU GATUNKÓW ===\n")
198
199# Tworzenie gatunków (konstruktor)
200lion = SpeciesCatalog(
201 scientific_name="Panthera leo",
202 common_name="Lew",
203 habitat="sawanna",
204 diet="mięsożerca",
205 dangerous=True
206)
207
208elephant = SpeciesCatalog(
209 scientific_name="Loxodonta africana",
210 common_name="Słoń afrykański",
211 habitat="sawanna",
212 diet="roślinożerca",
213 dangerous=False
214)
215
216python = SpeciesCatalog(
217 scientific_name="Python regius",
218 common_name="Pyton królewski",
219 habitat="dżungla",
220 diet="mięsożerca",
221 dangerous=False
222)
223
224crocodile = SpeciesCatalog(
225 scientific_name="Crocodylus niloticus",
226 common_name="Krokodyl nilowy",
227 habitat="rzeka",
228 diet="mięsożerca",
229 dangerous=True
230)
231
232# Dodawanie obserwacji (metody instancji)
233lion.add_observation("2024-01-15", "Serengeti", 12, "Stado polujące")
234lion.add_observation("2024-01-20", "Masai Mara", 8, "Rodzina z młodymi")
235elephant.add_observation("2024-01-16", "Amboseli", 45, "Duże stado")
236python.add_observation("2024-01-18", "Dżungla Konga", 3)
237crocodile.add_observation("2024-01-19", "Nil", 25, "Na brzegu rzeki")
238
239# Raporty dla poszczególnych gatunków
240print(lion.generate_report())
241print()
242print(elephant.generate_report())
243
244# Metody klasowe - statystyki całego katalogu
245print(f"\n=== STATYSTYKI KATALOGU ===")
246print(f"Łączna liczba gatunków: {SpeciesCatalog.get_total_species()}")
247
248print(f"\nGatunki sawanny:")
249for species in SpeciesCatalog.get_by_habitat("sawanna"):
250 print(f" - {species.common_name} ({species.get_total_observed()} osobników)")
251
252print(f"\nNiebezpieczne gatunki:")
253for species in SpeciesCatalog.get_dangerous_species():
254 risk = species.get_risk_level()
255 print(f" ⚠️ {species.common_name} - Ryzyko: {risk}/10")
256
257print(f"\nTop 3 najczęściej obserwowane:")
258for i, species in enumerate(SpeciesCatalog.get_most_observed(3), 1):
259 print(f" {i}. {species.common_name}: {species.get_total_observed()} osobników")
260
261# Metody statyczne - narzędzia
262print(f"\n=== WALIDACJA ===")
263names_to_test = ["Panthera leo", "panthera leo", "Lion", "Canis lupus familiaris"]
264for name in names_to_test:
265 valid = "✓" if SpeciesCatalog.validate_scientific_name(name) else "✗"
266 print(f"{valid} {name}")
267
268# Indeks bioróżnorodności
269all_species_list = list(SpeciesCatalog.all_species.values())
270biodiversity = SpeciesCatalog.calculate_biodiversity_index(all_species_list)
271print(f"\nIndeks bioróżnorodności: {biodiversity:.3f}")W tej lekcji nauczyłeś/aś się:
class__init__self do dostępu do atrybutówPrzed przejściem dalej:
self a cls@classmethod a kiedy @staticmethodKluczowa koncepcja:
self to "ja" - obiekt odnosi się do samego siebie, aby dostać się do swoich danych i zachowań!W następnej lekcji Darwin nauczy Cię dziedziczenia - jak tworzyć hierarchie klas jak w drzewie ewolucyjnym! 🌳🦁🐘