16. Python – obiektowo


Programowanie obiektowe (Object-Oriented Programming, OOP) to paradygmat programowania, który organizuje kod wokół obiektów i klas. Zamiast myśleć o programie jako o sekwencji instrukcji, myślisz o nim jako o zbiorze obiektów, które ze sobą współpracują.

O tym po co nam programie obiektowe możesz przeczytać niżej w materiale.

Kluczowe koncepcje:

  • Klasa – szablon/przepis na obiekt
  • Obiekt – konkretna instancja klasy
  • Atrybuty – dane/właściwości obiektu
  • Metody – funkcje należące do obiektu

Analogia:

  • Klasa = Przepis na ciasto
  • Obiekt = Konkretne ciasto upieczone według przepisu
  • Atrybuty = Składniki ciasta (mąka, jajka, cukier)
  • Metody = Akcje (pieczenie, dekorowanie)

Klasa i obiekt

Czym jest klasa?

Klasa to szablon/wzorzec definiujący:

  • Jakie dane będzie przechowywać obiekt (atrybuty)
  • Jakie operacje może wykonać (metody)

Analogia: Klasa to formularz, obiekt to wypełniony formularz.

Tworzenie pierwszej klasy

class Kot:
    pass  # Pusta klasa

# Tworzenie obiektu (instancji)
mruczek = Kot()
print(type(mruczek))  # <class '__main__.Kot'>

Klasa z atrybutami

class Test:
    # Atrybuty klasy (wspólne dla wszystkich instancji)
    wlasnosc = "Jestem własnością klasy Test"
    lista = ["kot", "pies", "królik"]
    slownik = {"k1": "v1", "k2": "v2"}

# Tworzenie obiektu
obiekt = Test()

# Dostęp do atrybutów
print(obiekt.wlasnosc)  # Jestem własnością klasy Test
print(obiekt.lista[0])  # kot
print(obiekt.slownik["k1"])  # v1

Prywatne atrybuty

W Pythonie nie ma prawdziwych modyfikatorów dostępu. Używamy konwencji:

  • nazwa – publiczne
  • _nazwa – chronione (konwencja, nie używaj poza klasą)
  • __nazwa – prywatne (name mangling)
class Test:
    publiczne = "Dostępne wszędzie"
    _chronione = "Konwencja - nie używaj"
    __prywatne = "Trudno dostępne"

obiekt = Test()
print(obiekt.publiczne)  # OK
# print(obiekt.__prywatne)  # AttributeError

# Można dostać się, ale to złamanie konwencji
print(obiekt._Test__prywatne)  # Zadziała, ale nie rób tego!

Metody

Metoda to funkcja zdefiniowana wewnątrz klasy.

Podstawowa metoda

class Pies:
    def szczekaj(self):
        # self - odwołanie do instancji obiektu
        print("Hau hau!")

azor = Pies()
azor.szczekaj()  # Hau hau!

Ważne: Pierwszy parametr metody to zawsze self (konwencja).

Metody z parametrami

class Kalkulator:
    def dodaj(self, a, b):
        return a + b
    
    def odejmij(self, a, b):
        return a - b

kalk = Kalkulator()
print(kalk.dodaj(5, 3))     # 8
print(kalk.odejmij(10, 4))  # 6

Metody korzystające z atrybutów

class Osoba:
    imie = "Jan"
    
    def przywitaj(self):
        # self.imie - dostęp do atrybutu
        print(f"Cześć, jestem {self.imie}!")

osoba = Osoba()
osoba.przywitaj()  # Cześć, jestem Jan!

Konstruktor – init

Konstruktor to specjalna metoda wywoływana automatycznie przy tworzeniu obiektu.

Podstawowy konstruktor

class Kot:
    def __init__(self, imie, wiek):
        """Konstruktor - inicjalizuje obiekt"""
        self.imie = imie  # Atrybut instancji
        self.wiek = wiek
    
    def info(self):
        print(f"{self.imie} ma {self.wiek} lat")

# Podczas tworzenia obiekt wywołuje __init__
mruczek = Kot("Mruczek", 3)
mruczek.info()  # Mruczek ma 3 lata

puszek = Kot("Puszek", 5)
puszek.info()  # Puszek ma 5 lat

Konstruktor z wartościami domyślnymi

class Gracz:
    def __init__(self, nick, hp=100, lvl=1):
        self.nick = nick
        self.hp = hp
        self.lvl = lvl
    
    def __str__(self):
        return f"{self.nick} (HP: {self.hp}, Lvl: {self.lvl})"

# Z domyślnymi wartościami
gracz1 = Gracz("Warrior")
print(gracz1)  # Warrior (HP: 100, Lvl: 1)

