16. Python – obiektowo


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

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 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 @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 Pies, Kot.
  2. Stwórz klasę Pojazd i klasy potomne Samochod, Motocykl.
  3. Stwórz klasę Figura i klasy potomne Kwadrat, Kolo.
  4. Stwórz klasę Osoba i klasy potomne Student, Pracownik.
  5. Stwórz klasę Konto i klasy potomne KontoOszczednosciowe, KontoBiezace.

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 Wojownik, Mag, Lucznik 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.

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(). Zapis kot = Kot nie 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__ jako self.nazwa należy do konkretnego obiektu.

self i konstruktor

  • Pierwszy parametr każdej metody to self. Python przekazuje go sam: wywołanie azor.szczekaj() działa jak szczekaj(azor).
  • Do atrybutu obiektu zawsze odwołujemy się przez self.nazwa. Samo nazwa to 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 __nazwa jedynie utrudnia dostęp spoza klasy.

Dziedziczenie i kompozycja

  • class Pies(Zwierze) – Pies dziedziczy atrybuty i metody Zwierze i może je nadpisać.
  • super().__init__(...) wywołuje konstruktor klasy bazowej. Bez niego atrybuty ustawiane w konstruktorze rodzica nie powstaną.
  • isinstance(pies, Zwierze) zwraca True, 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 ABC z metodą @abstractmethod nie pozwala utworzyć obiektu (TypeError). Służy jako szablon dla klas potomnych.
  • @property pozwala czytać wynik metody jak atrybut, bez nawiasów: prostokat.pole. Z @nazwa.setter moż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 self w definicji metody lub przed nazwą atrybutu,
  • imie = imie zamiast self.imie = imie w konstruktorze,
  • print zamiast return w __str__,
  • brak super().__init__() w klasie potomnej,
  • nawiasy przy @property (p.pole() zamiast p.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:

  1. Klasa to szablon, obiekt to jego instancja. Obiekt tworzysz z nawiasami: Kot().
  2. Każda metoda ma pierwszy parametr self, a do atrybutów obiektu odwołujesz się przez self.nazwa.
  3. Konstruktor to def __init__(self, ...). Uruchamia się sam przy tworzeniu obiektu i ustawia atrybuty.
  4. __str__ zwraca tekst i jest używany przez print(obiekt).
  5. _nazwa to umowa „nie ruszaj z zewnątrz”, __nazwa to atrybut prywatny. Obie formy są tylko konwencją.
  6. Dziedziczenie: class Pies(Zwierze). W konstruktorze klasy potomnej wywołaj super().__init__(...).
  7. JEST → dziedziczenie, MA → kompozycja.
  8. Klasy abstrakcyjnej (ABC) nie da się zainstancjonować.
  9. @property używasz bez nawiasów: p.pole.
  10. 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ść

ZapisZnaczenieDostęp z zewnątrz
nazwapublicznytak
_nazwachroniony (konwencja)technicznie tak, ale nie wolno
__nazwaprywatny (zmiana nazwy)AttributeError

Metody magiczne z lekcji

MetodaUruchamiana 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) zwraca True, jeśli kod jest poprawny, a w przeciwnym razie False.

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 = imie tworzy 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: 21 i True.
  • Bez super().__init__(marka) atrybut marka nie zostałby utworzony. Wywołanie r.info() zgłosiłoby AttributeError, bo Pojazd.info() korzysta z self.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