Kurs Python · Moduł 3: Programowanie obiektowe
Klasy jako gatunki w naturze
W tej lekcji9
Witaj w Module 3! Tu Darwin, mam dla Ciebie nowy paradygmat programowania.
Do tej pory nauczyłeś/aś się programowania proceduralnego - pisałeś/aś funkcje, które przetwarzają dane. Teraz czas na Programowanie Obiektowe (OOP - Object-Oriented Programming) - sposób organizowania kodu wokół obiektów, które łączą dane i zachowania.
Wyobraź sobie klasyfikację biologiczną. Biologowie nie opisują każdego zwierzęcia od zera. Zamiast tego tworzą gatunki (species) ze wspólnymi cechami:
- Wszystkie lwy (Panthera leo) mają cztery łapy, są mięsożerne, żyją w stadach
- Wszystkie pytony królewskie (Python regius) są wężami, zjadają zdobycz w całości, polują nocą
W OOP robimy to samo - tworzymy klasy jako "szablony" dla obiektów!
Czym jest Programowanie Obiektowe?
OOP to paradygmat programowania oparty na koncepcji obiektów, które zawierają:
- Dane (atrybuty, właściwości) - opisują stan obiektu
- Zachowania (metody) - opisują co obiekt może robić
Kluczowe pojęcia OOP:
- Klasa (Class) - szablon, przepis, gatunek biologiczny
- Obiekt (Object) - konkretna instancja klasy, konkretne zwierzę
- Atrybuty (Attributes) - dane obiektu (cechy zwierzęcia)
- Metody (Methods) - funkcje związane z obiektem (zachowania zwierzęcia)
1# Prosty przykład - klasyfikacja biologiczna
2
3class Species:
4 """Klasa reprezentująca gatunek"""
5
6 def __init__(self, name, habitat, diet):
7 """Konstruktor - inicjalizuje obiekt"""
8 self.name = name
9 self.habitat = habitat
10 self.diet = diet
11
12 def describe(self):
13 """Metoda - opisz gatunek"""
14 return f"{self.name} - siedlisko: {self.habitat}, dieta: {self.diet}"
15
16# Tworzenie obiektów (instancji klasy)
17lion = Species("Lew", "sawanna", "mięso")
18python = Species("Pyton", "dżungla", "gryzonie")
19
20# Używanie obiektów
21print(lion.describe()) # "Lew - siedlisko: sawanna, dieta: mięso"
22print(python.describe()) # "Pyton - siedlisko: dżungla, dieta: gryzonie"W tym przykładzie są dwa nowe elementy. __init__ to konstruktor: Python wywołuje go sam przy tworzeniu obiektu, np. Species("Lew", "sawanna", "mięso"). self to odwołanie do aktualnego obiektu, czyli do tego konkretnego zwierzęcia: self.name = name zapisuje nazwę właśnie w nim. Dokładniej omówimy oba w następnej lekcji.
Dlaczego OOP?
Bez OOP (proceduralnie)
Zacznijmy od stylu, który już znasz: każda cecha zwierzęcia to osobna zmienna, a funkcja dostaje je wszystkie jako argumenty.
1# Dane i funkcje są oddzielne
2lion_name = "Lew"
3lion_habitat = "sawanna"
4lion_diet = "mięso"
5
6python_name = "Pyton"
7python_habitat = "dżungla"
8python_diet = "gryzonie"
9
10def describe_animal(name, habitat, diet):
11 return f"{name} - siedlisko: {habitat}, dieta: {diet}"
12
13print(describe_animal(lion_name, lion_habitat, lion_diet))
14print(describe_animal(python_name, python_habitat, python_diet))Dla dwóch zwierząt to jeszcze działa, ale każde kolejne to trzy nowe zmienne do śledzenia. Przy dwudziestu gatunkach łatwo pomylić nazwy i podać funkcji siedlisko lwa razem z dietą pytona.
Z OOP
Teraz te same dwa zwierzęta zapisane jako obiekty: klasa Animal trzyma dane i metodę describe() w jednym miejscu.
1# Dane i funkcje są razem w obiektach
2class Animal:
3 def __init__(self, name, habitat, diet):
4 self.name = name
5 self.habitat = habitat
6 self.diet = diet
7
8 def describe(self):
9 return f"{self.name} - siedlisko: {self.habitat}, dieta: {self.diet}"
10
11lion = Animal("Lew", "sawanna", "mięso")
12python = Animal("Pyton", "dżungla", "gryzonie")
13
14print(lion.describe())
15print(python.describe())Wynik jest taki sam, ale kod jest lepiej zorganizowany: dane i zachowania są razem, a kolejne zwierzę to jedna linia Animal(...) zamiast trzech nowych zmiennych.
4 Filary OOP
1. Enkapsulacja (Encapsulation)
Grupowanie danych i metod w jednym obiekcie + ukrywanie szczegółów implementacji.
1class BankAccount:
2 def __init__(self, balance):
3 self.__balance = balance # Prywatny atrybut (__)
4
5 def deposit(self, amount):
6 """Publiczna metoda"""
7 if amount > 0:
8 self.__balance += amount
9
10 def get_balance(self):
11 """Publiczny dostęp do prywatnego atrybutu"""
12 return self.__balance
13
14account = BankAccount(1000)
15account.deposit(500)
16print(account.get_balance()) # 1500Saldo zmienia się wyłącznie przez metodę deposit(), która przyjmuje tylko dodatnie kwoty. Próba odczytu account.__balance spoza klasy kończy się błędem AttributeError, bo atrybut z dwoma podkreśleniami jest prywatny.
2. Abstrakcja (Abstraction)
Ukrywanie złożoności, pokazywanie tylko niezbędnego interfejsu.
1class Car:
2 def start_engine(self):
3 """Prosty interfejs"""
4 self.__check_fuel()
5 self.__ignite_spark_plugs()
6 self.__start_motor()
7 print("Silnik uruchomiony!")
8
9 def __check_fuel(self):
10 """Ukryta implementacja"""
11 pass
12
13 def __ignite_spark_plugs(self):
14 """Ukryta implementacja"""
15 pass
16
17 def __start_motor(self):
18 """Ukryta implementacja"""
19 pass
20
21car = Car()
22car.start_engine() # Prosty interfejs - nie musisz znać szczegółów!3. Dziedziczenie (Inheritance)
Tworzenie nowych klas na bazie istniejących - hierarchia taksonomiczna!
1class Animal:
2 """Klasa bazowa (parent, superclass)"""
3 def __init__(self, name):
4 self.name = name
5
6 def eat(self):
7 return f"{self.name} je"
8
9class Mammal(Animal):
10 """Klasa pochodna (child, subclass)"""
11 def __init__(self, name, fur_color):
12 super().__init__(name) # Wywołaj konstruktor klasy bazowej
13 self.fur_color = fur_color
14
15 def nurse_young(self):
16 return f"{self.name} karmi młode mlekiem"
17
18lion = Mammal("Lew", "złoty")
19print(lion.eat()) # Dziedziczone z Animal
20print(lion.nurse_young()) # Własna metoda Mammal4. Polimorfizm (Polymorphism)
Różne obiekty mogą reagować na tę samą metodę na różne sposoby.
1class Dog:
2 def speak(self):
3 return "Hau hau!"
4
5class Cat:
6 def speak(self):
7 return "Miau!"
8
9class Cow:
10 def speak(self):
11 return "Muuu!"
12
13# Polimorfizm - ta sama metoda, różne zachowania
14animals = [Dog(), Cat(), Cow()]
15for animal in animals:
16 print(animal.speak()) # Każdy "mówi" inaczej!Przykład z safari - Klasyfikacja gatunków
Połączmy te elementy w jednej klasie: gatunek ma nazwę naukową, siedlisko i listę obserwacji, a metody liczą osobniki i oceniają, czy gatunek jest zagrożony.
1class Species:
2 """
3 Klasa reprezentująca gatunek w ekspedycji Safari
4
5 Analogia: Biologiczny gatunek z cechami wspólnymi
6 """
7
8 def __init__(self, scientific_name, common_name, habitat, dangerous=False):
9 """
10 Konstruktor - inicjalizuje nowy gatunek
11
12 Args:
13 scientific_name: Nazwa naukowa (np. "Panthera leo")
14 common_name: Nazwa zwyczajowa (np. "Lew")
15 habitat: Środowisko naturalne
16 dangerous: Czy niebezpieczny dla ludzi
17 """
18 self.scientific_name = scientific_name
19 self.common_name = common_name
20 self.habitat = habitat
21 self.dangerous = dangerous
22 self.observations = [] # Lista obserwacji
23
24 def add_observation(self, location, count, notes=""):
25 """Dodaj obserwację gatunku"""
26 observation = {
27 "location": location,
28 "count": count,
29 "notes": notes
30 }
31 self.observations.append(observation)
32 print(f"Dodano obserwację: {count}x {self.common_name} w {location}")
33
34 def get_total_observed(self):
35 """Zwróć łączną liczbę zaobserwowanych osobników"""
36 return sum(obs["count"] for obs in self.observations)
37
38 def is_threatened(self):
39 """Sprawdź czy gatunek jest zagrożony (mniej niż 10 obserwacji)"""
40 return self.get_total_observed() < 10
41
42 def get_status(self):
43 """Zwróć status gatunku"""
44 total = self.get_total_observed()
45 if total == 0:
46 return "Nie zaobserwowano"
47 elif self.is_threatened():
48 return f"Zagrożony ({total} osobników)"
49 else:
50 return f"Stabilny ({total} osobników)"
51
52 def describe(self):
53 """Pełny opis gatunku"""
54 danger_status = "NIEBEZPIECZNY" if self.dangerous else "Bezpieczny"
55 return f"""
56╔═══════════════════════════════════════════════╗
57 {self.common_name} ({self.scientific_name})
58 Siedlisko: {self.habitat}
59 Status: {danger_status}
60 Obserwacje: {len(self.observations)}
61 Osobniki: {self.get_total_observed()}
62 Stan: {self.get_status()}
63╚═══════════════════════════════════════════════╝
64 """.strip()
65
66# Użycie
67lion = Species(
68 scientific_name="Panthera leo",
69 common_name="Lew",
70 habitat="sawanna",
71 dangerous=True
72)
73
74# Dodaj obserwacje
75lion.add_observation("Sawanna Północna", 5, "Stado polujące")
76lion.add_observation("Dolina Rift", 3, "Dwa dorosłe lwy z młodymi")
77lion.add_observation("Park Serengeti", 8)
78
79# Wyświetl informacje
80print(lion.describe())
81print(f"\nŁącznie zaobserwowano: {lion.get_total_observed()} lwów")
82print(f"Czy zagrożony? {'Tak' if lion.is_threatened() else 'Nie'}")Porównanie: Funkcje vs Klasy
Podejście proceduralne (funkcje i słowniki)
Najpierw gatunek jako zwykły słownik i trzy osobne funkcje, które ten słownik tworzą, zmieniają i podsumowują.
1def create_species(name, habitat):
2 """Zwraca słownik reprezentujący gatunek"""
3 return {
4 "name": name,
5 "habitat": habitat,
6 "observations": []
7 }
8
9def add_observation(species_dict, location, count):
10 """Modyfikuje słownik"""
11 species_dict["observations"].append({"location": location, "count": count})
12
13def get_total(species_dict):
14 """Oblicza ze słownika"""
15 return sum(obs["count"] for obs in species_dict["observations"])
16
17# Użycie - funkcje i dane są oddzielne
18lion = create_species("Lew", "sawanna")
19add_observation(lion, "Północ", 5)
20print(get_total(lion))Podejście obiektowe
Ta sama logika jako klasa: klucze słownika stają się atrybutami obiektu, a funkcje jego metodami.
1class Species:
2 def __init__(self, name, habitat):
3 self.name = name
4 self.habitat = habitat
5 self.observations = []
6
7 def add_observation(self, location, count):
8 """Dane i logika razem!"""
9 self.observations.append({"location": location, "count": count})
10
11 def get_total(self):
12 return sum(obs["count"] for obs in self.observations)
13
14# Użycie - dane i zachowania razem
15lion = Species("Lew", "sawanna")
16lion.add_observation("Północ", 5)
17print(lion.get_total())Zalety OOP:
- Dane i metody są razem (logiczne grupowanie)
- Łatwiejsze zarządzanie stanem
- Możliwość dziedziczenia i reużycia kodu
- Lepszy design dla złożonych systemów
- Enkapsulacja chroni dane
Kiedy używać OOP?
Użyj OOP gdy:
- Modelujesz rzeczywiste encje (zwierzęta, pojazdy, użytkowników)
- Potrzebujesz wielu podobnych obiektów z różnymi danymi
- Chcesz ukryć szczegóły implementacji (enkapsulacja)
- Potrzebujesz hierarchii i dziedziczenia
- Projektujesz API lub bibliotekę
Użyj funkcji/procedur gdy:
- Prosty, liniowy skrypt
- Przetwarzanie danych bez stanu
- Funkcje pomocnicze (utility functions)
- Nie potrzebujesz wielu instancji
Praktyczny przykład - menedżer ekspedycji
Na koniec większa klasa, która prowadzi całą ekspedycję: zapisuje członków zespołu i odkrycia z kolejnych dni, a potem drukuje raport.
1class Expedition:
2 """
3 Klasa zarządzająca ekspedycją Safari
4
5 Łączy dane (lokacja, zespół, odkrycia) i zachowania (dodawanie, raportowanie)
6 """
7
8 def __init__(self, name, leader, start_date):
9 self.name = name
10 self.leader = leader
11 self.start_date = start_date
12 self.team_members = []
13 self.discovered_species = []
14 self.days_elapsed = 0
15
16 def add_team_member(self, member_name, role):
17 """Dodaj członka zespołu"""
18 member = {"name": member_name, "role": role}
19 self.team_members.append(member)
20 print(f"{member_name} ({role}) dołączył/a do ekspedycji")
21
22 def discover_species(self, species_name, location, count=1):
23 """Zapisz odkrycie gatunku"""
24 discovery = {
25 "species": species_name,
26 "location": location,
27 "count": count,
28 "day": self.days_elapsed
29 }
30 self.discovered_species.append(discovery)
31 print(f"Dzień {self.days_elapsed}: Odkryto {count}x {species_name} w {location}")
32
33 def advance_day(self):
34 """Kolejny dzień ekspedycji"""
35 self.days_elapsed += 1
36 print(f"\nDzień {self.days_elapsed} ekspedycji '{self.name}'")
37
38 def get_statistics(self):
39 """Statystyki ekspedycji"""
40 unique_species = len(set(d["species"] for d in self.discovered_species))
41 total_animals = sum(d["count"] for d in self.discovered_species)
42
43 return {
44 "ekspedycja": self.name,
45 "lider": self.leader,
46 "dni": self.days_elapsed,
47 "zespół": len(self.team_members),
48 "unikalne_gatunki": unique_species,
49 "łącznie_osobników": total_animals
50 }
51
52 def generate_report(self):
53 """Wygeneruj raport ekspedycji"""
54 stats = self.get_statistics()
55
56 report = f"""
57╔══════════════════════════════════════════════════════════╗
58 RAPORT EKSPEDYCJI: {self.name}
59╠══════════════════════════════════════════════════════════╣
60 Lider: {self.leader}
61 Data rozpoczęcia: {self.start_date}
62 Dni w terenie: {stats['dni']}
63 Członkowie zespołu: {stats['zespół']}
64╠══════════════════════════════════════════════════════════╣
65 ODKRYCIA:
66 - Unikalne gatunki: {stats['unikalne_gatunki']}
67 - Łączna liczba osobników: {stats['łącznie_osobników']}
68╠══════════════════════════════════════════════════════════╣
69 ZESPÓŁ:
70 """
71
72 for member in self.team_members:
73 report += f"\n - {member['name']} ({member['role']})"
74
75 report += "\n╚══════════════════════════════════════════════════════════╝"
76 return report
77
78# Symulacja ekspedycji
79expedition = Expedition("Safari 2024", "Dr. Jane Wilson", "2024-06-01")
80
81# Dodaj zespół
82expedition.add_team_member("Darwin Brown", "Biolog")
83expedition.add_team_member("Alex Chen", "Fotograf")
84expedition.add_team_member("Maya Patel", "Przewodnik")
85
86# Dni ekspedycji
87expedition.advance_day()
88expedition.discover_species("Panthera leo", "Sawanna Północna", 5)
89expedition.discover_species("Loxodonta africana", "Dolina Słoni", 12)
90
91expedition.advance_day()
92expedition.discover_species("Python regius", "Dżungla", 2)
93expedition.discover_species("Panthera leo", "Sawanna Południowa", 3)
94
95expedition.advance_day()
96expedition.discover_species("Gorilla gorilla", "Las mglisty", 8)
97
98# Raport końcowy
99print("\n" + expedition.generate_report())Podsumowanie
W tej lekcji nauczyłeś/aś się:
- Czym jest Programowanie Obiektowe (OOP)
- Różnicy między podejściem proceduralnym a obiektowym
- 4 filarów OOP: enkapsulacja, abstrakcja, dziedziczenie, polimorfizm
- Dlaczego OOP jest potężne dla organizacji kodu
- Kiedy używać OOP, a kiedy funkcji
- Podstawowych konceptów: klasa, obiekt, atrybut, metoda
- Praktycznych przykładów Safari
Checkpoint
Przed przejściem dalej:
- Rozumiesz różnicę między klasą a obiektem
- Znasz 4 filary OOP (enkapsulacja, abstrakcja, dziedziczenie, polimorfizm)
- Rozumiesz dlaczego grupujemy dane i zachowania razem
- Widzisz analogię między klasami a gatunkami biologicznymi
- Wiesz kiedy używać OOP, a kiedy funkcji
Analogia Safari: Klasa to gatunek (Panthera leo), obiekt to konkretne zwierzę (konkretny lew o imieniu Simba)!
W następnej lekcji Darwin nauczy Cię jak tworzyć własne klasy i obiekty - stworzysz kompletny system klasyfikacji gatunków!
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Klasa w Pythonie to:
2. Metoda __init__ służy do:
To 2 z 4 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w pionie
Uporządkuj kroki tworzenia klasy i obiektu:
- Edytor kodu
Utwórz klasę Animal z metodą __init__, która przyjmuje name i species i zapisuje je w atrybutach self.name i self.species
- Edytor kodu
Zdefiniuj klasę Animal z __init__(self, name, species) i utwórz jej instancję: tiger = Animal('Tygrys', 'Panthera tigris')
- Układanie w poziomie
Ułóż nagłówek definicji klasy Animal:
- Klikanie w kolejności
Kliknij elementy nagłówka konstruktora, który przyjmuje parametr name:
- Układanie w pionie
Uporządkuj linie klasy Animal z konstruktorem (pierwsza metoda) i metodą describe():