# Nadpisanie domyślnych
gracz2 = Gracz("Mage", hp=80, lvl=5)
print(gracz2)  # Mage (HP: 80, Lvl: 5)

Metody magiczne (dunder methods)

Metody magiczne to specjalne metody otoczone podwójnymi podkreślnikami: __nazwa__.

str – reprezentacja tekstowa

class Ksiazka:
    def __init__(self, tytul, autor):
        self.tytul = tytul
        self.autor = autor
    
    def __str__(self):
        """Wywoływane przez print() i str()"""
        return f'"{self.tytul}" - {self.autor}'

ksiazka = Ksiazka("Wiedźmin", "Andrzej Sapkowski")
print(ksiazka)  # "Wiedźmin" - Andrzej Sapkowski

repr – reprezentacja techniczna

class Ksiazka:
    def __init__(self, tytul, autor, rok):
        self.tytul = tytul
        self.autor = autor
        self.rok = rok
    
    def __str__(self):
        # Dla użytkownika
        return f'"{self.tytul}" - {self.autor} ({self.rok})'
    
    def __repr__(self):
        # Dla programisty/debugowania
        return f'Ksiazka(tytul="{self.tytul}", autor="{self.autor}", rok={self.rok})'

ksiazka = Ksiazka("Wiedźmin", "Sapkowski", 1990)
print(str(ksiazka))   # "Wiedźmin" - Sapkowski (1990)
print(repr(ksiazka))  # Ksiazka(tytul="Wiedźmin", autor="Sapkowski", rok=1990)

len – długość obiektu

class Playlista:
    def __init__(self, nazwa):
        self.nazwa = nazwa
        self.utwory = []
    
    def dodaj(self, utwor):
        self.utwory.append(utwor)
    
    def __len__(self):
        """Wywoływane przez len()"""
        return len(self.utwory)

pl = Playlista("Rock")
pl.dodaj("Song 1")
pl.dodaj("Song 2")
print(len(pl))  # 2

Operatory porównania

class Osoba:
    def __init__(self, imie, wiek):
        self.imie = imie
        self.wiek = wiek
    
    def __eq__(self, other):
        """Operator =="""
        return self.wiek == other.wiek
    
    def __lt__(self, other):
        """Operator <"""
        return self.wiek < other.wiek
    
    def __gt__(self, other):
        """Operator >"""
        return self.wiek > other.wiek

anna = Osoba("Anna", 25)
jan = Osoba("Jan", 30)

print(anna < jan)   # True
print(jan > anna)   # True
print(anna == jan)  # False

getitem i setitem – indeksowanie

class Lista:
    def __init__(self):
        self.dane = []
    
    def __getitem__(self, index):
        """Wywoływane przez lista[index]"""
        return self.dane[index]
    
    def __setitem__(self, index, value):
        """Wywoływane przez lista[index] = value"""
        self.dane[index] = value
    
    def dodaj(self, element):
        self.dane.append(element)

lista = Lista()
lista.dodaj("Python")
lista.dodaj("Java")
lista.dodaj("C++")

print(lista[0])  # Python - wywołuje __getitem__
lista[1] = "JavaScript"  # Wywołuje __setitem__
print(lista[1])  # JavaScript

Operatory matematyczne

class Wektor:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __add__(self, other):
        """Operator +"""
        return Wektor(self.x + other.x, self.y + other.y)
    
    def __mul__(self, skalar):
        """Operator *"""
        return Wektor(self.x * skalar, self.y * skalar)
    
    def __str__(self):
        return f"({self.x}, {self.y})"

v1 = Wektor(1, 2)
v2 = Wektor(3, 4)

v3 = v1 + v2  # Wywołuje __add__
print(v3)     # (4, 6)

v4 = v1 * 3   # Wywołuje __mul__
print(v4)     # (3, 6)

call – obiekt jako funkcja

class Mnoznik:
    def __init__(self, mnoznik):
        self.mnoznik = mnoznik
    
    def __call__(self, x):
        """Pozwala wywołać obiekt jak funkcję"""
        return x * self.mnoznik

razy_2 = Mnoznik(2)
razy_10 = Mnoznik(10)

print(razy_2(5))   # 10
print(razy_10(5))  # 50

Tabela metod magicznych

MetodaWywołanieOpis
__init__obj = Klasa()Konstruktor
__str__print(obj)str(obj)Reprezentacja tekstowa
__repr__repr(obj)Reprezentacja techniczna
__len__len(obj)Długość
__eq__obj1 == obj2Równość
__lt__obj1 < obj2Mniejsze niż
__gt__obj1 > obj2Większe niż
__add__obj1 + obj2Dodawanie
__sub__obj1 - obj2Odejmowanie
__mul__obj1 * obj2Mnożenie
__getitem__obj[key]Dostęp przez indeks
__setitem__obj[key] = valUstawianie przez indeks
__call__obj()Wywołanie jak funkcja
__del__Usuwanie obiektuDestruktor

