Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Tworzenie własnych gatunków

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!

Anatomia klasy w Pythonie

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 statyczna

1. Definiowanie klasy -
class

Składnia:

class NazwaKlasy:

Konwencje nazewnictwa:

  • PascalCase (każde słowo wielką literą):
    Species
    ,
    AnimalBehavior
    ,
    DataProcessor
  • Opisowa nazwa: co klasa reprezentuje
  • Rzeczownik, nie czasownik:
    Lion
    ✅, 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

2. Konstruktor -
__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"

Parameter
self

self
to referencja do aktualnego obiektu. Python automatycznie przekazuje obiekt jako pierwszy argument!

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 = name

3. Atrybuty instancji vs atrybuty klasowe

Atrybuty instancji

Atrybuty 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

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 wszystkich

Kiedy 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ń" - inny

4. Metody instancji

Metody 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

5. Metody klasowe -
@classmethod

Metody 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

self
vs
cls
:

  • self
    = konkretny obiekt (instancja)
  • cls
    = klasa (typ)

6. Metody statyczne -
@staticmethod

Metody 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.15

Kiedy używać metod statycznych?

  • ✅ Funkcje pomocnicze (utility) związane tematycznie z klasą
  • ✅ Walidatory, konwertery, kalkulatory
  • ✅ Nie potrzebują dostępu do atrybutów obiektu ani klasy

Porównanie typów metod

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ła

Safari przykład - System katalogowania gatunków

1class 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}")

Podsumowanie

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

  • ✅ Jak definiować klasy z
    class
  • ✅ Tworzenia konstruktora
    __init__
  • ✅ Używania
    self
    do dostępu do atrybutów
  • ✅ Różnicy między atrybutami instancji a klasowymi
  • ✅ Tworzenia metod instancji, klasowych i statycznych
  • ✅ Kiedy używać każdego typu metody
  • ✅ Budowania kompletnego systemu katalogowania

Checkpoint

Przed przejściem dalej:

  • [ ] Potrafisz zdefiniować klasę z konstruktorem
  • [ ] Rozumiesz różnicę między
    self
    a
    cls
  • [ ] Znasz różnicę między atrybutami instancji a klasowymi
  • [ ] Wiesz kiedy użyć
    @classmethod
    a kiedy
    @staticmethod
  • [ ] Potrafisz stworzyć obiekt i wywołać jego metody

Kluczowa 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! 🌳🦁🐘

Przejdź do CodeWorlds