Szybki quiz – przypomnienie poprzedniej lekcji
Temat poprzedniej lekcji: obsługa błędów i wyjątków. Spróbuj odpowiedzieć bez zaglądania do materiałów.
1. Co zostanie wyświetlone?
try:
print("A")
x = int("abc")
print("B")
except ValueError:
print("C")
else:
print("D")
finally:
print("E")
Odpowiedź
Odpowiedź: A, C, E (każda litera w osobnej linii). B się nie wykona, bo błąd przerywa try. else pomijamy, bo błąd wystąpił. finally wykonuje się zawsze.
2. Dlaczego ten program może się zawiesić i jak to naprawić?
try:
liczba = int(input("Podaj liczbę: "))
print(10 / liczba)
except ZeroDivisionError:
print("Nie dziel przez zero")
Odpowiedź
Odpowiedź: Gdy użytkownik wpisze tekst (np. abc), int() zgłosi ValueError, którego nie obsługuje żaden except. Trzeba dodać drugi blok: except ValueError:.
3. Dopasuj kod do typu błędu: KeyError, IndexError, TypeError, FileNotFoundError
- a)
lista = [1, 2, 3]→lista[3] - b)
slownik = {"a": 1}→slownik["b"] - c)
"5" + 5 - d)
open("brak.txt")
Odpowiedź
Odpowiedź: a) IndexError, b) KeyError, c) TypeError, d) FileNotFoundError.
4. Uzupełnij kod, aby funkcja zgłaszała błąd dla ujemnego wieku:
def sprawdz_wiek(wiek):
if wiek < 0:
______ ValueError("Wiek nie może być ujemny")
return wiek
Odpowiedź
Odpowiedź: raise
5. Dlaczego except: bez podania typu błędu jest złą praktyką?
Odpowiedź
Odpowiedź: Łapie wszystkie błędy, więc nie wiadomo, co się stało. Utrudnia to debugowanie, może ukryć poważny błąd i nie pozwala inaczej reagować na różne błędy.
6. Co zostanie wyświetlone?
class BladX(Exception):
pass
try:
raise BladX("Ups")
except Exception as e:
print(e)
Odpowiedź
Odpowiedź: Ups. BladX dziedziczy po Exception, więc except Exception go złapie, a as e pozwala wypisać jego komunikat. (To pierwszy przykład dziedziczenia, który zaraz poznasz dokładniej.)
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
| Metoda | Wywołanie | Opis |
|---|---|---|
__init__ | obj = Klasa() | Konstruktor |
__str__ | print(obj), str(obj) | Reprezentacja tekstowa |
__repr__ | repr(obj) | Reprezentacja techniczna |
__len__ | len(obj) | Długość |
__eq__ | obj1 == obj2 | Równość |
__lt__ | obj1 < obj2 | Mniejsze niż |
__gt__ | obj1 > obj2 | Większe niż |
__add__ | obj1 + obj2 | Dodawanie |
__sub__ | obj1 - obj2 | Odejmowanie |
__mul__ | obj1 * obj2 | Mnożenie |
__getitem__ | obj[key] | Dostęp przez indeks |
__setitem__ | obj[key] = val | Ustawianie przez indeks |
__call__ | obj() | Wywołanie jak funkcja |
__del__ | Usuwanie obiektu | Destruktor |
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 zmieni salda
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
@propertydla computed properties - Dokumentuj klasy docstringami
- Nazwy klas PascalCase
Ćwiczenia – Programowanie obiektowe
Podstawy klas i obiektów
- Stwórz klasę
Punktz atrybutami x i y. Dodaj metodę wyświetlającą współrzędne. - Stwórz klasę
Prostokatz atrybutami szerokość i wysokość. Dodaj metody obliczające pole i obwód. - Stwórz klasę
Koloz atrybutem promień. Dodaj metody obliczające pole i obwód. - Stwórz klasę
Studentz atrybutami: imię, nazwisko, numer_indeksu. Dodaj metodę wyświetlającą pełne dane. - Stwórz klasę
Ksiazkaz atrybutami: tytuł, autor, rok_wydania, liczba_stron.
Konstruktor init
- Stwórz klasę
Samochodz konstruktorem przyjmującym markę, model i rok_produkcji. - Stwórz klasę
Pracownikz konstruktorem ustawiającym imię, nazwisko, pensję. - Stwórz klasę
Uzytkownikz konstruktorem i domyślnymi wartościami (aktywny=True, rola=”user”). - Stwórz klasę
Produktz konstruktorem walidującym cenę (musi być > 0). - Stwórz klasę
Dataz konstruktorem przyjmującym dzień, miesiąc, rok i walidującym je.
Metody
- Stwórz klasę
Licznikz metodami zwiększ(), zmniejsz(), reset(), pokaz_wartosc(). - Stwórz klasę
Temperaturaz metodą konwertującą Celsjusz na Fahrenheit. - Stwórz klasę
Hasloz metodą sprawdzającą siłę hasła (długość, znaki specjalne). - Stwórz klasę
Emailz metodą walidującą adres email (@, kropka). - Stwórz klasę
Kalkulatorz metodami: dodaj, odejmij, pomnoz, podziel.
Metody str i repr
- Dodaj
__str__do klasyOsobawyświetlający „Imię Nazwisko, wiek: X lat”. - Dodaj
__str__i__repr__do klasyProdukt. - Stwórz klasę
Czas(godzina, minuta) z__str__formatującym na „HH:MM”. - Stwórz klasę
Dataz__str__formatującym na „DD/MM/YYYY”. - Stwórz klasę
Ulamek(licznik, mianownik) z czytelnym__str__.
Atrybuty prywatne
- Stwórz klasę
KontoBankowez prywatnym atrybutem __saldo. - Dodaj do
KontoBankowemetody wplata() i wyplata() z walidacją. - Stwórz klasę
Hasloz prywatnym atrybutem __haslo i metodą sprawdz(). - Stwórz klasę
Uzytkownikz prywatnym __email i publiczną metodą get_email(). - Stwórz klasę
Sejfz prywatnym kodem i metodą otwierania.
Metody magiczne – porównania
- Dodaj
__eq__do klasyOsoba(porównanie po wieku). - Dodaj
__lt__i__gt__do klasyProdukt(porównanie po cenie). - Stwórz klasę
Ocenaz metodami porównania (__eq__,__lt__,__gt__). - Stwórz klasę
Dataz możliwością porównywania dat. - Stwórz klasę
Prostokatz porównywaniem pól powierzchni.
Metody magiczne – operatory
- Dodaj
__add__do klasyWektor2D(dodawanie wektorów). - Dodaj
__mul__do klasyWektor2D(mnożenie przez skalar). - Stwórz klasę
Czasz możliwością dodawania (__add__). - Stwórz klasę
Pieniadzez możliwością dodawania i odejmowania. - Stwórz klasę
Macierz2x2z operacjami dodawania i mnożenia.
Metody magiczne – kolekcje
- Stwórz klasę
Listaz__len__zwracającym liczbę elementów. - Dodaj
__getitem__do klasyLista(dostęp przez indeks). - Dodaj
__setitem__do klasyLista(ustawianie przez indeks). - Stwórz klasę
Slownikz__getitem__i__setitem__. - Stwórz klasę
Playlistaz__len__i iteracją po utworach.
@property
- Stwórz klasę
Prostokatz @property dla pola powierzchni. - Stwórz klasę
Koloz @property dla pola i obwodu. - Stwórz klasę
Osobaz @property dla pełnego imienia (imię + nazwisko). - Stwórz klasę
Temperaturaz @property dla Fahrenheita (obliczane z Celsjusza). - Stwórz klasę
Produktz @property i setterem dla ceny (walidacja > 0).
Dziedziczenie – podstawy
- Stwórz klasę bazową
Zwierzei klasy potomnePies,Kot. - Stwórz klasę
Pojazdi klasy potomneSamochod,Motocykl. - Stwórz klasę
Figurai klasy potomneKwadrat,Kolo. - Stwórz klasę
Osobai klasy potomneStudent,Pracownik. - Stwórz klasę
Kontoi klasy potomneKontoOszczednosciowe,KontoBiezace.
Dziedziczenie z super()
- W klasie
Samochod(dziedziczyPojazd) użyj super() w konstruktorze. - Nadpisz metodę w klasie potomnej i wywołaj metodę rodzica przez super().
- Stwórz łańcuch dziedziczenia:
Zwierze→Ssak→Pies, użyj super(). - Rozszerz konstruktor klasy potomnej o dodatkowe atrybuty używając super().
- Stwórz klasę
PracownikZPremiądziedziczącąPracowniki rozszerzającą metodę oblicz_wynagrodzenie().
Kompozycja
- Stwórz klasę
Silniki klasęSamochodzawierającą obiekt Silnik. - Stwórz klasę
Adresi klasęOsobazawierającą obiekt Adres. - Stwórz klasę
Telefoni klasęKontaktzawierającą listę telefonów. - Stwórz klasę
Skladniki klasęPrzepiszawierającą listę składników. - Stwórz klasę
Graczi klasęDruzynazawierającą listę graczy.
Klasy abstrakcyjne
- Stwórz abstrakcyjną klasę
Figuraz metodą abstrakcyjną pole(). - Stwórz abstrakcyjną klasę
Platnoscz metodą abstrakcyjną przetworz(). - Stwórz abstrakcyjną klasę
Zwierzez metodą abstrakcyjną dzwiek(). - Stwórz abstrakcyjną klasę
Pojazdz metodami abstrakcyjnymi jedz() i zatrzymaj(). - Stwórz abstrakcyjną klasę
Bazaz metodami abstrakcyjnymi polacz() i rozlacz().
isinstance() i type()
- Sprawdź isinstance() dla obiektów różnych klas z dziedziczeniem.
- Napisz funkcję przyjmującą tylko obiekty typu Prostokat (walidacja isinstance).
- Napisz funkcję działającą różnie dla różnych typów (isinstance).
- Sprawdź czy obiekt jest instancją klasy bazowej lub potomnej.
- Użyj isinstance() do walidacji argumentów metody.
Zadania praktyczne – Biblioteka
- Stwórz klasę
Ksiazkaz atrybutami i metodą__str__. - Stwórz klasę
Bibliotekaprzechowującą listę książek. - Dodaj metody dodaj_ksiazke(), usun_ksiazke() do Biblioteki.
- Dodaj metodę wyszukaj_po_tytule() do Biblioteki.
- Dodaj metodę wyszukaj_po_autorze() do Biblioteki.
- Dodaj atrybut wypozyczona do Ksiazki i metody wypozycz()/zwroc().
- Dodaj do Biblioteki metodę dostepne_ksiazki().
- Dodaj licznik wypożyczeń do Ksiazki.
- Dodaj metodę najpopularniejsza_ksiazka() do Biblioteki.
- Zapisz bibliotekę do pliku JSON.
Zadania praktyczne – Sklep
- Stwórz klasę
Produkt(nazwa, cena, ilosc_w_magazynie). - Stwórz klasę
Koszykprzechowującą produkty do zakupu. - Dodaj metody dodaj_do_koszyka(), usun_z_koszyka().
- Dodaj metodę oblicz_wartosc_koszyka().
- Dodaj metodę zrealizuj_zamowienie() (zmniejsza stan magazynowy).
- Dodaj kategorie produktów (kompozycja lub dziedziczenie).
- Dodaj kody rabatowe (klasa Rabat).
- Dodaj historię zamówień (klasa Zamowienie).
- Stwórz system logowania (klasa Uzytkownik).
- Dodaj oceny produktów.
Zadania praktyczne – Gra RPG
- Stwórz klasę
Postac(imię, hp, atak, obrona). - Dodaj metody atakuj() i otrzymaj_obrazenia().
- Stwórz klasy
Wojownik,Mag,Lucznikdziedziczące po Postac. - Dodaj system poziomów i doświadczenia.
- Dodaj ekwipunek (kompozycja – klasa Przedmiot).
- Dodaj broń zwiększającą atak (klasa Bron).
- Dodaj zbroje zwiększające obronę (klasa Zbroja).
- Dodaj umiejętności specjalne dla każdej klasy postaci.
- Stwórz system walki turowej.
- Dodaj zapisywanie stanu gry do pliku.
Zadania zaawansowane
- Stwórz system zarządzania studentami i kursami (wiele klas współpracujących).
- Stwórz symulator banku z różnymi typami kont.
- Stwórz system rezerwacji hotelu (pokoje, rezerwacje, goście).
- Stwórz prosty system CMS (artykuły, autorzy, kategorie).
- Stwórz system zarządzania projektami (projekty, zadania, użytkownicy).
- Stwórz symulator farmy (rośliny, zwierzęta, zasoby).
- Stwórz system biblioteki muzycznej (utwory, albumy, artyści, playlisty).
- Stwórz prosty system e-learningowy (kursy, lekcje, quizy).
- Stwórz system zarządzania restauracją (menu, zamówienia, stoły).
- Stwórz grę tekstową z systemem lokacji, przedmiotów i postaci.
PODSUMOWANIE
Klasa i obiekt
- Klasa to szablon, obiekt to jego konkretna instancja. Z jednej klasy można utworzyć wiele niezależnych obiektów.
- Obiekt tworzysz z nawiasami:
kot = Kot(). Zapiskot = Kotnie tworzy obiektu, tylko podaje drugą nazwę klasy. - Atrybuty to dane obiektu, metody to funkcje zdefiniowane w klasie.
- Atrybut zapisany w ciele klasy jest wspólny dla wszystkich instancji. Atrybut zapisany w
__init__jakoself.nazwanależy do konkretnego obiektu.
self i konstruktor
- Pierwszy parametr każdej metody to
self. Python przekazuje go sam: wywołanieazor.szczekaj()działa jakszczekaj(azor). - Do atrybutu obiektu zawsze odwołujemy się przez
self.nazwa. Samonazwato zwykła zmienna lokalna, która znika po zakończeniu metody. __init__uruchamia się automatycznie przy tworzeniu obiektu i ustawia jego początkowy stan. Parametry mogą mieć wartości domyślne.
Metody magiczne
- Wywołuje je Python, a nie my:
print(obj)→__str__,len(obj)→__len__,a < b→__lt__,a + b→__add__,obj[i]→__getitem__. __str__jest dla użytkownika,__repr__dla programisty. Obie zwracają tekst (return), a nie wypisują go.
Dostęp do atrybutów
nazwa– publiczny._nazwa– chroniony (tylko umowa między programistami).__nazwa– prywatny (Python zmienia jego nazwę na_Klasa__nazwa).- W Pythonie nie ma prawdziwych modyfikatorów dostępu. To konwencje, a
__nazwajedynie utrudnia dostęp spoza klasy.
Dziedziczenie i kompozycja
class Pies(Zwierze)–Piesdziedziczy atrybuty i metodyZwierzei może je nadpisać.super().__init__(...)wywołuje konstruktor klasy bazowej. Bez niego atrybuty ustawiane w konstruktorze rodzica nie powstaną.isinstance(pies, Zwierze)zwracaTrue, bo obiekt klasy potomnej jest też obiektem klasy bazowej.- Test wyboru: relacja JEST (Pies jest Zwierzęciem) → dziedziczenie. Relacja MA (Samochód ma Silnik) → kompozycja, czyli obiekt jako atrybut innego obiektu.
Klasy abstrakcyjne, @property, paradygmaty
- Klasa dziedzicząca po
ABCz metodą@abstractmethodnie pozwala utworzyć obiektu (TypeError). Służy jako szablon dla klas potomnych. @propertypozwala czytać wynik metody jak atrybut, bez nawiasów:prostokat.pole. Z@nazwa.settermożna dodać walidację przy przypisywaniu.- Cztery paradygmaty OOP z lekcji: abstrakcja, enkapsulacja, dziedziczenie, polimorfizm (ta sama metoda, różne zachowanie w zależności od obiektu).
Typowe źródła błędów
- brak
selfw definicji metody lub przed nazwą atrybutu, imie = imiezamiastself.imie = imiew konstruktorze,printzamiastreturnw__str__,- brak
super().__init__()w klasie potomnej, - nawiasy przy
@property(p.pole()zamiastp.pole), - próba utworzenia obiektu z klasy abstrakcyjnej.
Minimum, które powinno wejść w pamięć
Jeżeli masz zapamiętać tylko kilka rzeczy z tej lekcji, zapamiętaj te:
- Klasa to szablon, obiekt to jego instancja. Obiekt tworzysz z nawiasami:
Kot(). - Każda metoda ma pierwszy parametr
self, a do atrybutów obiektu odwołujesz się przezself.nazwa. - Konstruktor to
def __init__(self, ...). Uruchamia się sam przy tworzeniu obiektu i ustawia atrybuty. __str__zwraca tekst i jest używany przezprint(obiekt)._nazwato umowa „nie ruszaj z zewnątrz”,__nazwato atrybut prywatny. Obie formy są tylko konwencją.- Dziedziczenie:
class Pies(Zwierze). W konstruktorze klasy potomnej wywołajsuper().__init__(...). - JEST → dziedziczenie, MA → kompozycja.
- Klasy abstrakcyjnej (
ABC) nie da się zainstancjonować. @propertyużywasz bez nawiasów:p.pole.- Hermetyzacja = ukrywanie danych i dostęp przez metody (np.
wplata(),saldo). Polimorfizm = ta sama metoda, różne zachowanie.
Ściągawka – najważniejsze polecenia i składnia
Klasa, konstruktor, metoda, obiekt
class Gracz:
def __init__(self, nick, hp=100): # konstruktor, hp ma wartość domyślną
self.nick = nick # atrybut obiektu
self.hp = hp
def ranny(self, ile): # metoda
self.hp -= ile
def __str__(self): # tekst dla print()
return f"{self.nick} ({self.hp} HP)"
g = Gracz("Mag", hp=80) # tworzenie obiektu
g.ranny(10) # wywołanie metody
print(g.hp, g) # atrybut, print używa __str__
Widoczność
| Zapis | Znaczenie | Dostęp z zewnątrz |
|---|---|---|
nazwa | publiczny | tak |
_nazwa | chroniony (konwencja) | technicznie tak, ale nie wolno |
__nazwa | prywatny (zmiana nazwy) | AttributeError |
Metody magiczne z lekcji
| Metoda | Uruchamiana przez |
|---|---|
__init__ | Klasa(...) |
__str__ / __repr__ | print(obj), str(obj) / repr(obj) |
__len__ | len(obj) |
__eq__, __lt__, __gt__ | ==, <, > |
__add__, __sub__, __mul__ | +, -, * |
__getitem__, __setitem__ | obj[i], obj[i] = x |
__call__ | obj(...) |
Dziedziczenie i super()
class Pojazd:
def __init__(self, marka):
self.marka = marka
class Samochod(Pojazd): # Samochod JEST Pojazdem
def __init__(self, marka, model):
super().__init__(marka) # konstruktor klasy bazowej
self.model = model
isinstance(auto, Pojazd) # True
Kompozycja (MA)
class Samochod:
def __init__(self, moc):
self.silnik = Silnik(moc) # obiekt jako atrybut
Klasa abstrakcyjna
from abc import ABC, abstractmethod
class Figura(ABC):
@abstractmethod
def pole(self):
pass # Figura() -> TypeError
@property z walidacją
@property
def wiek(self): # odczyt: osoba.wiek
return self._wiek
@wiek.setter
def wiek(self, wartosc): # zapis: osoba.wiek = 30
if wartosc < 0:
raise ValueError("Wiek nie może być ujemny!")
self._wiek = wartosc
Quiz – sprawdź się
1. Który zapis tworzy obiekt klasy Kot?
- a)
mruczek = Kot - b)
mruczek = Kot() - c)
mruczek = class Kot() - d)
Kot = mruczek()
2. Co zostanie wyświetlone?
class Licznik:
def __init__(self):
self.wartosc = 0
def zwieksz(self):
self.wartosc += 1
a = Licznik()
b = Licznik()
a.zwieksz()
a.zwieksz()
b.zwieksz()
print(a.wartosc, b.wartosc)
3. Znajdź dwa błędy w kodzie i popraw je.
class Pies:
def __init__(self, imie):
imie = imie
def szczekaj():
print(f"{self.imie}: Hau!")
4. Co zostanie wyświetlone?
class Gracz:
def __init__(self, nick, hp=100):
self.nick = nick
self.hp = hp
def __str__(self):
return f"{self.nick} ({self.hp} HP)"
g = Gracz("Mag", 80)
print(g)
5. Uzupełnij kod tak, aby porównanie a < b działało i dawało True.
class Produkt:
def __init__(self, nazwa, cena):
self.nazwa = nazwa
self.cena = cena
def ______(self, other):
return self.cena < other.cena
a = Produkt("Mysz", 50)
b = Produkt("Klawiatura", 120)
print(a < b)
6. Co zostanie wyświetlone i co stanie się w ostatniej linii?
class Konto:
def __init__(self, saldo):
self.__saldo = saldo
def pokaz(self):
print(self.__saldo)
k = Konto(500)
k.pokaz()
print(k.__saldo)
7. Co zostanie wyświetlone? Co by się stało, gdyby w konstruktorze klasy Rower pominięto linię super().__init__(marka)?
class Pojazd:
def __init__(self, marka):
self.marka = marka
def info(self):
return f"Pojazd {self.marka}"
class Rower(Pojazd):
def __init__(self, marka, biegi):
super().__init__(marka)
self.biegi = biegi
def info(self):
return super().info() + f", biegi: {self.biegi}"
r = Rower("Kross", 21)
print(r.info())
print(isinstance(r, Pojazd))
8. Dla każdej pary wybierz: dziedziczenie czy kompozycja?
- a)
Pies–Zwierze - b)
Biblioteka–Ksiazka - c)
Student–Osoba - d)
Dom–Okno
9. Która linia spowoduje błąd? Co wyświetli program po jej usunięciu?
from abc import ABC, abstractmethod
class Figura(ABC):
@abstractmethod
def pole(self):
pass
class Kwadrat(Figura):
def __init__(self, a):
self.a = a
def pole(self):
return self.a ** 2
f = Figura()
k = Kwadrat(4)
print(k.pole())
10. Dlaczego ten kod się nie wykona i jak go poprawić?
class Prostokat:
def __init__(self, a, b):
self.a = a
self.b = b
@property
def pole(self):
return self.a * self.b
p = Prostokat(3, 4)
print(p.pole())
11. Co zostanie wyświetlone? Jak nazywa się mechanizm, który tu działa?
class Pies:
def dzwiek(self):
return "Hau"
class Kot:
def dzwiek(self):
return "Miau"
for zwierze in [Pies(), Kot(), Pies()]:
print(zwierze.dzwiek())
12. Zadanie praktyczne. Napisz klasę Sejf:
- konstruktor przyjmuje kod i zapisuje go w prywatnym atrybucie,
- metoda
otworz(kod)zwracaTrue, jeśli kod jest poprawny, a w przeciwnym razieFalse.
Utwórz sejf z kodem "1234" i sprawdź dwa kody: poprawny i błędny.
Odpowiedzi
1. b) mruczek = Kot(). Nawiasy wywołują klasę i tworzą obiekt. Zapis a) tylko nadaje klasie drugą nazwę.
2. 2 1. Każdy obiekt ma własny atrybut wartosc, więc a i b liczą niezależnie.
3.
- W konstruktorze:
imie = imietworzy tylko zmienną lokalną. Powinno byćself.imie = imie. - W metodzie: brakuje parametru
self. Powinno byćdef szczekaj(self):.
4. Mag (80 HP). print(g) wywołuje __str__, a wartość 80 nadpisuje domyślne 100.
5. __lt__
6. Pierwsze k.pokaz() wyświetli 500. Linia print(k.__saldo) zgłosi AttributeError, bo atrybut prywatny ma na zewnątrz zmienioną nazwę (name mangling).
7.
- Wynik:
Pojazd Kross, biegi: 21iTrue. - Bez
super().__init__(marka)atrybutmarkanie zostałby utworzony. Wywołanier.info()zgłosiłobyAttributeError, boPojazd.info()korzysta zself.marka.
8. a) dziedziczenie (pies jest zwierzęciem), b) kompozycja (biblioteka ma książki), c) dziedziczenie (student jest osobą), d) kompozycja (dom ma okna).
9. Błąd zgłosi linia f = Figura() (TypeError), bo klasy abstrakcyjnej nie można instancjonować. Po jej usunięciu program wyświetli 16.
10. p.pole jest właściwością (@property), więc zwraca liczbę 12. Dopisanie () próbuje wywołać liczbę, co kończy się TypeError. Poprawnie: print(p.pole).
11. Wynik: Hau, Miau, Hau (każde w osobnej linii). To polimorfizm: ta sama metoda dzwiek() daje różny efekt w zależności od obiektu.
12. Przykładowe rozwiązanie:
class Sejf:
def __init__(self, kod):
self.__kod = kod
def otworz(self, kod):
return kod == self.__kod
sejf = Sejf("1234")
print(sejf.otworz("1234")) # True
print(sejf.otworz("0000")) # False