Modyfikatory dostępu

Python nie ma prawdziwych modyfikatorów dostępu jak Java czy C++. Używa konwencji:

Publiczne (brak podkreślników)

class Osoba:
    def __init__(self, imie):
        self.imie = imie  # Publiczne

osoba = Osoba("Jan")
print(osoba.imie)  # OK
osoba.imie = "Anna"  # OK

Chronione (jeden podkreślnik)

class Osoba:
    def __init__(self, imie):
        self._imie = imie  # Chronione (konwencja)
    
    def _metoda_wewnetrzna(self):
        print("Używana tylko wewnątrz")

osoba = Osoba("Jan")
print(osoba._imie)  # Technicznie działa, ale nie powinieneś

Konwencja: _nazwa sugeruje „nie używaj tego poza klasą”.

Prywatne (dwa podkreślniki)

class Osoba:
    def __init__(self, imie):
        self.__imie = imie  # Prywatne
    
    def __metoda_prywatna(self):
        print("Prywatna metoda")
    
    def pokaz_imie(self):
        print(self.__imie)  # OK wewnątrz klasy

osoba = Osoba("Jan")
# print(osoba.__imie)  # AttributeError

# Python zmienia nazwę (name mangling)
print(osoba._Osoba__imie)  # Działa, ale nie rób tego!

Name mangling: Python zmienia __nazwa na _NazwaKlasy__nazwa.

Dziedziczenie

Dziedziczenie pozwala tworzyć nowe klasy bazując na istniejących.

Podstawowe dziedziczenie

class Zwierze:
    def __init__(self, imie):
        self.imie = imie
    
    def dzwiek(self):
        return "Jakiś dźwięk"

class Pies(Zwierze):  # Pies dziedziczy po Zwierze
    def dzwiek(self):
        return "Hau hau!"

class Kot(Zwierze):
    def dzwiek(self):
        return "Miau!"

pies = Pies("Azor")
kot = Kot("Mruczek")

print(f"{pies.imie}: {pies.dzwiek()}")  # Azor: Hau hau!
print(f"{kot.imie}: {kot.dzwiek()}")    # Mruczek: Miau!

super() – wywołanie metod rodzica

class Pojazd:
    def __init__(self, marka):
        self.marka = marka
        print(f"Utworzono pojazd: {marka}")
    
    def info(self):
        return f"Pojazd marki {self.marka}"

class Samochod(Pojazd):
    def __init__(self, marka, model):
        super().__init__(marka)  # Wywołanie konstruktora rodzica
        self.model = model
    
    def info(self):
        # Wywołanie metody rodzica
        podstawowa_info = super().info()
        return f"{podstawowa_info}, model {self.model}"

auto = Samochod("Toyota", "Corolla")
# Utworzono pojazd: Toyota
print(auto.info())
# Pojazd marki Toyota, model Corolla

isinstance() – sprawdzanie typu

class Zwierze:
    pass

class Pies(Zwierze):
    pass

class Kot(Zwierze):
    pass

pies = Pies()
kot = Kot()

print(isinstance(pies, Pies))      # True
print(isinstance(pies, Zwierze))   # True (dziedziczy!)
print(isinstance(pies, Kot))       # False
print(isinstance(pies, object))    # True (wszystko dziedziczy po object)

Wielodziedziczenie

Python obsługuje dziedziczenie z wielu klas (ostrożnie!).

class Latanie:
    def lataj(self):
        print("Lecę!")

class Plywanie:
    def plywaj(self):
        print("Pływam!")

class Kaczka(Latanie, Plywanie):
    def kwacz(self):
        print("Kwa kwa!")

kaczka = Kaczka()
kaczka.lataj()   # Lecę!
kaczka.plywaj()  # Pływam!
kaczka.kwacz()   # Kwa kwa!

Kompozycja vs Dziedziczenie

W OOP (programowaniu obiektowym) dziedziczenie i kompozycja to dwa sposoby budowania relacji między klasami.

Zadaj sobie pytanie:

Czy mogę powiedzieć: X jest Y

Przykłady:

  • Pies jest zwierzęciem – tak
  • Student jest człowiekiem – tak
  • Samochód jest pojazdem – tak

dziedziczenie

Czy mogę powiedzieć: X ma Y

Przykłady:

  • Samochód ma silnik – tak
  • Dom ma okna – tak
  • Komputer ma procesor – tak

kompozycja

Kiedy używać dziedziczenia?

Relacja „JEST” (is-a):

  • Pies JEST Zwierzęciem
  • Samochód JEST Pojazdem
class Zwierze:
    pass

class Pies(Zwierze):  # Pies JEST Zwierzęciem
    pass

Kiedy używać kompozycji?

Relacja „MA” (has-a):

  • Samochód MA Silnik
  • Osoba MA Adres
class Silnik:
    def __init__(self, moc):
        self.moc = moc
    
    def uruchom(self):
        print(f"Silnik {self.moc}KM uruchomiony")

class Samochod:
    def __init__(self, marka, moc_silnika):
        self.marka = marka
        self.silnik = Silnik(moc_silnika)  # MA silnik
    
    def uruchom(self):
        print(f"Uruchamiam {self.marka}")
        self.silnik.uruchom()

auto = Samochod("Toyota", 150)
auto.uruchom()
# Uruchamiam Toyota
# Silnik 150KM uruchomiony

Problem z dziedziczeniem

# ŹLE - nadmierne dziedziczenie
class Ptak:
    def lataj(self):
        print("Lecę")

class Pingwin(Ptak):
    # Problem! Pingwin nie lata, ale dziedziczy lataj()
    pass

pingwin = Pingwin()
pingwin.lataj()  # Pingwin lata? To błąd logiczny!

Rozwiązanie – kompozycja

# DOBRZE - kompozycja
class Latanie:
    def lataj(self):
        print("Lecę w powietrzu")

class Plywanie:
    def plywaj(self):
        print("Pływam w wodzie")

class Orzel:
    def __init__(self):
        self.latanie = Latanie()  # MA zdolność latania
    
    def wykonaj_lot(self):
        self.latanie.lataj()

class Pingwin:
    def __init__(self):
        self.plywanie = Plywanie()  # MA zdolność pływania
    
    def wykonaj_plywanie(self):
        self.plywanie.plywaj()

orzel = Orzel()
orzel.wykonaj_lot()  # Lecę w powietrzu

pingwin = Pingwin()
pingwin.wykonaj_plywanie()  # Pływam w wodzie

Złota zasada: „Prefer composition over inheritance” (Preferuj kompozycję nad dziedziczeniem)

Klasy abstrakcyjne

Klasa abstrakcyjna to klasa, która:

  • Nie może być instancjonowana (nie można z niej tworzyć obiektów)
  • Służy jako szablon dla klas potomnych
  • Może zawierać metody abstrakcyjne (bez implementacji)
from abc import ABC, abstractmethod

class Zwierze(ABC):
    def __init__(self, imie):
        self.imie = imie
    
    @abstractmethod
    def dzwiek(self):
        """Metoda abstrakcyjna - musi być zaimplementowana"""
        pass
    
    def przedstaw_sie(self):
        print(f"Jestem {self.imie} i mówię {self.dzwiek()}")

# zwierze = Zwierze("Test")  # TypeError! Nie można utworzyć

class Pies(Zwierze):
    def dzwiek(self):
        return "Hau hau!"

class Kot(Zwierze):
    def dzwiek(self):
        return "Miau!"

pies = Pies("Azor")
pies.przedstaw_sie()  # Jestem Azor i mówię Hau hau!

Dekorator @property

@property pozwala traktować metodę jak atrybut.

class Prostokat:
    def __init__(self, szerokosc, wysokosc):
        self.szerokosc = szerokosc
        self.wysokosc = wysokosc
    
    @property
    def pole(self):
        """Metoda jako właściwość"""
        return self.szerokosc * self.wysokosc
    
    @property
    def obwod(self):
        return 2 * (self.szerokosc + self.wysokosc)

prostokat = Prostokat(5, 10)
# Wywołanie bez nawiasów!
print(prostokat.pole)    # 50
print(prostokat.obwod)   # 30

Getter i setter

class Osoba:
    def __init__(self, imie, wiek):
        self._imie = imie
        self._wiek = wiek
    
    @property
    def wiek(self):
        """Getter"""
        return self._wiek
    
    @wiek.setter
    def wiek(self, wartosc):
        """Setter z walidacją"""
        if wartosc < 0:
            raise ValueError("Wiek nie może być ujemny!")
        self._wiek = wartosc

osoba = Osoba("Jan", 25)
print(osoba.wiek)  # 25 - wywołuje getter

osoba.wiek = 30    # Wywołuje setter
print(osoba.wiek)  # 30

# osoba.wiek = -5  # ValueError!

Paradygmaty OOP

1. Abstrakcja

Ukrywanie skomplikowanych szczegółów, pokazywanie tylko tego co potrzebne.

class Telefon:
    def __init__(self, model):
        self.model = model
        self.__system = "Android"  # Ukryte
    
    def dzwon(self, numer):
        """Prosty interfejs - nie musisz wiedzieć jak działa"""
        self.__polacz_z_sieciami()
        self.__wyslij_sygnal(numer)
        print(f"Dzwonię na {numer}")
    
    def __polacz_z_sieciami(self):
        """Ukryte szczegóły"""
        pass
    
    def __wyslij_sygnal(self, numer):
        """Ukryte szczegóły"""
        pass

telefon = Telefon("iPhone")
telefon.dzwon("123456789")  # Prosta, ukrywa złożoność

2. Enkapsulacja

Ukrywanie danych i kontrolowanie dostępu do nich.

class KontoBankowe:
    def __init__(self, saldo):
        self.__saldo = saldo  # Prywatne - enkapsulacja
    
    def wplata(self, kwota):
        if kwota > 0:
            self.__saldo += kwota
            return True
        return False
    
    def wyplata(self, kwota):
        if 0 < kwota <= self.__saldo:
            self.__saldo -= kwota
            return True
        return False
    
    @property
    def saldo(self):
        """Kontrolowany dostęp tylko do odczytu"""
        return self.__saldo

konto = KontoBankowe(1000)
konto.wplata(500)
print(konto.saldo)  # 1500
# konto.__saldo = 999999  # Nie działa - chronione!

3. Dziedziczenie

Tworzenie hierarchii klas, współdzielenie kodu.

class Pojazd:
    def __init__(self, marka):
        self.marka = marka
    
    def info(self):
        return f"Pojazd {self.marka}"

class Samochod(Pojazd):
    def __init__(self, marka, model):
        super().__init__(marka)
        self.model = model
    
    def info(self):
        return f"Samochód {self.marka} {self.model}"

class Motocykl(Pojazd):
    def info(self):
        return f"Motocykl {self.marka}"

4. Polimorfizm

Różne obiekty reagują na tę samą metodę w różny sposób.

class Pies:
    def dzwiek(self):
        return "Hau hau!"

class Kot:
    def dzwiek(self):
        return "Miau!"

class Krowa:
    def dzwiek(self):
        return "Muuu!"

# Polimorfizm - ta sama metoda, różne zachowania
zwierzeta = [Pies(), Kot(), Krowa()]

for zwierze in zwierzeta:
    print(zwierze.dzwiek())
# Hau hau!
# Miau!
# Muuu!

Przykłady praktyczne

Zadanie 1: Klasa Trójkąt

import random

class Trojkat:
    def __init__(self):
        self.wysokosc = random.randint(1, 20)
        self.podstawa = random.randint(1, 20)
    
    def oblicz_pole(self):
        return (self.podstawa * self.wysokosc) / 2
    
    def __str__(self):
        return f"Trójkąt(podstawa={self.podstawa}, wysokość={self.wysokosc})"

trojkat1 = Trojkat()
trojkat2 = Trojkat()

pole1 = trojkat1.oblicz_pole()
pole2 = trojkat2.oblicz_pole()

print(f"{trojkat1} - Pole: {pole1} cm²")
print(f"{trojkat2} - Pole: {pole2} cm²")

if pole1 > pole2:
    print(f"Trójkąt 1 jest większy")
else:
    print(f"Trójkąt 2 jest większy")

Zadanie 2: Okrąg i Koło

from math import pi

class Okrag:
    def __init__(self, promien):
        self.promien = promien
    
    def obwod(self):
        return 2 * pi * self.promien

class Kolo(Okrag):
    def pole(self):
        return pi * self.promien ** 2

kolo = Kolo(10)
print(f"Obwód: {kolo.obwod():.2f}")
print(f"Pole: {kolo.pole():.2f}")

Zadanie 3: Gracz RPG

import random

class Gracz:
    def __init__(self, nick):
        self.nick = nick
        self.poziom = 1
        self.hp = 100
        self.exp = 0
    
    def atak(self, cel):
        obrazenia = random.randint(10, 25)
        print(f"{self.nick} atakuje {cel.nick}!")
        cel.otrzymaj_obrazenia(obrazenia)
    
    def otrzymaj_obrazenia(self, ilosc):
        self.hp -= ilosc
        if self.hp < 0:
            self.hp = 0
        print(f"{self.nick} ma {self.hp} HP")
    
    def zdobadz_exp(self, ilosc):
        self.exp += ilosc
        if self.exp >= 100:
            self.poziom += 1
            self.exp = 0
            print(f"{self.nick} awansował na poziom {self.poziom}!")
    
    def __str__(self):
        return f"{self.nick} | Poziom: {self.poziom} | HP: {self.hp} | EXP: {self.exp}"

gracz1 = Gracz("Warrior")
gracz2 = Gracz("Mage")
gracz1.atak(gracz2)

Po co programowanie obiektowe?

1. Organizacja i modularność

Kod podzielony na logiczne części (klasy) jest bardziej przejrzysty.

# Bez OOP - wszystko w jednym miejscu
gracz_imie = "Jan"
gracz_hp = 100
gracz_lvl = 1

przeciwnik_imie = "Goblin"
przeciwnik_hp = 50
przeciwnik_lvl = 1

# Z OOP - każdy obiekt osobno
class Postac:
    def __init__(self, imie, hp, lvl):
        self.imie = imie
        self.hp = hp
        self.lvl = lvl

gracz = Postac("Jan", 100, 1)
przeciwnik = Postac("Goblin", 50, 1)

2. Reużywalność kodu

Możesz wykorzystywać klasy wielokrotnie.

class Pojazd:
    def __init__(self, marka):
        self.marka = marka
    
    def jedz(self):
        print(f"{self.marka} jedzie")

# Możesz utworzyć wiele obiektów tej samej klasy
samochod1 = Pojazd("Toyota")
samochod2 = Pojazd("BMW")
samochod3 = Pojazd("Audi")

3. Łatwiejsze rozwijanie aplikacji

Zmiany w jednej klasie nie wpływają na inne.

# Dodanie nowej funkcjonalności tylko w jednym miejscu
class Samochod:
    def __init__(self, marka):
        self.marka = marka
        self.predkosc = 0
    
    def przyspiesz(self, ile):
        self.predkosc += ile
        print(f"Prędkość: {self.predkosc} km/h")

4. Enkapsulacja – ukrywanie szczegółów

Dane są chronione, dostęp tylko przez metody.

class KontoBankowe:
    def __init__(self, saldo):
        self.__saldo = saldo  # Prywatne
    
    def wplac(self, kwota):
        if kwota > 0:
            self.__saldo += kwota
    
    def pobierz_saldo(self):
        return self.__saldo

# Nie można bezpośrednio zmieniać salda
konto = KontoBankowe(1000)
konto.wplac(500)  # OK
# konto.__saldo = 999999  # Nie zadziała!

5. Naturalne odwzorowanie rzeczywistości

OOP naśladuje sposób myślenia o świecie.

class Kot:
    def __init__(self, imie, kolor):
        self.imie = imie
        self.kolor = kolor
    
    def miaucz(self):
        print(f"{self.imie}: Miauuu!")

mruczek = Kot("Mruczek", "rudy")
mruczek.miaucz()  # Mruczek: Miauuu!

Podsumowanie

Podstawy OOP:

  • Klasa – szablon obiektu
  • Obiekt – instancja klasy
  • Atrybuty – dane obiektu
  • Metody – funkcje obiektu
  • self – odniesienie do instancji

Metody specjalne:

  • __init__ – konstruktor
  • __str__ – reprezentacja tekstowa
  • __len__ – długość
  • __eq____lt____gt__ – porównania
  • __add____mul__ – operatory matematyczne
  • __getitem____setitem__ – indeksowanie

Zaawansowane:

  • Dziedziczenie – tworzenie hierarchii klas
  • super() – wywołanie metod rodzica
  • Kompozycja – obiekty zawierają inne obiekty
  • Klasy abstrakcyjne – szablony dla klas potomnych
  • @property – metody jako atrybuty

Paradygmaty OOP:

  • Abstrakcja – ukrywanie szczegółów
  • Enkapsulacja – ochrona danych
  • Dziedziczenie – współdzielenie kodu
  • Polimorfizm – jedna metoda, różne zachowania

Dobre praktyki:

  • Jedna klasa = jedna odpowiedzialność
  • Preferuj kompozycję nad dziedziczeniem
  • Używaj @property dla computed properties
  • Dokumentuj klasy docstringami
  • Nazwy klas PascalCase

Ćwiczenia – Programowanie obiektowe

Podstawy klas i obiektów

  1. Stwórz klasę Punkt z atrybutami x i y. Dodaj metodę wyświetlającą współrzędne.
  2. Stwórz klasę Prostokat z atrybutami szerokość i wysokość. Dodaj metody obliczające pole i obwód.
  3. Stwórz klasę Kolo z atrybutem promień. Dodaj metody obliczające pole i obwód.
  4. Stwórz klasę Student z atrybutami: imię, nazwisko, numer_indeksu. Dodaj metodę wyświetlającą pełne dane.
  5. Stwórz klasę Ksiazka z atrybutami: tytuł, autor, rok_wydania, liczba_stron.

Konstruktor init

  1. Stwórz klasę Samochod z konstruktorem przyjmującym markę, model i rok_produkcji.
  2. Stwórz klasę Pracownik z konstruktorem ustawiającym imię, nazwisko, pensję.
  3. Stwórz klasę Uzytkownik z konstruktorem i domyślnymi wartościami (aktywny=True, rola=”user”).
  4. Stwórz klasę Produkt z konstruktorem walidującym cenę (musi być > 0).
  5. Stwórz klasę Data z konstruktorem przyjmującym dzień, miesiąc, rok i walidującym je.

Metody

  1. Stwórz klasę Licznik z metodami zwiększ(), zmniejsz(), reset(), pokaz_wartosc().
  2. Stwórz klasę Temperatura z metodą konwertującą Celsjusz na Fahrenheit.
  3. Stwórz klasę Haslo z metodą sprawdzającą siłę hasła (długość, znaki specjalne).
  4. Stwórz klasę Email z metodą walidującą adres email (@, kropka).
  5. Stwórz klasę Kalkulator z metodami: dodaj, odejmij, pomnoz, podziel.

Metody str i repr

  1. Dodaj __str__ do klasy Osoba wyświetlający „Imię Nazwisko, wiek: X lat”.
  2. Dodaj __str__ i __repr__ do klasy Produkt.
  3. Stwórz klasę Czas (godzina, minuta) z __str__ formatującym na „HH:MM”.
  4. Stwórz klasę Data z __str__ formatującym na „DD/MM/YYYY”.
  5. Stwórz klasę Ulamek (licznik, mianownik) z czytelnym __str__.

Atrybuty prywatne

  1. Stwórz klasę KontoBankowe z prywatnym atrybutem __saldo.
  2. Dodaj do KontoBankowe metody wplata() i wyplata() z walidacją.
  3. Stwórz klasę Haslo z prywatnym atrybutem __haslo i metodą sprawdz().
  4. Stwórz klasę Uzytkownik z prywatnym __email i publiczną metodą get_email().
  5. Stwórz klasę Sejf z prywatnym kodem i metodą otwierania.

Metody magiczne – porównania

  1. Dodaj __eq__ do klasy Osoba (porównanie po wieku).
  2. Dodaj __lt__ i __gt__ do klasy Produkt (porównanie po cenie).
  3. Stwórz klasę Ocena z metodami porównania (__eq____lt____gt__).
  4. Stwórz klasę Data z możliwością porównywania dat.
  5. Stwórz klasę Prostokat z porównywaniem pól powierzchni.

Metody magiczne – operatory

  1. Dodaj __add__ do klasy Wektor2D (dodawanie wektorów).
  2. Dodaj __mul__ do klasy Wektor2D (mnożenie przez skalar).
  3. Stwórz klasę Czas z możliwością dodawania (__add__).
  4. Stwórz klasę Pieniadze z możliwością dodawania i odejmowania.
  5. Stwórz klasę Macierz2x2 z operacjami dodawania i mnożenia.

Metody magiczne – kolekcje

  1. Stwórz klasę Lista z __len__ zwracającym liczbę elementów.
  2. Dodaj __getitem__ do klasy Lista (dostęp przez indeks).
  3. Dodaj __setitem__ do klasy Lista (ustawianie przez indeks).
  4. Stwórz klasę Slownik z __getitem__ i __setitem__.
  5. Stwórz klasę Playlista z __len__ i iteracją po utworach.

@property

  1. Stwórz klasę Prostokat z @property dla pola powierzchni.
  2. Stwórz klasę Kolo z @property dla pola i obwodu.
  3. Stwórz klasę Osoba z @property dla pełnego imienia (imię + nazwisko).
  4. Stwórz klasę Temperatura z @property dla Fahrenheita (obliczane z Celsjusza).
  5. Stwórz klasę Produkt z @property i setterem dla ceny (walidacja > 0).

Dziedziczenie – podstawy

  1. Stwórz klasę bazową Zwierze i klasy potomne PiesKot.
  2. Stwórz klasę Pojazd i klasy potomne SamochodMotocykl.
  3. Stwórz klasę Figura i klasy potomne KwadratKolo.
  4. Stwórz klasę Osoba i klasy potomne StudentPracownik.
  5. Stwórz klasę Konto i klasy potomne KontoOszczednoscioweKontoBiezace.

Dziedziczenie z super()

  1. W klasie Samochod (dziedziczy Pojazd) użyj super() w konstruktorze.
  2. Nadpisz metodę w klasie potomnej i wywołaj metodę rodzica przez super().
  3. Stwórz łańcuch dziedziczenia: Zwierze → Ssak → Pies, użyj super().
  4. Rozszerz konstruktor klasy potomnej o dodatkowe atrybuty używając super().
  5. Stwórz klasę PracownikZPremią dziedziczącą Pracownik i rozszerzającą metodę oblicz_wynagrodzenie().

Kompozycja

  1. Stwórz klasę Silnik i klasę Samochod zawierającą obiekt Silnik.
  2. Stwórz klasę Adres i klasę Osoba zawierającą obiekt Adres.
  3. Stwórz klasę Telefon i klasę Kontakt zawierającą listę telefonów.
  4. Stwórz klasę Skladnik i klasę Przepis zawierającą listę składników.
  5. Stwórz klasę Gracz i klasę Druzyna zawierającą listę graczy.

Klasy abstrakcyjne

  1. Stwórz abstrakcyjną klasę Figura z metodą abstrakcyjną pole().
  2. Stwórz abstrakcyjną klasę Platnosc z metodą abstrakcyjną przetworz().
  3. Stwórz abstrakcyjną klasę Zwierze z metodą abstrakcyjną dzwiek().
  4. Stwórz abstrakcyjną klasę Pojazd z metodami abstrakcyjnymi jedz() i zatrzymaj().
  5. Stwórz abstrakcyjną klasę Baza z metodami abstrakcyjnymi polacz() i rozlacz().

isinstance() i type()

  1. Sprawdź isinstance() dla obiektów różnych klas z dziedziczeniem.
  2. Napisz funkcję przyjmującą tylko obiekty typu Prostokat (walidacja isinstance).
  3. Napisz funkcję działającą różnie dla różnych typów (isinstance).
  4. Sprawdź czy obiekt jest instancją klasy bazowej lub potomnej.
  5. Użyj isinstance() do walidacji argumentów metody.

Zadania praktyczne – Biblioteka

  1. Stwórz klasę Ksiazka z atrybutami i metodą __str__.
  2. Stwórz klasę Biblioteka przechowującą listę książek.
  3. Dodaj metody dodaj_ksiazke(), usun_ksiazke() do Biblioteki.
  4. Dodaj metodę wyszukaj_po_tytule() do Biblioteki.
  5. Dodaj metodę wyszukaj_po_autorze() do Biblioteki.
  6. Dodaj atrybut wypozyczona do Ksiazki i metody wypozycz()/zwroc().
  7. Dodaj do Biblioteki metodę dostepne_ksiazki().
  8. Dodaj licznik wypożyczeń do Ksiazki.
  9. Dodaj metodę najpopularniejsza_ksiazka() do Biblioteki.
  10. Zapisz bibliotekę do pliku JSON.

Zadania praktyczne – Sklep

  1. Stwórz klasę Produkt (nazwa, cena, ilosc_w_magazynie).
  2. Stwórz klasę Koszyk przechowującą produkty do zakupu.
  3. Dodaj metody dodaj_do_koszyka(), usun_z_koszyka().
  4. Dodaj metodę oblicz_wartosc_koszyka().
  5. Dodaj metodę zrealizuj_zamowienie() (zmniejsza stan magazynowy).
  6. Dodaj kategorie produktów (kompozycja lub dziedziczenie).
  7. Dodaj kody rabatowe (klasa Rabat).
  8. Dodaj historię zamówień (klasa Zamowienie).
  9. Stwórz system logowania (klasa Uzytkownik).
  10. Dodaj oceny produktów.

Zadania praktyczne – Gra RPG

  1. Stwórz klasę Postac (imię, hp, atak, obrona).
  2. Dodaj metody atakuj() i otrzymaj_obrazenia().
  3. Stwórz klasy WojownikMagLucznik dziedziczące po Postac.
  4. Dodaj system poziomów i doświadczenia.
  5. Dodaj ekwipunek (kompozycja – klasa Przedmiot).
  6. Dodaj broń zwiększającą atak (klasa Bron).
  7. Dodaj zbroje zwiększające obronę (klasa Zbroja).
  8. Dodaj umiejętności specjalne dla każdej klasy postaci.
  9. Stwórz system walki turowej.
  10. Dodaj zapisywanie stanu gry do pliku.

Zadania zaawansowane

  1. Stwórz system zarządzania studentami i kursami (wiele klas współpracujących).
  2. Stwórz symulator banku z różnymi typami kont.
  3. Stwórz system rezerwacji hotelu (pokoje, rezerwacje, goście).
  4. Stwórz prosty system CMS (artykuły, autorzy, kategorie).
  5. Stwórz system zarządzania projektami (projekty, zadania, użytkownicy).
  6. Stwórz symulator farmy (rośliny, zwierzęta, zasoby).
  7. Stwórz system biblioteki muzycznej (utwory, albumy, artyści, playlisty).
  8. Stwórz prosty system e-learningowy (kursy, lekcje, quizy).
  9. Stwórz system zarządzania restauracją (menu, zamówienia, stoły).
  10. Stwórz grę tekstową z systemem lokacji, przedmiotów i postaci.