Streszczenie Podsumowanie i mini-projekt - system biblioteczny

Ten ostatni moduł kursu łączy wszystkie poznane koncepcje programowania obiektowego w praktyczny projekt systemu bibliotecznego. Wykorzystuje @dataclass dla klasy Książka , @property dla hermetyzacji atrybutów w Czytelnik , @classmethod jako alternatywny konstruktor ( from_csv ), własną hierarchię wyjątków ( BladBiblioteki(Exception) ), kompozycję ( Biblioteka zawiera listy Książka i Czytelnik ), enkapsulację z atrybutami chronionymi oraz kompleksową obsługę błędów biznesowych. Projekt obejmuje dodawanie książek, wypożyczanie i zwracanie, wyszukiwanie, wykrywanie duplikatów, walidację danych wejściowych oraz import z pliku CSV. Każda klasa jest testowana w izolacji, a scenariusze biznesowe demonstrują praktyczne użycie polimorfizmu, kompozycji i obsługi wyjątków.

Projekt został zaprojektowany zgodnie z zasadami SOLID. Każda klasa ma jedną odpowiedzialność (SRP): Książka przechowuje dane książki, Czytelnik reprezentuje użytkownika, Biblioteka zarządza logiką biznesową, a BladBiblioteki obsługuje błędy. Dzięki temu system jest łatwy do rozwijania i testowania. Zastosowano również regułę otwarte-zamknięte (OCP) - dodanie nowej funkcjonalności, jak wyszukiwanie, nie wymagało modyfikacji istniejących klas.

  • @dataclass Książka - model danych z automatycznym __init__ , __repr__ , __eq__ ; oszczędza dziesiątki linii kodu boilerplate
  • @property Czytelnik - hermetyzacja z walidacją, właściwość liczba_ksiazek liczona dynamicznie z długości listy
  • @classmethod Biblioteka.from_csv - alternatywny konstruktor, wzorzec Factory Method; umożliwia tworzenie instancji z różnych źródeł danych
  • Kompozycja i agregacja - Biblioteka zawiera Książki i Czytelników, relacje międzyklasowe; słabe więzi między składnikami (obiekty mogą istnieć poza biblioteką)
  • Własne wyjątki biznesowe - BladBiblioteki z kodami błędów, precyzyjną sygnalizacją problemów; kod błędu umożliwia programistyczne reagowanie bez parsowania komunikatu

Podsumowanie to dobry moment na refleksję nad przyswojonym materiałem i identyfikację obszarów wymagających dodatkowej pracy. Wymienione punkty stanowią esencję przerobionej części kursu - od definicji klasy, przez tworzenie obiektów, aż po kompozycję i wzorzec fabryki. Każdy z tych punktów będzie rozwijany w kolejnych modułach, dlatego warto upewnić się, że są dobrze zrozumiane. Zachęcamy do tworzenia własnych notatek i map myśli, które pomagają w usystematyzowaniu wiedzy. Regularne powtórki są kluczowe dla trwałego zapamiętania materiału.

Mapy myśli są skutecznym narzędziem wizualizacji złożonych koncepcji i relacji między nimi. Przedstawiona mapa obrazuje najważniejsze pojęcia omówione w tej części kursu oraz ich wzajemne powiązania. Tworzenie własnych map myśli podczas nauki programowania aktywuje inne obszary mózgu niż czytanie liniowego tekstu, co przekłada się na lepsze zapamiętywanie. Studenci, którzy regularnie tworzą mapy myśli, osiągają lepsze wyniki w testach koncepcyjnych i szybciej łączą nowe informacje z już posiadaną wiedzą. Zachęcamy do wykorzystania tej techniki.

1Wprowadzenie

System biblioteczny w Pythonie OOP

W tej ostatniej części kursu łączymy wszystkie poznane koncepcje programowania obiektowego w jeden spójny projekt. Zbudujemy od podstaw prosty system biblioteczny wykorzystujący klasy, dziedziczenie, hermetyzację, własne wyjątki, @dataclass , @property oraz @classmethod .

Jest to kulminacja dwunastu części kursu - praktyczne zastosowanie teorii w działającej aplikacji. Każda koncepcja omówiona w poprzednich modułach znajduje tu swoje odzwierciedlenie w konkretnym fragmencie kodu. Dzięki temu zobaczysz, jak poszczególne elementy OOP współgrają ze sobą w rzeczywistym projekcie.

Projekt, choć prosty, pokazuje typowe wyzwania w projektowaniu systemów obiektowych: jak podzielić odpowiedzialność między klasy, jak obsługiwać błędy biznesowe, jak zapewnić spójność danych i jak projektować elastyczne interfejsy. Te umiejętności są kluczowe przy budowie większych aplikacji w Pythonie, niezależnie od tego, czy będziesz tworzyć systemy webowe, narzędzia analityczne, czy automatyzacje.

Moduł ten stanowi pierwszą część kompleksowego kursu programowania obiektowego w języku Python. Jego celem jest wprowadzenie studentów w paradygmat obiektowy oraz przedstawienie fundamentalnych koncepcji, takich jak klasy, obiekty, atrybuty i metody. Materiał został zaprojektowany tak, aby stopniowo budować zrozumienie od prostych analogii do konkretnych implementacji w kodzie. Każdy slajd zawiera przykłady, które można samodzielnie przetestować w środowisku REPL. Zaleca się aktywne uczestnictwo poprzez modyfikowanie przykładów i wykonywanie ćwiczeń. Systematyczna praca z materiałem gwarantuje solidne opanowanie podstaw OOP. W kolejnych modułach wiedza ta będzie rozwijana o bardziej zaawansowane zagadnienia.

Programowanie obiektowe to nie tylko zestaw reguł składniowych, ale przede wszystkim sposób myślenia o problemach programistycznych. Kluczowe jest zrozumienie, że klasy służą do modelowania rzeczywistych bytów i relacji między nimi. Dzięki OOP kod staje się bardziej modularny, łatwiejszy w utrzymaniu i bardziej odporny na błędy. Współczesne aplikacje webowe, systemy bazodanowe i frameworki w dużym stopniu opierają się na paradygmacie obiektowym. Opanowanie OOP otwiera drzwi do zrozumienia zaawansowanych wzorców projektowych. Zachęcamy do cierpliwej i systematycznej nauki - każde nowe pojęcie będzie szczegółowo wyjaśnione i zilustrowane przykładami.

2Cele dydaktyczne
Cel główny: Samodzielne zaprojektowanie i implementacja prostego systemu obiektowego w Pythonie z wykorzystaniem poznanych mechanizmów.
  • Projektowanie hierarchii klas i relacji między nimi
  • Implementacja @dataclass dla prostych modeli danych
  • Stosowanie @property do hermetyzacji atrybutów
  • Tworzenie własnej klasy wyjątku
  • Użycie @classmethod jako alternatywnego konstruktora
  • Kompozycja obiektów - Biblioteka zawiera Książki i Czytelników
  • Testówanie scenariuszy biznesowych (wypożyczanie, zwracanie, błędy)

Cele szczegółowe obejmują również zrozumienie różnicy między kompozycją a agregacją, umiejętność projektowania własnych hierarchii wyjątków z kodami błędów, stosowanie wzorca Factory Method poprzez @classmethod oraz praktyczne wykorzystanie mechanizmu field(default_factory=...) w @dataclass do uniknięcia współdzielenia obiektów mutowalnych. Po ukończeniu tego modułu będziesz potrafił samodzielnie zaprojektować i zaimplementować prosty system obiektowy z obsługą błędów biznesowych i testami jednostkowymi.

Cele dydaktyczne

Przedstawione cele dydaktyczne zostały opracowane zgodnie z zasadą stopniowania trudności, co pozwala na systematyczne budowanie kompetencji. Każdy cel koncentruje się na konkretnym aspekcie programowania obiektowego, od teorii przez praktykę aż po zaawansowane zastosowania. Realizacja wszystkich założeń gwarantuje solidne podstawy do dalszego rozwoju w kierunku bardziej złożonych zagadnień, takich jak wzorce projektowe. Materiał został dostosowany do potrzeb studentów kierunków informatycznych. Oczekuje się, że po ukończeniu modułu student będzie potrafił samodzielnie projektować proste hierarchie klas i implementować je w Pythonie. Regularne sprawdzanie postępów pomoże w identyfikacji obszarów wymagających dodatkowej uwagi i powtórki.

Struktura kursu przewiduje płynne przejście od zagadnień podstawowych do bardziej złożonych, z licznymi przykładami i ćwiczeniami praktycznymi. Zaleca się aktywne uczestnictwo poprzez samodzielne eksperymenty z kodem. Środowisko REPL Pythona jest doskonałym narzędziem do natychmiastowego testowania omawianych koncepcji bez konieczności tworzenia plików projektowych. Warto również korzystać z dodatkowych materiałów, takich jak dokumentacja oficjalna Pythona oraz społecznościowe fora programistyczne. Systematyczna praca i regularne powtórki są kluczem do sukcesu w opanowaniu programowania obiektowego.

3 Mapa myśli: klasy i obiekty

Przegląd koncepcji OOP

  • Klasa - szablon definiujący strukturę i zachowanie obiektów. Definiuje atrybuty (stan) i metody (zachowanie). W Pythonie klasę tworzymy słowem kluczowym class .
  • Obiekt - konkretna instancją klasy z własnym stanem. Każdy obiekt ma własne wartości atrybutów, nawet jeśli został utworzony z tej samej klasy.
  • Atrybuty - dane przechowywane w obiekcie (stan). Mogą być atrybutami instancji (różne dla każdego obiektu) lub atrybutami klasowymi (wspólne dla wszystkich instancji).
  • Metody - funkcje zdefiniowane w klasie (zachowanie). Przyjmują self jako pierwszy argument, który odnosi się do konkretnej instancji wywołującej metodę.
  • Konstruktor __init__ - metoda wywoływana automatycznie przy tworzeniu obiektu. Służy do inicjalizacji atrybutów instancji. Nie zwraca wartości (zwraca None ).
  • self - odniesienie do bieżącej instancji. Konwencja nazewnicza (może być inna nazwa, ale self jest standardem). Pozwala na dostęp do atrybutów i metod danej instancji.

Klasa to przepis, obiekt to danie. Możemy tworzyć wiele obiektów z jednej klasy, każdy z innymi danymi. Na przykład z klasy Książka możemy utworzyć obiekty reprezentujące "Pana Tadeusza", "Lalkę" i "Dziady" - każdy z innym tytułem, autorem i rokiem wydania, ale wszystkie dzielą tę samą strukturę (atrybuty) i zachowanie (metody).

Tworzenie obiektu przez wywołanie klasy to jeden z najważniejszych wzorców w Pythonie. Składnia NazwaKlasy() może być myląca dla początkujących, ponieważ wygląda jak wywołanie funkcji, ale w rzeczywistości uruchamia złożony proces alokacji i inicjalizacji. Python, jako język dynamiczny, pozwala na tworzenie obiektów w dowolnym momencie działania programu, w tym wewnątrz pętli, warunków czy wyrażeń listowych. Każde wywołanie klasy tworzy nowy, niezależny obiekt w pamięci, nawet jeżeli klasa nie ma zdefiniowanego konstruktora. Zmienna przechowuje referencję do obiektu, a nie sam obiekt, co ma kluczowe znaczenie dla zrozumienia mechanizmu przypisań i kopiowania.

Proces tworzenia obiektu składa się z kilku etapów: najpierw wywoływana jest metoda __new__, która alokuje pamięć dla nowej instancji, następnie uruchamiany jest __init__ do inicjalizacji atrybutów, a na końcu zwracana jest referencja do gotowego obiektu. Większość programistów modyfikuje wyłącznie __init__, pozostawiając __new__ w domyślnej implementacji. Zrozumienie tej kolejności jest ważne przy tworzeniu bardziej zaawansowanych wzorców, takich jak Singleton czy Fabryka. Gdy tworzymy wiele obiektów w pętli, każdy z nich jest niezależną instancją z własnym stanem i tożsamością.

4 Mapa myśli: dziedziczenie, hermetyzacja, polimorfizm

Trzy filary OOP

  • Dziedziczenie - klasa pochodna przejmuje atrybuty i metody klasy bazowej. Użycie super() do wywołania konstruktora rodzica. W naszym projekcie BladBiblioteki dziedziczy po Exception , przejmując jego mechanizm obsługi błędów. Dziedziczenie pozwala na wielokrotne użycie kodu i budowanie hierarchii pojęciowych (relacja "jest").
  • Hermetyzacja - ukrywanie wewnętrznej implementacji. Konwencja _protected (pojedyncze podkreślenie) i __private (podwójne podkreślenie, name mangling). Dostęp poprzez gettery/setter lub @property . W projekcie używamy @property dla atrybutu liczba_ksiazek w klasie Czytelnik .
  • Polimorfizm - różne klasy implementują tę samą metodę na swój sposób. W Pythonie dzięki duck typing i metodom wirtualnym. W systemie bibliotecznym polimorfizm przejawia się w metodzie __str__ klasy BladBiblioteki , która nadpisuje domyślna implementację z Exception . Dzięki polimorfizmowi możemy traktować różne obiekty w jednolity sposób.

Dziedziczenie buduje hierarchię, hermetyzacja chroni dane, polimorfizm umożliwia elastyczność. Wszystkie trzy filary są ze sobą powiązane: dziedziczenie umożliwia polimorfizm, a hermetyzacja chroni szczegóły implementacji przed przypadkową modyfikacją.

Podsumowanie to dobry moment na refleksję nad przyswojonym materiałem i identyfikację obszarów wymagających dodatkowej pracy. Wymienione punkty stanowią esencję przerobionej części kursu - od definicji klasy, przez tworzenie obiektów, aż po kompozycję i wzorzec fabryki. Każdy z tych punktów będzie rozwijany w kolejnych modułach, dlatego warto upewnić się, że są dobrze zrozumiane. Zachęcamy do tworzenia własnych notatek i map myśli, które pomagają w usystematyzowaniu wiedzy. Regularne powtórki są kluczowe dla trwałego zapamiętania materiału.

Mapy myśli są skutecznym narzędziem wizualizacji złożonych koncepcji i relacji między nimi. Przedstawiona mapa obrazuje najważniejsze pojęcia omówione w tej części kursu oraz ich wzajemne powiązania. Tworzenie własnych map myśli podczas nauki programowania aktywuje inne obszary mózgu niż czytanie liniowego tekstu, co przekłada się na lepsze zapamiętywanie. Studenci, którzy regularnie tworzą mapy myśli, osiągają lepsze wyniki w testach koncepcyjnych i szybciej łączą nowe informacje z już posiadaną wiedzą. Zachęcamy do wykorzystania tej techniki.

5 Mapa myśli: dataclass, wyjątki, kompozycja

Zaawansowane mechanizmy

  • @dataclass - automatycznie generuje __init__ , __repr__ , __eq__ . Wystarczy zdefiniowac atrybuty klasowe z adnotacjami typów. Można również użyć parametrów takich jak order=True (generuje metody porównujace) czy frozen=True (tworzy niemutowalne obiekty). W projekcie używamy field(default_factory=list) dla atrybutów listowych, aby uniknąć problemu wspóldzielenia domyślnych wartości.
  • Własne wyjątki - klasa dziedzicząca po Exception . Pozwala na precyzyjną obsługę błędów. W naszej implementacji dodajemy atrybut kod , który umożliwia programistyczne określenie rodzaju błędu bez parsowania komunikatu tekstowego. To dobra praktyka w projektach komercyjnych - kod błędu jest stabilny, komunikat może ulec zmianie.
  • Kompozycja - obiekt zawiera inne obiekty (relacja "ma"). W systemie Biblioteka zawiera listę Książek i Czytelników. Jest to relacja słabsza niż dziedziczenie: zmiana w Ksiazce nie wpływa bezpośrednio na Bibliotekę, a obiekty mogą istniec niezależnie. Kompozycje preferujemy nad dziedziczeniem, gdy nie ma naturalnej relacji "jest".
  • @classmethod - metoda działająca na klasie, nie na instancji. Alternatywny konstruktor. W projekcie metoda from_csv tworzy instancję Biblioteka wypełniona danymi z pliku. To realizacja wzorca projektowego Factory Method. @classmethod przyjmuje cls jako pierwszy argument (referencję do klasy), co pozwala na polimorficzne tworzenie instancji klas pochodnych.

Przechowywanie obiektów w kolekcjach, takich jak listy, słowniki czy zbiory, jest podstawowym wzorcem w programowaniu obiektowym. Listy obiektów pozwalają na zbiorcze operacje, takie jak iterowanie, filtrowanie, sortowanie czy agregowanie danych. Python oferuje bogaty zestaw narzędzi do pracy z listąmi, w tym wyrażenia listowe, funkcje sorted(), filter() oraz sortowanie z kluczem lambda. Ważne jest zrozumienie, że lista przechowuje referencję do obiektów, a nie ich kopie, co ma wpływ na modyfikację danych przez różne części programu. Iterowanie po liście obiektów i wywoływanie ich metod to jeden z najczęstszych wzorców w codziennej pracy.

Kompozycja, czyli umieszczanie jednych obiektów wewnątrz innych, jest podstawową techniką budowania złożonych systemów z prostszych komponentów. W przeciwieństwie do dziedziczenia, które wyraża relację 'jest', kompozycja wyraża relację 'ma' i jest często preferowanym podejściem w projektowaniu obiektowym. Graf obiektów w rzeczywistych systemach może być bardzo złożony, ale nawigacja po nim jest naturalna i czytelna dzięki notacji kropkowej. Kompozycja pozwala na elastyczne budowanie funkcjonalności poprzez łączenie prostych obiektów w bardziej złożone struktury. Wzorzec ten jest fundamentem takich koncepcji jak wstrzykiwanie zależności czy architektura warstwowa.

6 Wprowadzenie do projektu: System biblioteczny

System biblioteczny

Zaprojektujemy i zaimplementujemy prosty system do zarządzania biblioteka. System pozwoli na przechowywanie informacji o książkach i czytelnikach, wypożyczanie i zwracanie książek oraz obsługę błędów.

Projekt wykorzystuje wszystkie kluczowe koncepcje OOP poznane w trakcie kursu: klasy, obiekty, hermetyzację, kompozycję, własne wyjątki, dekoratory @dataclass , @property i @classmethod .

Motywacja do wyboru systemu bibliotecznego jest jego uniwersalność - każdy rozumie koncept wypożyczania książek, dzięki czemu możesz się skupić na aspektach technicznych OOP, a nie na zrozumieniu domeny biznesowej. System jest na tyle prosty, by zaimplementować go w ciągu kilkudziesięciu minut, ale jednocześnie na tyle złożony, by zaprezentować wszystkie ważne mechanizmy OOP.

Cel: Działający system konsolowy z testami jednostkowymi, demonstrujący poprawne praktyki OOP w Pythonie.
System biblioteczny

Moduł ten stanowi pierwszą część kompleksowego kursu programowania obiektowego w języku Python. Jego celem jest wprowadzenie studentów w paradygmat obiektowy oraz przedstawienie fundamentalnych koncepcji, takich jak klasy, obiekty, atrybuty i metody. Materiał został zaprojektowany tak, aby stopniowo budować zrozumienie od prostych analogii do konkretnych implementacji w kodzie. Każdy slajd zawiera przykłady, które można samodzielnie przetestować w środowisku REPL. Zaleca się aktywne uczestnictwo poprzez modyfikowanie przykładów i wykonywanie ćwiczeń. Systematyczna praca z materiałem gwarantuje solidne opanowanie podstaw OOP. W kolejnych modułach wiedza ta będzie rozwijana o bardziej zaawansowane zagadnienia.

Programowanie obiektowe to nie tylko zestaw reguł składniowych, ale przede wszystkim sposób myślenia o problemach programistycznych. Kluczowe jest zrozumienie, że klasy służą do modelowania rzeczywistych bytów i relacji między nimi. Dzięki OOP kod staje się bardziej modularny, łatwiejszy w utrzymaniu i bardziej odporny na błędy. Współczesne aplikacje webowe, systemy bazodanowe i frameworki w dużym stopniu opierają się na paradygmacie obiektowym. Opanowanie OOP otwiera drzwi do zrozumienia zaawansowanych wzorców projektowych. Zachęcamy do cierpliwej i systematycznej nauki - każde nowe pojęcie będzie szczegółowo wyjaśnione i zilustrowane przykładami.

7Wymagania funkcjonalne

Co system ma robić?

Wymagania funkcjonalne określają, jakie operację system musi udostępniać. Są one podstawą do zaprojektowania klas i metod. Każde wymaganie przekłada się na konkretna metodę w kodzie.

  1. Przechowywac liste książek (tytuł, autor, rok) z informacją o dostępności
  2. Przechowywac liste czytelników (imie, nazwisko) z listą wypożyczonych książek
  3. Dodawac nowe książki do biblioteki - z walidacją duplikatów
  4. Wypożyczac książkę czytelnikowi (jeśli dostępną)
  5. Zwracac książkę do biblioteki (jeśli wypożyczona przez tego czytelnika)
  6. Sygnalizowac błędy: wypożyczenie niedostępnej książki, zwrot nie swojej książki, duplikat tytułu
  7. Wyszukiwac książki po tytule lub autorze - nieczułe na wielkość liter

Wymagania niefunkcjonalne: dane przechowywane w pamięci (brak bazy danych), aplikacja konsolowa (bez GUI), każda książka identyfikowana po tytule (unikalność w obrębie biblioteki), czytelnik może wypożyczyć wiele książek jednocześnie. Te założenia upraszczają implementację, zachowując jednocześnie wystarczająca złożoność do zaprezentowania OOP.

Przekazywanie obiektów jako argumentów do funkcji i zwracanie ich z funkcji to podstawowe mechanizmy komunikacji między modułami programu. W Pythonie obiekty są przekazywane przez referencję, co oznacza, że funkcja otrzymuje dostęp do oryginalnego obiektu, a nie jego kopii. Ma to istotne konsekwencje dla projektowania - funkcja modyfikująca obiekt wpływa na stan widoczny w miejscu wywołania. Wzorzec fabryki polega na wydzieleniu logiki tworzenia obiektów do osobnej funkcji, co pozwala ukryć złożoność konstruktora i zapewnić centralne miejsce do zarządzania procesem tworzenia. Funkcje fabryczne są szczególnie przydatne przy walidacji danych, transformacji formatów i konfiguracji.

Funkcje operujące na obiektach przez ich atrybuty nie muszą znać wewnętrznej struktury klasy - to przejaw polimorfizmu i enkapsulacji. Jeśli funkcja modyfikuje przekazane obiekty, warto jasno dokumentować ten fakt w docstringu, aby uniknąć nieoczekiwanych skutków ubocznych. W podejściu funkcyjnym preferuje się tworzenie nowych obiektów zamiast modyfikacji istniejących, co prowadzi do kodu bardziej przewidywalnego. W OOP modyfikacja przez funkcję zewnętrzną jest akceptowalna, ale należy stosować ją z umiarem. Dobrze zaprojektowana fabryka może znacząco uprościć kod kliencki i zwiększyć jego czytelność.

8 Diagram klas (tekstowy opis)

Struktura klas

Poniższy diagram przedstawia cztery klasy systemu, ich atrybuty i metody. Strzałki wskazują relacje: BladBiblioteki dziedziczy po Exception , Biblioteka zawiera listy obiektów Książka i Czytelnik (kompozycja), a Czytelnik przechowuje referencję do wypożyczonych Książka (asocjacja).

# Diagram klas - System biblioteczny
# ====================================

Ksiazka
    tytul: str
  autor: str
  rok: int
  wypozyczona: bool = False

Czytelnik
    imie: str
  nazwisko: str
  wypozyczone: list[Ksiazka]
    liczba_ksiazek (property)

BladBiblioteki(Exception)
    komunikat: str
    kod: int

Biblioteka
    ksiazki: list[Ksiazka]
  czytelnicy: list[Czytelnik]
    dodaj_ksiazke()
    wypozycz()
    zwroc()
    szukaj_ksiazki()
    from_csv (@classmethod)

Z diagramu wynika, że Biblioteka jest klasą centralną - to ona posiada logikę biznesową i zarządza obiektami. Książka i Czytelnik to modele danych (reprezentują encje świata rzeczywistego). BladBiblioteki to klasa pomocnicza używana przez Bibliotekę do sygnalizowania błędów. Taki podział odpowiedzialności spełnia zasadę pojedynczej odpowiedzialności (SRP).

Diagram klas

Slajd zatytułowany "Diagram klas (tekstowy opis)" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

9Opis klasy Książka

Klasa Książka

Klasa reprezentująca pojedynczą książkę w systemie bibliotecznym. Zastosujemy @dataclass , aby uniknąć pisania kodu boilerplate dla konstruktora i reprezentacji tekstowej. To pozwala skupić się na tym, jakie dane klasa przechowuje, a nie na tym, jak je inicjalizuje.

  • tytul : str - tytuł książki. Unikalny identyfikator książki w systemie (dwie książki nie mogą miec tego samego tytułu).
  • autor : str - autor książki. Przechowywany jako pełne imie i nazwisko.
  • rok : int - rok wydania. Liczba całkowita, np. 1834 dla "Pana Tadeusza".
  • wypożyczona : bool - status wypożyczenia (domyślnie False ). Informacja o tym, czy książka jest aktualnie wypożyczona.

Klasą będzie prosta - przechowuje dane, nie ma własnych metod biznesowych. To świadoma decyzja projektowa: oddzielamy model danych od logiki biznesowej. Dzięki temu, jeśli w przyszłości zechcemy zmienić sposób reprezentacji książki (np. dodać pole ISBN), zmiana będzie izolowana w jednym miejscu. Klasa Książka jest przykładem wzorca Anemic Domain Model, który w prostych systemach jest w pełni wystarczający.

Klasą Książka

Slajd zatytułowany "Opis klasy Książka" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

10Opis klasy Czytelnik

Klasa Czytelnik

Klasa reprezentująca czytelnika bibliotecznego. Każdy czytelnik ma liste wypożyczonych książek. Użyjemy @property do udostępnienia liczby wypożyczonych książek. W przeciwieństwie do Książka , klasa Czytelnik posiada jedna metodę biznesową - właściwość liczba_ksiazek .

  • imie: str- imie czytelnika
  • nazwisko: str- nazwisko czytelnika
  • wypożyczone : list[Książka] - lista wypożyczonych książek. Inicjalizowana jako pusta lista dla każdego nowego czytelnika.
  • liczba_ksiazek : int - właściwość (property) zwracajaca len(wypożyczone) . Dzięki @property używamy tej wartości jak atrybutu ( cz.liczba_ksiazek ), a nie metody ( cz.liczba_ksiazek() ).

Użycie @property zamiast zwykłego atrybutu daje nam elastyczność: w przyszłości możemy dodać logikę walidacji lub liczenia bez zmiany interfejsu klasy. Na przykład, jeśli zechcemy ograniczyć maksymalna liczbę wypożyczonych książek, dodamy odpowiednie sprawdzenie we wnetrzu property, a zewnętrzny kod dalej będzie używal cz.liczba_ksiazek .

Klasą Czytelnik

Slajd zatytułowany "Opis klasy Czytelnik" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

11Opis klasy Biblioteka

Klasa Biblioteka

Główna klasa systemu - zarządza ksiazkami i czytelnikami. To tutaj znajduje się logika biznesową: wypożyczanie, zwracanie, dodawanie i wyszukiwanie. Jest to najbardziej złożona klasa w projekcie, implementujaca wszystkie operacje biznesowe.

Klasa Biblioteka pełni role fasady - udostępnia prosty interfejs do złożonych operacji. Użytkownik systemu (programista używajacy tej klasy) nie musi znac szczególów implementacji, wystarczy że wywoła odpowiednią metodę z odpowiednimi argumentami. To realizacja zasady enkapsulacji na poziomie klasy.

  • książki : list[Książka] - wszystkie książki w bibliotece. Lista przechowuje obiekty klasy Książka .
  • czytelnicy : list[Czytelnik] - zarejestrowani czytelnicy. Lista przechowuje obiekty klasy Czytelnik .
  • dodaj_ksiazke(książka) - dodaje książkę, sprawdza duplikaty na podstawie tytułu (porównanie nieczułe na wielkość liter)
  • wypożycz(tytuł, czytelnik)- wypożycza książkę czytelnikowi, jeśli jest dostępną
  • zwroc(tytuł, czytelnik) - przyjmuje zwrot książki, jeśli byla wypożyczona przez tego czytelnika
  • szukaj_ksiazki(fraza) - wyszukuje po tytule lub autorze, zwraca liste wszystkich pasujących książek (może być pusta)
  • from_csv(ścieżka) - klasowa metoda do wczytywania książek z pliku CSV, alternatywny konstruktor (wzorzec Factory Method)
Klasą Biblioteka

Slajd zatytułowany "Opis klasy Biblioteka" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

12 Opis klasy BladBiblioteki

Klasa BladBiblioteki

Własna klasa wyjątku, dziedzicząca po Exception . Umożliwia precyzyjne określenie rodzaju błędu w systemie bibliotecznym. Zamiast używac ogólnego Exception lub rzucać standardowe wyjątki Pythona (jak ValueError ), tworzymy własna hierarchię, co pozwala na selektywne łapanie błędów związanych z logiką biblioteki.

  • komunikat : str - opis błędu, czytelny dla człowieka. Przekazywany do konstruktora rodzica ( Exception ), dzięki czemu jest dostępny w standardowym mechanizmie wyjątków.
  • kod : int - numeryczny kod błędu. Umożliwia programistyczne reagowanie bez parsowania komunikatu tekstowego:
  • 1 - książka niedostępna (próba wypożyczenia już wypożyczonej książki)
  • 2 - czytelnik nie ma takiej książki (próba zwrotu nie swojej książki)
  • 3 - duplikat książki (próba dodania książki o istniejacym tytule)
  • 4 - nie znaleziono książki (próba wypożyczenia/zwrotu nieistniejącej książki lub brak pliku CSV)

Dzięki kodom błędów możemy programistycznie reagować na różne sytuację. Na przykład, kod 1 może wyświetlić inny komunikat w interfejsie użytkownika niż kod 3. W kodzie obsługi błędu możemy użyć instrukcji warunkowej: if e.kod == 1: ... elif e.kod == 3: ... .

Klasą BladBiblioteki

Błędy są naturalną i nieodłączną częścią procesu uczenia się programowania. Każdy, nawet najbardziej doświadczony programista, popełnia błędy na co dzień - kluczowa jest umiejętność ich szybkiego identyfikowania i poprawiania. Przedstawione na tym slajdzie typowe pomyłki zostały zebrane na podstawie wieloletnich doświadczeń nauczycieli programowania i występują u większości początkujących. Zapamiętanie ich i zrozumienie przyczyn pomoże Ci uniknąć frustracji i straconego czasu na debugowanie. Warto również zapoznać się z technikami debugowania, takimi jak użycie print(), logging czy debuggera wbudowanego w IDE. Świadomość typowych pułapek to pierwszy krok do ich unikania.

Nowoczesne edytory kodu i IDE oferują wiele narzędzi pomagających w wykrywaniu błędów jeszcze przed uruchomieniem programu. PyCharm, VS Code z wtyczką Python, a nawet zaawansowane edytory tekstu z obsługą lintingu potrafią ostrzegać przed wieloma typowymi pomyłkami w czasie rzeczywistym. Korzystanie z type hintów, narzędzi takich jak mypy do statycznej analizy typów oraz systemów CI/CD z automatycznym uruchamianiem testów znacząco redukuje liczbę błędów w kodzie produkcyjnym. Warto od początku nauki wyrobić sobie nawyk korzystania z tych narzędzi, ponieważ znacząco podnoszą one jakość i niezawodność tworzonego oprogramowania.

13Relacje między klasami

Jak klasy wspólpracuja?

W systemie bibliotecznym występują cztery rodzaje relacji między klasami. Zrozumienie tych relacji jest kluczowe dla projektowania obiektowego - pozwala określić, jak klasy beda ze sobą powiązane i jakie mają zależności.

  • Kompozycja : Biblioteka zawiera liste Książek i liste Czytelników. Jeśli Biblioteka zostanie zniszczona, książki i czytelnicy nadal istnieją (slaba kompozycja - agregacja). To relacja "ma": Biblioteka ma książki, Biblioteka ma czytelników.
  • Asocjacja : Czytelnik zna swoje wypożyczone Książki (przechowuje referencję w liscie wypożyczone ). Książka wie , czy jest wypożyczona (atrybut wypożyczona ). To relacja dwukierunkowa: Czytelnik wie, co wypożyczył, a Książka wie, czy jest wypożyczona.
  • Zależność : Biblioteka używa BladBiblioteki do sygnalizowania błędów. To najsłabszy typ relacji - Biblioteka importuje i używa BladBiblioteki, ale nie przechowuje jej jako atrybutu. BladBiblioteki jest używana tylko w momencie wystąpienia błędu.
  • Dziedziczenie : BladBiblioteki extends Exception. To relacja "jest": BladBiblioteki jest wyjątkiem. Dziedziczenie pozwala przejac wszystkie metody i właściwości klasy bazowej.

Slajd zatytułowany "Relacje między klasami" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

14Założenia projektowe
Założenia: Projekt edukacyjny - priorytetem jest czytelność i zgodność z OOP, nie wydajność. Kod ma być przede wszystkim zrozumiały i dobrze ilustrowac koncepcje.
  • Dane przechowywane w pamięci (brak bazy danych) - upraszcza implementację i pozwala skupić się na OOP, a nie na mechanizmach trwałości danych
  • Brak interfejsu graficznego - aplikacja konsolowa, testowana przez skrypty Pythona
  • Każda książka ma unikalny tytuł (identyfikator) - porównanie tytulów jest nieczułe na wielkość liter
  • Jeden czytelnik może wypożyczyć wiele książek - brak limitu wypożyczeń
  • Klasa BladBiblioteki służy do obsługi wszystkich błędów biznesowych - jeden punkt obsługi błędów dla całej domeny biblioteki
  • Testówanie ręczne poprzez skrypty demonstracyjne oraz testy jednostkowe z asercjami

Te założenia mają konkretne konsekwencje projektowe. Na przykład, brak bazy danych oznacza, że dane istnieją tylko podczas działania programu - po zakończeniu skryptu stan biblioteki jest tracony. Unikalność tytułu jako identyfikatora wymuszą sprawdzanie duplikatów przy dodawaniu książek. Brak limitu wypożyczeń upraszcza logikę biznesową, ale w realnym systemie wymagałby dodatkowej walidacji.

Założenia projektowe

Slajd zatytułowany "Założenia projektowe" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

15Podsumowanie projektu

Co zbudujemy?

System składa się z czterech klas wspólpracujacych ze sobą. Klasa Biblioteka jest centrum systemu - zarządza danymi i logika biznesową. Książka i Czytelnik to modele danych. BladBiblioteki to mechanizm obsługi błędów.

Wszystkie klasy są ze sobą powiązane w spójna całość, co demonstruje kompozycję i zależności w praktyce. Dzięki takiemu podzialowi każda klasa ma jasno określona odpowiedzialność, co ułatwia testowanie, debugowanie i przyszly rozwój.

W kolejnych slajdach przejdziemy do implementacji krok po kroku. Zaczniemy od najprostszych klas (modeli danych), przez własny wyjątek, az po glówna klasę biblioteki z logiką biznesową. Każdy krok będzie poprzedzony wprowadzeniem teoretycznym i zakończony testami.

Podsumowanie to dobry moment na refleksję nad przyswojonym materiałem i identyfikację obszarów wymagających dodatkowej pracy. Wymienione punkty stanowią esencję przerobionej części kursu - od definicji klasy, przez tworzenie obiektów, aż po kompozycję i wzorzec fabryki. Każdy z tych punktów będzie rozwijany w kolejnych modułach, dlatego warto upewnić się, że są dobrze zrozumiane. Zachęcamy do tworzenia własnych notatek i map myśli, które pomagają w usystematyzowaniu wiedzy. Regularne powtórki są kluczowe dla trwałego zapamiętania materiału.

Mapy myśli są skutecznym narzędziem wizualizacji złożonych koncepcji i relacji między nimi. Przedstawiona mapa obrazuje najważniejsze pojęcia omówione w tej części kursu oraz ich wzajemne powiązania. Tworzenie własnych map myśli podczas nauki programowania aktywuje inne obszary mózgu niż czytanie liniowego tekstu, co przekłada się na lepsze zapamiętywanie. Studenci, którzy regularnie tworzą mapy myśli, osiągają lepsze wyniki w testach koncepcyjnych i szybciej łączą nowe informacje z już posiadaną wiedzą. Zachęcamy do wykorzystania tej techniki.

16 Wprowadzenie - klasa Książka

Implementacja: Książka

Rozpoczynamy implementację od najprostszej klasy - Książka . Użyjemy dekoratora @dataclass , który automatycznie wygeneruje metodę __init__ , __repr__ oraz __eq__ . To jeden z najwygodniejszych mechanizmów Pythona do szybkiego tworzenia modeli danych.

Dzięki @dataclass kod jest zwarty i czytelny - definiujemy tylko atrybuty z adnotacjami typów. Reszta generowana jest automatycznie. Gdybyśmy mieli pisać tę metodę ręcznie, potrzebowalibyśmy około 20 linii kodu zamiast 4.

Atrybut wypożyczona ma domyślną wartość False - nowa książka jest zawsze dostępna. To ważne: dzięki wartości domyślnej nie musimy podawać tego argumentu przy tworzeniu obiektu, chyba że chcemy utworzyć książkę już wypożyczoną.

Ważna uwaga: w @dataclass atrybuty z wartościami domyślnymi muszą być zdefiniowane po atrybutach bez wartości domyślnych. W przeciwnym razie Python zgłosi błąd składni. Dlatego wypożyczona jest ostatnim atrybutem.

Wprowadzenie Książka

Moduł ten stanowi pierwszą część kompleksowego kursu programowania obiektowego w języku Python. Jego celem jest wprowadzenie studentów w paradygmat obiektowy oraz przedstawienie fundamentalnych koncepcji, takich jak klasy, obiekty, atrybuty i metody. Materiał został zaprojektowany tak, aby stopniowo budować zrozumienie od prostych analogii do konkretnych implementacji w kodzie. Każdy slajd zawiera przykłady, które można samodzielnie przetestować w środowisku REPL. Zaleca się aktywne uczestnictwo poprzez modyfikowanie przykładów i wykonywanie ćwiczeń. Systematyczna praca z materiałem gwarantuje solidne opanowanie podstaw OOP. W kolejnych modułach wiedza ta będzie rozwijana o bardziej zaawansowane zagadnienia.

Programowanie obiektowe to nie tylko zestaw reguł składniowych, ale przede wszystkim sposób myślenia o problemach programistycznych. Kluczowe jest zrozumienie, że klasy służą do modelowania rzeczywistych bytów i relacji między nimi. Dzięki OOP kod staje się bardziej modularny, łatwiejszy w utrzymaniu i bardziej odporny na błędy. Współczesne aplikacje webowe, systemy bazodanowe i frameworki w dużym stopniu opierają się na paradygmacie obiektowym. Opanowanie OOP otwiera drzwi do zrozumienia zaawansowanych wzorców projektowych. Zachęcamy do cierpliwej i systematycznej nauki - każde nowe pojęcie będzie szczegółowo wyjaśnione i zilustrowane przykładami.

17Kod: import dataclass

Importowanie dataclass

Zanim zdefiniujemy klasę, musimy zaimportować dekorator dataclass z modułu dataclasses . Modul dataclasses jest dostępny od Pythona 3.7 i stanowi część standardowej biblioteki.

# krok_01_ksiazka.py
from dataclasses import dataclass

To wszystko, czego potrzebujemy na początek. dataclass jest częścią standardowej biblioteki Pythona, więc nie trzeba instalowac żadnych dodatkowych pakietów. W razie potrzeby możemy zaimportować również field (do zaawansowanej konfiguracji atrybutów) oraz inne narzędzia jak asdict , astuple czy replace .

Standardowy import dla tego projektu będzie wygladal następująco:

from dataclasses import dataclass, field

field przyda się nam później, gdy bedziemy definiować klasę Czytelnik z domyślną listą.

Import dataclass

Slajd zatytułowany "Kod: import dataclass" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

18 Kod: definicja Książka z @dataclass

Definicja klasy Książka

@dataclass
class Ksiazka:
    tytul: str
    autor: str
    rok: int
    wypozyczona: bool = False

Dzięki @dataclassPython automatycznie tworzy:

  • __init__(self, tytuł, autor, rok, wypożyczona=False) - konstruktor z argumentami pozycyjnymi
  • __repr__(self) - czytelna reprezentacja tekstowa, np. Książka(tytul='Pan Tadeusz', autor='Adam Mickiewicz', rok=1834, wypożyczona=False)
  • __eq__(self, other) - porównywanie obiektów po wartościach wszystkich atrybutów, a nie po identyfikatorze w pamięci

Gdybysmy mieli napisać tę samą klasę bez @dataclass, wygladalaby tak:

class Ksiazka:
    def __init__(self, tytul, autor, rok, wypozyczona=False):
        self.tytul = tytul
        self.autor = autor
        self.rok = rok
        self.wypozyczona = wypozyczona

    def __repr__(self):
        return f"Ksiazka(tytul={self.tytul!r}, autor={self.autor!r}, rok={self.rok!r}, wypozyczona={self.wypozyczona!r})"

    def __eq__(self, other):
        if not isinstance(other, Ksiazka):
            return NotImplemented
        return (self.tytul, self.autor, self.rok, self.wypozyczona) == (other.tytul, other.autor, other.rok, other.wypozyczona)

To az 17 linii kodu zamiast 4 - widać wyraźnie, ile oszczedza @dataclass.

Definicja Książka

Programowanie obiektowe wywodzi się z języka Simula z lat 60. XX wieku, a jego rozwój przyspieszył w latach 70. wraz z językiem Smalltalk, który wprowadził wiele koncepcji używanych do dziś, takich jak klasy, metody i dziedziczenie. Współcześnie OOP jest wspierane przez większość popularnych języków programowania, choć każdy z nich implementuje ten paradygmat nieco inaczej. Python przyjął podejście pragmatyczne, w którym wszystko jest obiektem, ale programista nie jest zmuszany do obiektowego stylu. Dzięki temu Python jest językiem wieloparadygmatowym, łączącym OOP z programowaniem proceduralnym i funkcyjnym w elastyczny sposób. Zrozumienie genezy i ewolucji OOP pomaga docenić jego zalety i świadomie stosować go we własnych projektach programistycznych.

Cztery filary OOP - enkapsulacja, dziedziczenie, polimorfizm i abstrakcja - są ze sobą ściśle powiązane i wzajemnie się uzupełniają. Enkapsulacja chroni stan obiektu przed niekontrolowanym dostępem z zewnątrz, co zwiększa bezpieczeństwo i spójność danych. Dziedziczenie pozwala na budowanie hierarchii klas i wielokrotne wykorzystanie kodu bez jego kopiowania. Polimorfizm umożliwia jednolity interfejs dla różnych typów obiektów, co upraszcza projektowanie rozszerzalnych systemów. Abstrakcja koncentruje się na istotnych cechach, pomijając nieistotne szczegóły implementacyjne. Opanowanie tych czterech koncepcji stanowi fundament biegłości w OOP.

19 Kod: tworzenie obiektów Książka

Tworzenie instancji Książka

# Tworzymy obiekty ksiazek
ks1 = Ksiazka("Pan Tadeusz", "Adam Mickiewicz", 1834)
ks2 = Ksiazka("Lalka", "Boleslaw Prus", 1890)
ks3 = Ksiazka("Dziady", "Adam Mickiewicz", 1823)

# Specjalnie oznaczamy jako wypozyczona
ks4 = Ksiazka(
    "Ferdydurke", "Witold Gombrowicz", 1937,
    wypozyczona=True
)

Konstruktor przyjmuje argumenty zgodnie z kolejnością atrybutów w klasie. Argumenty z wartością domyślna mogą być pomijane. W przypadku ks4 używamy argumentu kluczowego wypożyczona=True , aby utworzyć książkę już wypożyczona.

Możemy tez używac argumentów kluczowych dla wszystkich parametrów, co poprawia czytelność kodu:

ks5 = Ksiazka(
    tytul="Solaris",
    autor="Stanislaw Lem",
    rok=1961,
    wypozyczona=False
)

Każdy obiekt Książka jest niezależny - zmiana atrybutów w jednym obiekcie nie wpływa na inne.

Tworzenie obiektu przez wywołanie klasy to jeden z najważniejszych wzorców w Pythonie. Składnia NazwaKlasy() może być myląca dla początkujących, ponieważ wygląda jak wywołanie funkcji, ale w rzeczywistości uruchamia złożony proces alokacji i inicjalizacji. Python, jako język dynamiczny, pozwala na tworzenie obiektów w dowolnym momencie działania programu, w tym wewnątrz pętli, warunków czy wyrażeń listowych. Każde wywołanie klasy tworzy nowy, niezależny obiekt w pamięci, nawet jeżeli klasa nie ma zdefiniowanego konstruktora. Zmienna przechowuje referencję do obiektu, a nie sam obiekt, co ma kluczowe znaczenie dla zrozumienia mechanizmu przypisań i kopiowania.

Proces tworzenia obiektu składa się z kilku etapów: najpierw wywoływana jest metoda __new__, która alokuje pamięć dla nowej instancji, następnie uruchamiany jest __init__ do inicjalizacji atrybutów, a na końcu zwracana jest referencja do gotowego obiektu. Większość programistów modyfikuje wyłącznie __init__, pozostawiając __new__ w domyślnej implementacji. Zrozumienie tej kolejności jest ważne przy tworzeniu bardziej zaawansowanych wzorców, takich jak Singleton czy Fabryka. Gdy tworzymy wiele obiektów w pętli, każdy z nich jest niezależną instancją z własnym stanem i tożsamością.

20 Kod: testowanie Książka

Testówanie klasy Książka

print(ks1)
# Ksiazka(tytul='Pan Tadeusz', autor='Adam Mickiewicz', rok=1834, wypozyczona=False)

print(ks1 == ks2)
# False - rozne wartosci atrybutow

print(ks1.wypozyczona)
# False

print(ks4.wypozyczona)
# True

__repr__ generowany przez @dataclass pokazuje wszystkie atrybuty i ich wartości - idealne do debugowania. Możemy dzięki temu latwo sprawdzić stan każdego obiektu.

Porównajmy z działaniem domyslnym (bez @dataclass ): standardowo Python porównuje obiekty po identyfikatorze w pamięci ( is ), a nie po wartościach. Dzięki __eq__ wygenerowanemu przez @dataclass , dwie rózne instancję z tymi samymi atrybutami są uwazane za równe:

ks_a = Ksiazka("Test", "Autor", 2000)
ks_b = Ksiazka("Test", "Autor", 2000)
print(ks_a == ks_b)  # True (dzieki @dataclass)
print(ks_a is ks_b)  # False (to rozne obiekty w pamieci)
Testówanie Książka

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

21 Wprowadzenie - klasa Czytelnik

Implementacja: Czytelnik

Klasa Czytelnik przechowuje dane osobowe oraz liste wypożyczonych książek. Wykorzystamy @dataclass ponownie, ale tym razem z @property do udostępnienia liczby książek. To pokazuje, jak @dataclass może być laczony z recznie definiowanymi właściwosciami.

Atrybut wypożyczone jest lista - nie możemy użyć domyślnej wartości list() bezpośrednio w @dataclass , ponieważ spowodowaloby to wspóldzielenie tego samego obiektu listy przez wszystkie instancję. To jeden z najczestszych błędów w Pythonie: domyślne argumenty w funkcjach są ewaluowane tylko raz, w momencie definicji. Gdybysmy napisali wypożyczone: list = [] , każdy nowy Czytelnik wspóldzielilby tę samą liste. Zamiast tego użyjemy field(default_factory=list) , co tworzy nowa, pusta liste dla każdej instancji.

Klasa Czytelnik jest przykładem polaczenia automatycznie generowanego kodu ( @dataclass ) z recznie zdefiniowana logika ( @property ). To elastyczne podejście, charakterystyczne dla Pythona.

Moduł ten stanowi pierwszą część kompleksowego kursu programowania obiektowego w języku Python. Jego celem jest wprowadzenie studentów w paradygmat obiektowy oraz przedstawienie fundamentalnych koncepcji, takich jak klasy, obiekty, atrybuty i metody. Materiał został zaprojektowany tak, aby stopniowo budować zrozumienie od prostych analogii do konkretnych implementacji w kodzie. Każdy slajd zawiera przykłady, które można samodzielnie przetestować w środowisku REPL. Zaleca się aktywne uczestnictwo poprzez modyfikowanie przykładów i wykonywanie ćwiczeń. Systematyczna praca z materiałem gwarantuje solidne opanowanie podstaw OOP. W kolejnych modułach wiedza ta będzie rozwijana o bardziej zaawansowane zagadnienia.

Programowanie obiektowe to nie tylko zestaw reguł składniowych, ale przede wszystkim sposób myślenia o problemach programistycznych. Kluczowe jest zrozumienie, że klasy służą do modelowania rzeczywistych bytów i relacji między nimi. Dzięki OOP kod staje się bardziej modularny, łatwiejszy w utrzymaniu i bardziej odporny na błędy. Współczesne aplikacje webowe, systemy bazodanowe i frameworki w dużym stopniu opierają się na paradygmacie obiektowym. Opanowanie OOP otwiera drzwi do zrozumienia zaawansowanych wzorców projektowych. Zachęcamy do cierpliwej i systematycznej nauki - każde nowe pojęcie będzie szczegółowo wyjaśnione i zilustrowane przykładami.

22 Kod: definicja Czytelnik

Definicja klasy Czytelnik

from dataclasses import dataclass, field

@dataclass
class Czytelnik:
    imie: str
    nazwisko: str
    wypozyczone: list = field(default_factory=list)

    @property
    def liczba_ksiazek(self):
        return len(self.wypozyczone)

field(default_factory=list) tworzy nowa, pusta liste dla każdej nowej instancji - unikamy wspóldzielenia tej samej listy między obiektami. To najbezpieczniejszy sposób inicjalizacji mutowalnych typów w @dataclass .

Właściwość @property liczba_ksiazek jest tylko do odczytu - nie ma settera. To celowe: liczba książek jest pochodna listy wypożyczone , więc nie powinna być ustawiana bezpośrednio. Gdybysmy chcieli ja zmodyfikować, musielibysmy dodać lub usunąć element z listy wypożyczone , a liczba_ksiazek automatycznie się zaktualizuje.

Ważne: typ atrybutu wypożyczone to list (bez adnotacji typu elementów). W nowszym Pythonie można użyć list[Książka] , ale wymaga to importu from __future__ import annotations lub użycia typing.List w starszych wersjach. Dla prostoty używamy list .

Definicja Czytelnik

Programowanie obiektowe wywodzi się z języka Simula z lat 60. XX wieku, a jego rozwój przyspieszył w latach 70. wraz z językiem Smalltalk, który wprowadził wiele koncepcji używanych do dziś, takich jak klasy, metody i dziedziczenie. Współcześnie OOP jest wspierane przez większość popularnych języków programowania, choć każdy z nich implementuje ten paradygmat nieco inaczej. Python przyjął podejście pragmatyczne, w którym wszystko jest obiektem, ale programista nie jest zmuszany do obiektowego stylu. Dzięki temu Python jest językiem wieloparadygmatowym, łączącym OOP z programowaniem proceduralnym i funkcyjnym w elastyczny sposób. Zrozumienie genezy i ewolucji OOP pomaga docenić jego zalety i świadomie stosować go we własnych projektach programistycznych.

Cztery filary OOP - enkapsulacja, dziedziczenie, polimorfizm i abstrakcja - są ze sobą ściśle powiązane i wzajemnie się uzupełniają. Enkapsulacja chroni stan obiektu przed niekontrolowanym dostępem z zewnątrz, co zwiększa bezpieczeństwo i spójność danych. Dziedziczenie pozwala na budowanie hierarchii klas i wielokrotne wykorzystanie kodu bez jego kopiowania. Polimorfizm umożliwia jednolity interfejs dla różnych typów obiektów, co upraszcza projektowanie rozszerzalnych systemów. Abstrakcja koncentruje się na istotnych cechach, pomijając nieistotne szczegóły implementacyjne. Opanowanie tych czterech koncepcji stanowi fundament biegłości w OOP.

23 Kod: @property dla liczby książek

Właściwość liczba_ksiazek

# Uzycie property
cz = Czytelnik("Jan", "Kowalski")
print(cz.liczba_ksiazek)
# 0

# Dodajemy ksiazke do listy wypozyczonych
cz.wypozyczone.append(ks1)
print(cz.liczba_ksiazek)
# 1

# liczba_ksiazek jest wlasciwoscia, nie metoda - bez nawiasow
# @property chroni atrybut i dodaje logikę przy dostepie

Dzięki @property możemy używac liczba_ksiazek jak atrybutu, podczas gdy w tle wykonywana jest metoda. To kluczowa zaleta: zmiana implementacji (np. dodanie cache'owania lub logowania) nie wymaga zmiany kodu klienckiego.

Gdybysmy zamiast property uzyli zwykłej metody, wywołanie wygladaloby tak: cz.liczba_ksiazek() - z nawiasami. Property sprawia, ze interfejs jest czystszy, a właściwość może być używana w wyrazeniach tak samo jak zwykły atrybut.

Zauwaz, ze liczba_ksiazek jest obliczana dynamiczne za każdym razem, gdy ja odczytujemy. Jeśli lista wypożyczone ulegnie zmianie, property automatycznie zwróci nowa wartość. Nie ma ryzyka, ze wartość będzie nieaktualna.

Slajd zatytułowany "Kod: @property dla liczby książek" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

24 Kod: tworzenie czytelników

Tworzenie obiektów Czytelnik

# Tworzymy czytelnikow
cz1 = Czytelnik("Anna", "Zielinska")
cz2 = Czytelnik("Piotr", "Nowak")
cz3 = Czytelnik("Kasia", "Wisniewska")

print(cz1)
# Czytelnik(imie='Anna', nazwisko='Zielinska', wypozyczone=[])

print(cz2.liczba_ksiazek)
# 0

Każdy czytelnik tworzony jest z pustą lista wypożyczonych książek. Stan zmienia się w trakcie działania systemu. Dzięki field(default_factory=list) każdy czytelnik ma własna, niezależna liste - dodanie książki do cz1.wypożyczone nie wpływa na cz2.wypożyczone .

Widać, ze __repr__ generowany przez @dataclass pokazuje wszystkie atrybuty, wlacznie z pustą lista. W miare dodawania książek lista będzie się wypełniać, a __repr__ automatycznie pokaże aktualny stan.

Tworzenie obiektu przez wywołanie klasy to jeden z najważniejszych wzorców w Pythonie. Składnia NazwaKlasy() może być myląca dla początkujących, ponieważ wygląda jak wywołanie funkcji, ale w rzeczywistości uruchamia złożony proces alokacji i inicjalizacji. Python, jako język dynamiczny, pozwala na tworzenie obiektów w dowolnym momencie działania programu, w tym wewnątrz pętli, warunków czy wyrażeń listowych. Każde wywołanie klasy tworzy nowy, niezależny obiekt w pamięci, nawet jeżeli klasa nie ma zdefiniowanego konstruktora. Zmienna przechowuje referencję do obiektu, a nie sam obiekt, co ma kluczowe znaczenie dla zrozumienia mechanizmu przypisań i kopiowania.

Proces tworzenia obiektu składa się z kilku etapów: najpierw wywoływana jest metoda __new__, która alokuje pamięć dla nowej instancji, następnie uruchamiany jest __init__ do inicjalizacji atrybutów, a na końcu zwracana jest referencja do gotowego obiektu. Większość programistów modyfikuje wyłącznie __init__, pozostawiając __new__ w domyślnej implementacji. Zrozumienie tej kolejności jest ważne przy tworzeniu bardziej zaawansowanych wzorców, takich jak Singleton czy Fabryka. Gdy tworzymy wiele obiektów w pętli, każdy z nich jest niezależną instancją z własnym stanem i tożsamością.

25 Kod: testowanie Czytelnik

Testówanie klasy Czytelnik

# Testowanie Czytelnik
cz = Czytelnik("Test", "Testowy")

assert cz.liczba_ksiazek == 0
assert isinstance(cz.wypozyczone, list)
assert len(cz.wypozyczone) == 0

# Dodajemy ksiazke do listy
ks = Ksiazka("Test", "Autor", 2000)
cz.wypozyczone.append(ks)
assert cz.liczba_ksiazek == 1
assert cz.wypozyczone[0] == ks

print("Test Czytelnik OK")

Test sprawdza podstawowe działanie klasy: początkowa liczbę książek (0), typ atrybutu wypożyczone (lista), dodanie książki i zaktualizowanie liczby. Asercje to najprostsza forma testowania - jeśli warunek jest fałszywy, Python rzuca AssertionError . W większych projektach zamiast assert używa się frameworków testówych jak pytest .

Test weryfikuje również, ze property liczba_ksiazek działa poprawnie - zwraca 0 dla pustej listy i 1 po dodaniu książki. To potwierdza, ze @property jest poprawnie zaimplementowane.

Testówanie Czytelnik

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

26 Wprowadzenie - własny wyjątek

Implementacja: BladBiblioteki

Własna klasa wyjątku pozwala na precyzyjne określenie, co poszlo nie tak w systemie. Dziedziczymy po Exception i dodajemy własne atrybuty: komunikat i kod błędu. Dzięki temu obsługa błędów jest bardziej wyrazista i łatwiejsza w utrzymaniu.

Dzięki własnemu wyjątkowi możemy łapać błędy specyficzne dla biblioteki, nie mieszajac ich z ogólnymi wyjątkami Pythona. W kodzie obsługi błędów wystarczy jeden blok except BladBiblioteki , aby złapać wszystkie błędy biznesowe, zamiast wielu bloków except dla różnych standardowych wyjątków.

Dodanie numerycznego kodu błędu to dodatkowe usprawnienie: pozwala na programistyczne rozróznienie rodzaju błędu bez potrzeby parsowania komunikatu tekstowego. To szczególnie ważne, gdy komunikat ma być wyświetlany użytkownikowi - kod błędu może być uzyty do wybrania odpowiedniego komunikatu w języku użytkownika.

Dobra praktyka: Własne wyjątki powinny miec suffix "Error" lub być opisowe. Nazwa BladBiblioteki jasno określa kontekst. Warto umieszczać własne wyjątki w osobnym module (np. exceptions.py ), szczególnie w większych projektach.
Własny wyjątek

Moduł ten stanowi pierwszą część kompleksowego kursu programowania obiektowego w języku Python. Jego celem jest wprowadzenie studentów w paradygmat obiektowy oraz przedstawienie fundamentalnych koncepcji, takich jak klasy, obiekty, atrybuty i metody. Materiał został zaprojektowany tak, aby stopniowo budować zrozumienie od prostych analogii do konkretnych implementacji w kodzie. Każdy slajd zawiera przykłady, które można samodzielnie przetestować w środowisku REPL. Zaleca się aktywne uczestnictwo poprzez modyfikowanie przykładów i wykonywanie ćwiczeń. Systematyczna praca z materiałem gwarantuje solidne opanowanie podstaw OOP. W kolejnych modułach wiedza ta będzie rozwijana o bardziej zaawansowane zagadnienia.

Programowanie obiektowe to nie tylko zestaw reguł składniowych, ale przede wszystkim sposób myślenia o problemach programistycznych. Kluczowe jest zrozumienie, że klasy służą do modelowania rzeczywistych bytów i relacji między nimi. Dzięki OOP kod staje się bardziej modularny, łatwiejszy w utrzymaniu i bardziej odporny na błędy. Współczesne aplikacje webowe, systemy bazodanowe i frameworki w dużym stopniu opierają się na paradygmacie obiektowym. Opanowanie OOP otwiera drzwi do zrozumienia zaawansowanych wzorców projektowych. Zachęcamy do cierpliwej i systematycznej nauki - każde nowe pojęcie będzie szczegółowo wyjaśnione i zilustrowane przykładami.

27 Kod: definicja BladBiblioteki

Definicja klasy BladBiblioteki

class BladBiblioteki(Exception):
    def __init__(self, komunikat: str, kod: int = 0):
        self.komunikat = komunikat
        self.kod = kod
        super().__init__(self.komunikat)

    def __str__(self):
        return f"[Blad {self.kod}] {self.komunikat}"

Klasa dziedziczy po Exception . W konstruktorze wywołujemy super().__init__ , aby komunikat byl dostępny poprzez standardowy mechanizm wyjątków - dzięki temu można użyć str(e) lub print(e) i otrzymac komunikat błędu.

Metoda __str__ dodaje kod błędu do wiadomosci. To metoda nadpisana z klasy bazowej, odpowiedźialna za tekstowa reprezentację wyjątku. Format [Błąd 1] Książka jest już wypożyczona jest czytelny zarówno dla programisty, jak i dla użytkownika.

Parametr kod ma domyślna wartość 0 , co oznacza "nieokreślony blad". W praktyce używamy konkretnych kodów: 1-4. Wartość domyślna pozwala na utworzenie wyjątku bez kodu w sytuacjach, gdy nie jest on potrzebny.

Programowanie obiektowe wywodzi się z języka Simula z lat 60. XX wieku, a jego rozwój przyspieszył w latach 70. wraz z językiem Smalltalk, który wprowadził wiele koncepcji używanych do dziś, takich jak klasy, metody i dziedziczenie. Współcześnie OOP jest wspierane przez większość popularnych języków programowania, choć każdy z nich implementuje ten paradygmat nieco inaczej. Python przyjął podejście pragmatyczne, w którym wszystko jest obiektem, ale programista nie jest zmuszany do obiektowego stylu. Dzięki temu Python jest językiem wieloparadygmatowym, łączącym OOP z programowaniem proceduralnym i funkcyjnym w elastyczny sposób. Zrozumienie genezy i ewolucji OOP pomaga docenić jego zalety i świadomie stosować go we własnych projektach programistycznych.

Cztery filary OOP - enkapsulacja, dziedziczenie, polimorfizm i abstrakcja - są ze sobą ściśle powiązane i wzajemnie się uzupełniają. Enkapsulacja chroni stan obiektu przed niekontrolowanym dostępem z zewnątrz, co zwiększa bezpieczeństwo i spójność danych. Dziedziczenie pozwala na budowanie hierarchii klas i wielokrotne wykorzystanie kodu bez jego kopiowania. Polimorfizm umożliwia jednolity interfejs dla różnych typów obiektów, co upraszcza projektowanie rozszerzalnych systemów. Abstrakcja koncentruje się na istotnych cechach, pomijając nieistotne szczegóły implementacyjne. Opanowanie tych czterech koncepcji stanowi fundament biegłości w OOP.

28 Kod: testowanie wyjątku

Testówanie BladBiblioteki

# Testowanie wyjatku
try:
    raise BladBiblioteki(
        "Ksiazka jest juz wypozyczona", kod=1
    )
except BladBiblioteki as e:
    print(e)
    # [Blad 1] Ksiazka jest juz wypozyczona
    print(e.kod)
    # 1
    print(e.komunikat)
    # Ksiazka jest juz wypozyczona

Własny wyjątek działa jak każdy inny - można go rzucić ( raise ) i złapać ( except ). Dodatkowe atrybuty pomagaja w diagnostyce. W praktyce wyjątek jest rzucany przez metody klasy Biblioteka , a lapiemy go w kodzie klienckim (np. w tescie lub skrypcie demonstracyjnym).

Zauwaz, ze print(e) wyświetla sformatowany komunikat dzięki metodzie __str__ . Bez niej standardowe zachowanie Exception po prostu wyświetliloby komunikat przekazany do konstruktora. Dzięki własnej implementacji __str__ otrzymujemy czytelny format z kodem błędu.

Ważne: zawsze używamy super().__init__(self.komunikat) , aby komunikat byl dostępny przez standardowe args wyjątku. Dzięki temu działają wszystkie standardowe mechanizmy, takie jak str(e) , repr(e) , czy logowanie błędów przez frameworki.

Testówanie wyjątku

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

29 Wprowadzenie - główna klasa

Implementacja: Biblioteka

Klasa Biblioteka jest sercem systemu. Zarządza lista książek i czytelników, udostępnia metody biznesowe: dodawanie, wypożyczanie, zwracanie i wyszukiwanie. To najważniejsza klasa z perspektywy logiki biznesowej - reszta klas to modele danych i mechanizmy pomocnicze.

To największa klasa w projekcie, ale wciaz zachowuje pojedynczą odpowiedzialność: zarządzanie operacjami bibliotecznymi. Zastosujemy:

  • Kompozycje - Bibliotekamaksiążki i czytelników (dwie listy jako atrybuty)
  • @classmethod - alternatywny konstruktor wczytujacy dane z CSV (wzorzec Factory Method)
  • Własny wyjątek BladBiblioteki - do zgłaszania błędów biznesowych w kontrolowany sposób

Klasa nie dziedziczy po żadnej klasie (poza domyslnym object ). Wszystkie jej metody są metodami instancji (przyjmują self ), z wyjątkiem from_csv , która jest metoda klasowa. To świadoma decyzja: metody instancji operuja na konkretnym stanie biblioteki, a metoda klasowa tworzy nową instancję.

Moduł ten stanowi pierwszą część kompleksowego kursu programowania obiektowego w języku Python. Jego celem jest wprowadzenie studentów w paradygmat obiektowy oraz przedstawienie fundamentalnych koncepcji, takich jak klasy, obiekty, atrybuty i metody. Materiał został zaprojektowany tak, aby stopniowo budować zrozumienie od prostych analogii do konkretnych implementacji w kodzie. Każdy slajd zawiera przykłady, które można samodzielnie przetestować w środowisku REPL. Zaleca się aktywne uczestnictwo poprzez modyfikowanie przykładów i wykonywanie ćwiczeń. Systematyczna praca z materiałem gwarantuje solidne opanowanie podstaw OOP. W kolejnych modułach wiedza ta będzie rozwijana o bardziej zaawansowane zagadnienia.

Programowanie obiektowe to nie tylko zestaw reguł składniowych, ale przede wszystkim sposób myślenia o problemach programistycznych. Kluczowe jest zrozumienie, że klasy służą do modelowania rzeczywistych bytów i relacji między nimi. Dzięki OOP kod staje się bardziej modularny, łatwiejszy w utrzymaniu i bardziej odporny na błędy. Współczesne aplikacje webowe, systemy bazodanowe i frameworki w dużym stopniu opierają się na paradygmacie obiektowym. Opanowanie OOP otwiera drzwi do zrozumienia zaawansowanych wzorców projektowych. Zachęcamy do cierpliwej i systematycznej nauki - każde nowe pojęcie będzie szczegółowo wyjaśnione i zilustrowane przykładami.

30 Kod: definicja Biblioteki (część 1)

Szkielet klasy Biblioteka

class Biblioteka:
    def __init__(self):
        self.ksiazki: list[Ksiazka] = []
        self.czytelnicy: list[Czytelnik] = []

    def dodaj_ksiazke(self, ksiazka: Ksiazka):
        # sprawdza duplikaty, dodaje do listy
        ...

    def wypozycz(self, tytul: str, czytelnik: Czytelnik):
        # wypozycza ksiazke jesli dostepna
        ...

    def zwroc(self, tytul: str, czytelnik: Czytelnik):
        # przyjmuje zwrot ksiazki
        ...

Konstruktor inicjalizuje puste listy. Metody zostaną zaimplementowane w kolejnych krokach. Adnotacje typów ( list[Książka] , list[Czytelnik] ) są opcjonalne, ale pomocne - poprawiaja czytelność i umożliwiają działanie narzedziom do analizy statycznej (np. mypy, pylance).

Trzy kropki ( ... ) w ciele metod to symbol zastępczy. W Pythonie ... (ellipsis) jest poprawnym wyrazeniem, które nic nie robi. To lepsze niż pass , bo sugeruje, ze implementacja zostanie uzupełniona później.

Zauwaz, ze konstruktor nie przyjmuje żadnych argumentów - biblioteka tworzona jest pusta, a następnie wypełniana danymi poprzez metodę dodaj_ksiazke lub alternatywny konstruktor from_csv .

Szkielet Biblioteki

Programowanie obiektowe wywodzi się z języka Simula z lat 60. XX wieku, a jego rozwój przyspieszył w latach 70. wraz z językiem Smalltalk, który wprowadził wiele koncepcji używanych do dziś, takich jak klasy, metody i dziedziczenie. Współcześnie OOP jest wspierane przez większość popularnych języków programowania, choć każdy z nich implementuje ten paradygmat nieco inaczej. Python przyjął podejście pragmatyczne, w którym wszystko jest obiektem, ale programista nie jest zmuszany do obiektowego stylu. Dzięki temu Python jest językiem wieloparadygmatowym, łączącym OOP z programowaniem proceduralnym i funkcyjnym w elastyczny sposób. Zrozumienie genezy i ewolucji OOP pomaga docenić jego zalety i świadomie stosować go we własnych projektach programistycznych.

Cztery filary OOP - enkapsulacja, dziedziczenie, polimorfizm i abstrakcja - są ze sobą ściśle powiązane i wzajemnie się uzupełniają. Enkapsulacja chroni stan obiektu przed niekontrolowanym dostępem z zewnątrz, co zwiększa bezpieczeństwo i spójność danych. Dziedziczenie pozwala na budowanie hierarchii klas i wielokrotne wykorzystanie kodu bez jego kopiowania. Polimorfizm umożliwia jednolity interfejs dla różnych typów obiektów, co upraszcza projektowanie rozszerzalnych systemów. Abstrakcja koncentruje się na istotnych cechach, pomijając nieistotne szczegóły implementacyjne. Opanowanie tych czterech koncepcji stanowi fundament biegłości w OOP.

31 Kod: metoda dodaj_ksiazke

Implementacja dodaj_ksiazke

def dodaj_ksiazke(self, ksiazka: Ksiazka):
    # Sprawdzamy czy ksiazka o tym tytule juz istnieje
    for k in self.ksiazki:
        if k.tytul.lower() == ksiazka.tytul.lower():
            raise BladBiblioteki(
                f"Ksiazka '{ksiazka.tytul}' juz istnieje",
                kod=3
            )
    self.ksiazki.append(ksiazka)

Metoda sprawdza, czy książka o takim samym tytule (bez wzgledu na wielkość liter) już istnieje w bibliotece. Jeśli tak, rzuca wyjątek BladBiblioteki z kodem 3 (duplikat). W przeciwnym razie dodaje książkę do listy.

Porównanie tytuł.lower() zapewnia, ze "Pan Tadeusz" i "pan tadeusz" są traktowane jako ten sam tytuł. To ważne z perspektywy użytkownika - nikt nie powinien być zmuszony do zapamiętywania dokładnej wielkości liter przy wpisywaniu tytułu.

Metoda nie sprawdza innych atrybutów (autora, roku) - jeśli inna książka ma ten sam tytuł, ale innego autora, nadal jest uznawana za duplikat. To założenie projektowe: tytuł jest unikalnym identyfikatorem książki w systemie. W realnym systemie bibliotecznym unikalnym identyfikatorem byloby raczej ISBN, ale dla uproszczenia używamy tytułu.

Metoda dodaj_ksiazke

Metody są funkcjami zdefiniowanymi w przestrzeni nazw klasy, które otrzymują automatycznie pierwszy argument w postaci referencji do obiektu. Dzięki temu mogą odczytywać i modyfikować stan obiektu, na którym zostały wywołane. W Pythonie istnieje kilka rodzajów metod: instancyjne (z self), klasowe (z cls i dekoratorem @classmethod), statyczne (z @staticmethod) oraz abstrakcyjne (z @abstractmethod w klasach ABC). Metody instancyjne są najczęściej używane i to one będą głównym tematem tej części kursu. Każda metoda instancyjna przyjmuje self jako pierwszy parametr, a Python automatycznie przekazuje obiekt w momencie wywołania przez notację kropkową. Metody mogą przyjmować dodatkowe argumenty, mieć wartości domyślne i zwracać wartości za pomocą instrukcji return.

Dobrą praktyką projektowania metod jest zasada pojedynczej odpowiedzialności - każda metoda powinna wykonywać jedną, dobrze określoną operację. Metody tylko do odczytu stanu, nazywane getterami, nie powinny modyfikować atrybutów obiektu. Metody modyfikujące stan, nazywane setterami, powinny walidować dane wejściowe przed wprowadzeniem zmian. Przestrzeganie tych zasad prowadzi do kodu łatwiejszego w testówaniu i debugowaniu. Warto również stosować metody pomocnicze, aby uniknąć powielania kodu wewnątrz klasy. Dobrze zaprojektowane metody sprawiają, że klasa jest intuicyjna w użyciu i trudna do niepoprawnego wykorzystania.

32Kod: metoda wypożycz

Implementacja wypożycz

def wypozycz(self, tytul: str, czytelnik: Czytelnik):
    # Szukamy ksiazki po tytule
    for ksiazka in self.ksiazki:
        if ksiazka.tytul.lower() == tytul.lower():
            if ksiazka.wypozyczona:
                raise BladBiblioteki(
                    f"Ksiazka '{tytul}' jest juz wypozyczona",
                    kod=1
                )
            ksiazka.wypozyczona = True
            czytelnik.wypozyczone.append(ksiazka)
            return
    raise BladBiblioteki(
        f"Nie znaleziono ksiazki '{tytul}'",
        kod=4
    )

Metoda wypożycz wykonuje dwie operację na raz: zmienia status książki i dodaje ja do listy wypożyczonych czytelnika. To transakcja logiczna - jeśli ktoras z tych operacji się nie powiedzie, żadna nie powinna być wykonana. W naszym przypadku obie operację są wykonywane dopiero po potwierdzeniu, ze książka istnieje i jest dostępną.

Jeśli książka istnieje i jest dostępną ( wypożyczona == False ), metoda: (1) ustawia wypożyczona = True i (2) dodaje książkę do listy czytelnika. Jeśli książka jest już wypożyczona, rzucamy BladBiblioteki z kodem 1. Jeśli książka nie istnieje, rzucamy z kodem 4.

Dzięki pętli for przeszukujemy wszystkie książki w bibliotece. Złożoność obliczeniowa to O(n) - dla małych zbiorów (kilkadziesiąt książek) jest to w pełni wystarczające. Dla większych zbiorów można użyć słownika z tytułem jako kluczem.

Metoda wypożycz

Metody są funkcjami zdefiniowanymi w przestrzeni nazw klasy, które otrzymują automatycznie pierwszy argument w postaci referencji do obiektu. Dzięki temu mogą odczytywać i modyfikować stan obiektu, na którym zostały wywołane. W Pythonie istnieje kilka rodzajów metod: instancyjne (z self), klasowe (z cls i dekoratorem @classmethod), statyczne (z @staticmethod) oraz abstrakcyjne (z @abstractmethod w klasach ABC). Metody instancyjne są najczęściej używane i to one będą głównym tematem tej części kursu. Każda metoda instancyjna przyjmuje self jako pierwszy parametr, a Python automatycznie przekazuje obiekt w momencie wywołania przez notację kropkową. Metody mogą przyjmować dodatkowe argumenty, mieć wartości domyślne i zwracać wartości za pomocą instrukcji return.

Dobrą praktyką projektowania metod jest zasada pojedynczej odpowiedzialności - każda metoda powinna wykonywać jedną, dobrze określoną operację. Metody tylko do odczytu stanu, nazywane getterami, nie powinny modyfikować atrybutów obiektu. Metody modyfikujące stan, nazywane setterami, powinny walidować dane wejściowe przed wprowadzeniem zmian. Przestrzeganie tych zasad prowadzi do kodu łatwiejszego w testówaniu i debugowaniu. Warto również stosować metody pomocnicze, aby uniknąć powielania kodu wewnątrz klasy. Dobrze zaprojektowane metody sprawiają, że klasa jest intuicyjna w użyciu i trudna do niepoprawnego wykorzystania.

33Kod: metoda zwroc

Implementacja zwroc

def zwroc(self, tytul: str, czytelnik: Czytelnik):
    for ksiazka in self.ksiazki:
        if ksiazka.tytul.lower() == tytul.lower():
            if ksiazka not in czytelnik.wypozyczone:
                raise BladBiblioteki(
                    f"Czytelnik nie wypozyczyl ksiazki '{tytul}'",
                    kod=2
                )
            ksiazka.wypozyczona = False
            czytelnik.wypozyczone.remove(ksiazka)
            return
    raise BladBiblioteki(
        f"Nie znaleziono ksiazki '{tytul}'",
        kod=4
    )

Analogicznie do wypożycz, ale sprawdza czy książka byla wypożyczona przez tego czytelnika. Warunek książka not in czytelnik.wypożyczone wykorzystuje metodę __eq__ wygenerowana przez @dataclass - porównuje obiekty po wartościach atrybutów.

Metoda zwroc jest ważna z perspektywy spójnosci danych: jeśli czytelnik nie wypożyczył danej książki, nie powinien jej zwracać. W realnym systemie bibliotecznym takie zabezpieczenie zapobiega sytuacjom, w których ktos zwraca książkę wypożyczona na konto innej osoby.

Po pomyslnym zwrocie książka staje się dostępną ( wypożyczona = False ) i jest usuwana z listy wypożyczonych czytelnika. Stan systemu jest aktualizowany w sposób atomowy - obie zmiany są wykonywane razem.

Metoda zwroc

Metody są funkcjami zdefiniowanymi w przestrzeni nazw klasy, które otrzymują automatycznie pierwszy argument w postaci referencji do obiektu. Dzięki temu mogą odczytywać i modyfikować stan obiektu, na którym zostały wywołane. W Pythonie istnieje kilka rodzajów metod: instancyjne (z self), klasowe (z cls i dekoratorem @classmethod), statyczne (z @staticmethod) oraz abstrakcyjne (z @abstractmethod w klasach ABC). Metody instancyjne są najczęściej używane i to one będą głównym tematem tej części kursu. Każda metoda instancyjna przyjmuje self jako pierwszy parametr, a Python automatycznie przekazuje obiekt w momencie wywołania przez notację kropkową. Metody mogą przyjmować dodatkowe argumenty, mieć wartości domyślne i zwracać wartości za pomocą instrukcji return.

Dobrą praktyką projektowania metod jest zasada pojedynczej odpowiedzialności - każda metoda powinna wykonywać jedną, dobrze określoną operację. Metody tylko do odczytu stanu, nazywane getterami, nie powinny modyfikować atrybutów obiektu. Metody modyfikujące stan, nazywane setterami, powinny walidować dane wejściowe przed wprowadzeniem zmian. Przestrzeganie tych zasad prowadzi do kodu łatwiejszego w testówaniu i debugowaniu. Warto również stosować metody pomocnicze, aby uniknąć powielania kodu wewnątrz klasy. Dobrze zaprojektowane metody sprawiają, że klasa jest intuicyjna w użyciu i trudna do niepoprawnego wykorzystania.

34 Kod: @classmethod from_csv (alternatywny konstruktor)

Alternatywny konstruktor from_csv

@classmethod
def from_csv(cls, sciezka: str):
    biblioteka = cls()
    try:
        with open(sciezka, 'r', encoding='utf-8') as f:
            next(f)  # pomijamy naglowek
            for linia in f:
                linia = linia.strip()
                if not linia:
                    continue
                tytul, autor, rok = linia.split(',')
                ksiazka = Ksiazka(
                    tytul.strip(),
                    autor.strip(),
                    int(rok.strip())
                )
                biblioteka.dodaj_ksiazke(ksiazka)
    except FileNotFoundError:
        raise BladBiblioteki(
            f"Nie znaleziono pliku '{sciezka}'", kod=4
        )
    return biblioteka

@classmethod przyjmuje cls (klasę) zamiast self i zwraca nowa instancję biblioteki wypełniona danymi z pliku CSV. To realizacja wzorca Factory Method - metoda klasowa tworzy obiekt w alternatywny sposób, nie używając bezpośrednio konstruktora.

Format pliku CSV jest prosty: pierwsza linia to naglowek (pomijany przez next(f) ), a każda następna linia zawiera trzy pola oddzielone przecinkami: tytuł, autor, rok. Przykładowo:

tytul,autor,rok
Pan Tadeusz,Adam Mickiewicz,1834
Lalka,Boleslaw Prus,1890

Użycie encoding='utf-8' jest kluczowe dla poprawnej obsługi polskich znaków w pliku. Błąd FileNotFoundError jest lapiety i przekształcany na własny wyjątek BladBiblioteki z kodem 4, co zapewnia spójna obsługę błędów w całym systemie.

Classmethod from_csv

Atrybuty są podstawowym mechanizmem przechowywania stanu obiektu i mogą zawierać dane dowolnego typu dostępnego w Pythonie. Każdy obiekt ma własną, niezależną kopię atrybutów zdefiniowanych w konstruktorze, co oznacza, że zmiana wartości w jednej instancji nie wpływa na pozostałe. W Pythonie atrybuty są przechowywane w słowniku __dict__ każdego obiektu, co umożliwia dynamiczne dodawanie i usuwanie atrybutów w czasie wykonania. Chociaż dynamiczne dodawanie atrybutów jest możliwe, w praktyce zaleca się definiowanie wszystkich atrybutów w konstruktorze __init__ dla zachowania przewidywalności kodu. Taki nawyk sprawia, że strukturą obiektu jest jasna dla każdego programisty czytającego definicję klasy. Ponadto wiele narzędzi do statycznej analizy kodu wymaga jawnego deklarowania atrybutów.

Konstruktor __init__ jest wywoływany automatycznie po utworzeniu obiektu przez metodę __new__, która alokuje pamięć dla nowej instancji. Zadaniem __init__ jest przygotowanie obiektu do użycia poprzez ustawienie początkowych wartości atrybutów i wykonanie ewentualnych czynności inicjalizacyjnych. W przeciwieństwie do niektórych innych języków, Python nie wspiera przeciążania konstruktorów - zamiast tego stosuje się parametry opcjonalne z wartościami domyślnymi oraz wzorzec fabryki. Parametr self, obowiązkowy w każdej metodzie instancyjnej, jest referencją do konkretnego obiektu, na którym wywołano metodę. Dzięki self metoda ma dostęp do wszystkich atrybutów i innych metod danego obiektu.

35 Kod: testowanie Biblioteki

Testówanie podstawowych metod Biblioteki

# Test Biblioteki
bibl = Biblioteka()
ks = Ksiazka("Test", "Autor", 2000)
bibl.dodaj_ksiazke(ks)
assert len(bibl.ksiazki) == 1

# Duplikat powinien rzucic wyjatek
try:
    bibl.dodaj_ksiazke(ks)
    assert False, "Powinien byc blad"
except BladBiblioteki as e:
    assert e.kod == 3

print("Test Biblioteka podstawy OK")

Test sprawdza dwie rzeczy: (1) czy dodanie książki zwiększa liczbę książek w bibliotece, (2) czy próba dodania duplikatu rzuca wyjątek z kodem 3. Konstrukcja try/except z assert False w bloku try to standardowa technika testowania, czy wyjątek został rzucony - jeśli nie, asercja nie przejdzie.

Warto zauwazyc, ze test używa tego samego obiektu ks do próby dodania duplikatu. To oznacza, ze próbujemy dodać dokładnie ten sam obiekt dwa razy. Metoda dodaj_ksiazke porównuje tytuly, więc wykryje duplikat niezależnie od tego, czy to ten sam obiekt, czy inny obiekt z tym samym tytułem.

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

36 Wprowadzenie - łączenie wszystkiego

Łączenie klas w działający system

Mamy zdefiniowane wszystkie klasy. Teraz łączymy je w jeden działający system. Poniższe slajdy pokazują kompletny przepływ danych: tworzenie obiektów, wypożyczanie, zwracanie i obsługę błędów. To najważniejszy moment projektu - widzimy, jak poszczególne klasy wspólpracuja ze sobą w praktyce.

Przepływ danych jest następujący: tworzymy książki i czytelników (dane), dodajemy je do biblioteki (kompozycja), następnie wykonujemy operacje biznesowe (wypożyczanie, zwracanie) z obsługa błędów (własne wyjątki). Każda klasa pełni określona role, a całość tworzy spójny system.

Scenariusz: Tworzymy bibliotekę z kilkoma ksiazkami, dodajemy czytelników, wypożyczamy i zwracamy książki, testujemy błędy. To realistyczny scenariusz, który móglby miec miejsce w prawdziwej bibliotece.

Każdy krok jest pokazany w osobnym bloku kodu z objaśnieniami. Możesz uruchomić wszystkie kroki po kolei w jednym skrypcie Python, aby zobaczyć działający system.

Łączenie klas

Moduł ten stanowi pierwszą część kompleksowego kursu programowania obiektowego w języku Python. Jego celem jest wprowadzenie studentów w paradygmat obiektowy oraz przedstawienie fundamentalnych koncepcji, takich jak klasy, obiekty, atrybuty i metody. Materiał został zaprojektowany tak, aby stopniowo budować zrozumienie od prostych analogii do konkretnych implementacji w kodzie. Każdy slajd zawiera przykłady, które można samodzielnie przetestować w środowisku REPL. Zaleca się aktywne uczestnictwo poprzez modyfikowanie przykładów i wykonywanie ćwiczeń. Systematyczna praca z materiałem gwarantuje solidne opanowanie podstaw OOP. W kolejnych modułach wiedza ta będzie rozwijana o bardziej zaawansowane zagadnienia.

Programowanie obiektowe to nie tylko zestaw reguł składniowych, ale przede wszystkim sposób myślenia o problemach programistycznych. Kluczowe jest zrozumienie, że klasy służą do modelowania rzeczywistych bytów i relacji między nimi. Dzięki OOP kod staje się bardziej modularny, łatwiejszy w utrzymaniu i bardziej odporny na błędy. Współczesne aplikacje webowe, systemy bazodanowe i frameworki w dużym stopniu opierają się na paradygmacie obiektowym. Opanowanie OOP otwiera drzwi do zrozumienia zaawansowanych wzorców projektowych. Zachęcamy do cierpliwej i systematycznej nauki - każde nowe pojęcie będzie szczegółowo wyjaśnione i zilustrowane przykładami.

37Kod: tworzenie książek

Krok 1: Tworzymy książki

Rozpoczynamy od przygotowania danych - tworzymy liste obiektów Książka reprezentujących klasyke polskiej literatury. To zbiór danych, który będzie wypełniać nasza bibliotekę.

# Przygotowujemy zbior ksiazek
ksiazki = [
    Ksiazka("Pan Tadeusz", "Adam Mickiewicz", 1834),
    Ksiazka("Lalka", "Boleslaw Prus", 1890),
    Ksiazka("Dziady", "Adam Mickiewicz", 1823),
    Ksiazka("Ferdydurke", "Witold Gombrowicz", 1937),
    Ksiazka("Solaris", "Stanislaw Lem", 1961),
    Ksiazka("Quo Vadis", "Henryk Sienkiewicz", 1896),
    Ksiazka("Przedwiosnie", "Stefan Zeromski", 1924),
]
print(f"Przygotowano {len(ksiazki)} ksiazek")
# Przygotowano 7 ksiazek

Każda książka jest tworzona z tytułem, autorem i rokiem wydania. Wszystkie są domyślnie dostępne ( wypożyczona=False ). Lista zawiera 7 książek, które następnie dodamy do biblioteki. Używamy składni listy (lista literatów), co jest czytelne i zwarte.

Tworzenie książek

Tworzenie obiektu przez wywołanie klasy to jeden z najważniejszych wzorców w Pythonie. Składnia NazwaKlasy() może być myląca dla początkujących, ponieważ wygląda jak wywołanie funkcji, ale w rzeczywistości uruchamia złożony proces alokacji i inicjalizacji. Python, jako język dynamiczny, pozwala na tworzenie obiektów w dowolnym momencie działania programu, w tym wewnątrz pętli, warunków czy wyrażeń listowych. Każde wywołanie klasy tworzy nowy, niezależny obiekt w pamięci, nawet jeżeli klasa nie ma zdefiniowanego konstruktora. Zmienna przechowuje referencję do obiektu, a nie sam obiekt, co ma kluczowe znaczenie dla zrozumienia mechanizmu przypisań i kopiowania.

Proces tworzenia obiektu składa się z kilku etapów: najpierw wywoływana jest metoda __new__, która alokuje pamięć dla nowej instancji, następnie uruchamiany jest __init__ do inicjalizacji atrybutów, a na końcu zwracana jest referencja do gotowego obiektu. Większość programistów modyfikuje wyłącznie __init__, pozostawiając __new__ w domyślnej implementacji. Zrozumienie tej kolejności jest ważne przy tworzeniu bardziej zaawansowanych wzorców, takich jak Singleton czy Fabryka. Gdy tworzymy wiele obiektów w pętli, każdy z nich jest niezależną instancją z własnym stanem i tożsamością.

38 Kod: tworzenie czytelników

Krok 2: Tworzymy czytelników

Tworzymy liste czytelników - użytkowników systemu bibliotecznego. Każdy czytelnik ma imie, nazwisko i pusta liste wypożyczonych książek. Property liczba_ksiazek pokazuje początkowy stan: 0.

# Przygotowujemy czytelnikow
czytelnicy = [
    Czytelnik("Anna", "Zielinska"),
    Czytelnik("Piotr", "Nowak"),
    Czytelnik("Kasia", "Wisniewska"),
    Czytelnik("Marek", "Kowalczyk"),
]
print(f"Przygotowano {len(czytelnicy)} czytelnikow")
# Przygotowano 4 czytelnikow

for cz in czytelnicy:
    print(f"  {cz.imie} {cz.nazwisko} (ksiazek: {cz.liczba_ksiazek})")

Petla for wyświetla liste wszystkich czytelników wraz z liczba wypożyczonych książek (na razie wszyscy mają 0). W miare postepu skryptu liczby te beda się zmieniac. To pokazuje, że property liczba_ksiazek jest wartością dynamiczna - zmienia się, gdy modyfikujemy liste wypożyczone .

Tworzenie obiektu przez wywołanie klasy to jeden z najważniejszych wzorców w Pythonie. Składnia NazwaKlasy() może być myląca dla początkujących, ponieważ wygląda jak wywołanie funkcji, ale w rzeczywistości uruchamia złożony proces alokacji i inicjalizacji. Python, jako język dynamiczny, pozwala na tworzenie obiektów w dowolnym momencie działania programu, w tym wewnątrz pętli, warunków czy wyrażeń listowych. Każde wywołanie klasy tworzy nowy, niezależny obiekt w pamięci, nawet jeżeli klasa nie ma zdefiniowanego konstruktora. Zmienna przechowuje referencję do obiektu, a nie sam obiekt, co ma kluczowe znaczenie dla zrozumienia mechanizmu przypisań i kopiowania.

Proces tworzenia obiektu składa się z kilku etapów: najpierw wywoływana jest metoda __new__, która alokuje pamięć dla nowej instancji, następnie uruchamiany jest __init__ do inicjalizacji atrybutów, a na końcu zwracana jest referencja do gotowego obiektu. Większość programistów modyfikuje wyłącznie __init__, pozostawiając __new__ w domyślnej implementacji. Zrozumienie tej kolejności jest ważne przy tworzeniu bardziej zaawansowanych wzorców, takich jak Singleton czy Fabryka. Gdy tworzymy wiele obiektów w pętli, każdy z nich jest niezależną instancją z własnym stanem i tożsamością.

39 Kod: tworzenie biblioteki

Krok 3: Tworzymy bibliotekę

Tworzymy glówny obiekt systemu - instancję klasy Biblioteka . Na początku biblioteka jest pusta: nie ma książek ani czytelników. To naturalny stan początkowy - dane beda dodawane w kolejnych krokach.

# Tworzymy instancje biblioteki
biblioteka = Biblioteka()
print("Utworzono biblioteke")
print(f"  Liczba ksiazek: {len(biblioteka.ksiazki)}")
print(f"  Liczba czytelnikow: {len(biblioteka.czytelnicy)}")
# Utworzono biblioteke
#   Liczba ksiazek: 0
#   Liczba czytelnikow: 0

W tym momencie mamy oddzielnie: liste książek, liste czytelników i pusta bibliotekę. Żadna książka ani czytelnik nie są jeszcze powiązane z biblioteka - to zrobimy w kolejnych krokach, dodajac je do odpowiednich list w bibliotece.

Tworzenie biblioteki

Tworzenie obiektu przez wywołanie klasy to jeden z najważniejszych wzorców w Pythonie. Składnia NazwaKlasy() może być myląca dla początkujących, ponieważ wygląda jak wywołanie funkcji, ale w rzeczywistości uruchamia złożony proces alokacji i inicjalizacji. Python, jako język dynamiczny, pozwala na tworzenie obiektów w dowolnym momencie działania programu, w tym wewnątrz pętli, warunków czy wyrażeń listowych. Każde wywołanie klasy tworzy nowy, niezależny obiekt w pamięci, nawet jeżeli klasa nie ma zdefiniowanego konstruktora. Zmienna przechowuje referencję do obiektu, a nie sam obiekt, co ma kluczowe znaczenie dla zrozumienia mechanizmu przypisań i kopiowania.

Proces tworzenia obiektu składa się z kilku etapów: najpierw wywoływana jest metoda __new__, która alokuje pamięć dla nowej instancji, następnie uruchamiany jest __init__ do inicjalizacji atrybutów, a na końcu zwracana jest referencja do gotowego obiektu. Większość programistów modyfikuje wyłącznie __init__, pozostawiając __new__ w domyślnej implementacji. Zrozumienie tej kolejności jest ważne przy tworzeniu bardziej zaawansowanych wzorców, takich jak Singleton czy Fabryka. Gdy tworzymy wiele obiektów w pętli, każdy z nich jest niezależną instancją z własnym stanem i tożsamością.

40Kod: dodawanie książek

Krok 4: Dodajemy książki do biblioteki

Teraz wypełniamy bibliotekę danymi. Iterujemy przez liste książek i każda dodajemy do biblioteki. Używamy bloku try/except , aby obsłużyć ewentualne błędy duplikatów (chociaz w tym przypadku nie wystapia, bo każda książka ma unikalny tytul).

# Dodajemy wszystkie ksiazki do biblioteki
for ks in ksiazki:
    try:
        biblioteka.dodaj_ksiazke(ks)
        print(f"  Dodano: {ks.tytul}")
    except BladBiblioteki as e:
        print(f"  Blad: {e}")

print(f"\nLaczna liczba ksiazek: {len(biblioteka.ksiazki)}")
# Dodano: Pan Tadeusz
# Dodano: Lalka
# Dodano: Dziady
# Dodano: Ferdydurke
# Dodano: Solaris
# Dodano: Quo Vadis
# Dodano: Przedwiosnie
# Laczna liczba ksiazek: 7

Po dodaniu wszystkich książek biblioteka zawiera 7 pozycji. Każda książka jest dostępną (status wypożyczona=False ). To jest stan początkowy przed wykonaniem operacji wypożyczania.

Dodawanie książek

Slajd zatytułowany "Kod: dodawanie książek" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

41Kod: wypożyczanie

Krok 5: Wypożyczamy książki

Przechodzimy do sedna systemu - wypożyczania książek. Anna wypożycza jedna książkę, Piotr dwie. To pokazuje, że jeden czytelnik może wypożyczyć wiele książek jednocześnie.

# Anna wypozycza "Pan Tadeusz"
anna = czytelnicy[0]
biblioteka.wypozycz("Pan Tadeusz", anna)
print(f"Anna wypozyczyla: {[k.tytul for k in anna.wypozyczone]}")
# Anna wypozyczyla: ['Pan Tadeusz']

# Piotr wypozycza "Lalke" i "Solaris"
piotr = czytelnicy[1]
biblioteka.wypozycz("Lalka", piotr)
biblioteka.wypozycz("Solaris", piotr)
print(f"Piotr wypozyczyl: {[k.tytul for k in piotr.wypozyczone]}")
# Piotr wypozyczyl: ['Lalka', 'Solaris']

print(f"Piotr ma lacznie: {piotr.liczba_ksiazek} ksiazek")
# Piotr ma lacznie: 2 ksiazki

Po wykonaniu tych operacji stan systemu się zmienil: dwie książki są wypożyczone ("Pan Tadeusz" i "Lalka", "Solaris"), a pozostałe są dostępne. Wykorzystujemy liste skladana ( [k.tytuł for k in anna.wypożyczone] ) do czytelnego wyświetlenia tytulów wypożyczonych książek. Property liczba_ksiazek Piotra zwraca 2, potwierdzajac, ze mechanizm działa poprawnie.

Wypożyczanie

Slajd zatytułowany "Kod: wypożyczanie" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

42Kod: zwracanie

Krok 6: Zwracamy książki

Piotr zwraca "Lalkę". To odwrócenie operacji wypożyczania: książka staje się dostępną, a czytelnik przestaje ja posiadac. Sprawdzamy stan po zwrocie - lista wypożyczonych Piotra zawiera już tylko "Solaris".

# Piotr zwraca "Lalke"
biblioteka.zwroc("Lalka", piotr)
print(f"Piotr po zwrocie: {[k.tytul for k in piotr.wypozyczone]}")
# Piotr po zwrocie: ['Solaris']

print(f"Piotr ma teraz: {piotr.liczba_ksiazek} ksiazke")
# Piotr ma teraz: 1 ksiazke

# Sprawdzamy status ksiazki "Lalka"
for ks in biblioteka.ksiazki:
    if ks.tytul == "Lalka":
        print(f"Lalka dostepna: {not ks.wypozyczona}")
        # Lalka dostepna: True

Ważne: zwrot jest możliwy tylko dla książki, która została wypożyczona przez tego czytelnika. Próba zwrotu książki, której czytelnik nie wypożyczył (np. "Pan Tadeusz" przez Piotra), zakończy się błędem BladBiblioteki z kodem 2. To zabezpieczenie zapobiega niekontrolowanym zwrotom.

Zwracanie

Przekazywanie obiektów jako argumentów do funkcji i zwracanie ich z funkcji to podstawowe mechanizmy komunikacji między modułami programu. W Pythonie obiekty są przekazywane przez referencję, co oznacza, że funkcja otrzymuje dostęp do oryginalnego obiektu, a nie jego kopii. Ma to istotne konsekwencje dla projektowania - funkcja modyfikująca obiekt wpływa na stan widoczny w miejscu wywołania. Wzorzec fabryki polega na wydzieleniu logiki tworzenia obiektów do osobnej funkcji, co pozwala ukryć złożoność konstruktora i zapewnić centralne miejsce do zarządzania procesem tworzenia. Funkcje fabryczne są szczególnie przydatne przy walidacji danych, transformacji formatów i konfiguracji.

Funkcje operujące na obiektach przez ich atrybuty nie muszą znać wewnętrznej struktury klasy - to przejaw polimorfizmu i enkapsulacji. Jeśli funkcja modyfikuje przekazane obiekty, warto jasno dokumentować ten fakt w docstringu, aby uniknąć nieoczekiwanych skutków ubocznych. W podejściu funkcyjnym preferuje się tworzenie nowych obiektów zamiast modyfikacji istniejących, co prowadzi do kodu bardziej przewidywalnego. W OOP modyfikacja przez funkcję zewnętrzną jest akceptowalna, ale należy stosować ją z umiarem. Dobrze zaprojektowana fabryka może znacząco uprościć kod kliencki i zwiększyć jego czytelność.

43Kod: obsługa błędów

Krok 7: Testówanie obsługi błędów

Testujemy dwa scenariusze błędów: próba wypożyczenia już wypożyczonej książki (kod 1) i próba zwrotu książki, której czytelnik nie wypożyczył (kod 2). W obu przypadkach system rzuca BladBiblioteki z odpowiednim kodem.

# Blad 1: Ksiazka juz wypozyczona
try:
    biblioteka.wypozycz("Pan Tadeusz", piotr)
except BladBiblioteki as e:
    print(f"Blad {e.kod}: {e.komunikat}")
# Blad 1: Ksiazka 'Pan Tadeusz' jest juz wypozyczona

# Blad 2: Zwrot nie swojej ksiazki
try:
    biblioteka.zwroc("Pan Tadeusz", piotr)
except BladBiblioteki as e:
    print(f"Blad {e.kod}: {e.komunikat}")
# Blad 2: Czytelnik nie wypozyczyl ksiazki 'Pan Tadeusz'

Błąd 1: Piotr próbuje wypożyczyć "Pan Tadeusz", ale książka jest już wypożyczona przez Anne. System odrzuca operację z kodem 1. Błąd 2: Piotr próbuje zwrócić "Pan Tadeusz", ale nie on wypożyczył te książkę - zrobiła to Anna. System odrzuca operację z kodem 2. W obu przypadkach kod błędu pozwala programiście zidentyfikowac rodzaj problemu bez parsowania komunikatu tekstowego.

Obsługa błędów

Slajd zatytułowany "Kod: obsługa błędów" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

44 Kod: wyświetlanie stanu

Krok 8: Wyświetlanie stanu systemu

Funkcja pomocnicza do wizualizacji stanu biblioteki. To narzędzie przydatne podczas debugowania i demonstracji. Pokazuje wszystkie książki (z oznaczeniem [X] dla wypożyczonych) oraz wszystkich czytelników z ich wypożyczeniami.

def wyswietl_stan(bibl: Biblioteka):
    print("=" * 50)
    print("STAN BIBLIOTEKI")
    print("=" * 50)
    print(f"\nKsiazki ({len(bibl.ksiazki)}):")
    for ks in bibl.ksiazki:
        status = "[X]" if ks.wypozyczona else "[ ]"
        print(f"  {status} {ks.tytul} ({ks.autor}, {ks.rok})")
    print(f"\nCzytelnicy ({len(bibl.czytelnicy)}):")
    for cz in bibl.czytelnicy:
        tytuly = [k.tytul for k in cz.wypozyczone]
        print(f"  {cz.imie} {cz.nazwisko}: {tytuly}")
    print("=" * 50)

Funkcja nie jest metoda klasy Biblioteka , ale samodzielna funkcją przyjmujaca obiekt biblioteki jako argument. To świadoma decyzja - oddzielamy logikę wyświetlania od logiki biznesowej. W praktyce takie funkcję często trafiaja do osobnego modułu odpowiedźialnego za prezentacje (np. ui.py ).

Wyświetlanie stanu

Atrybuty są podstawowym mechanizmem przechowywania stanu obiektu i mogą zawierać dane dowolnego typu dostępnego w Pythonie. Każdy obiekt ma własną, niezależną kopię atrybutów zdefiniowanych w konstruktorze, co oznacza, że zmiana wartości w jednej instancji nie wpływa na pozostałe. W Pythonie atrybuty są przechowywane w słowniku __dict__ każdego obiektu, co umożliwia dynamiczne dodawanie i usuwanie atrybutów w czasie wykonania. Chociaż dynamiczne dodawanie atrybutów jest możliwe, w praktyce zaleca się definiowanie wszystkich atrybutów w konstruktorze __init__ dla zachowania przewidywalności kodu. Taki nawyk sprawia, że strukturą obiektu jest jasna dla każdego programisty czytającego definicję klasy. Ponadto wiele narzędzi do statycznej analizy kodu wymaga jawnego deklarowania atrybutów.

Konstruktor __init__ jest wywoływany automatycznie po utworzeniu obiektu przez metodę __new__, która alokuje pamięć dla nowej instancji. Zadaniem __init__ jest przygotowanie obiektu do użycia poprzez ustawienie początkowych wartości atrybutów i wykonanie ewentualnych czynności inicjalizacyjnych. W przeciwieństwie do niektórych innych języków, Python nie wspiera przeciążania konstruktorów - zamiast tego stosuje się parametry opcjonalne z wartościami domyślnymi oraz wzorzec fabryki. Parametr self, obowiązkowy w każdej metodzie instancyjnej, jest referencją do konkretnego obiektu, na którym wywołano metodę. Dzięki self metoda ma dostęp do wszystkich atrybutów i innych metod danego obiektu.

45Kod: kompletny skrypt

Uruchomienie kompletnego systemu

Po wykonaniu wszystkich kroków (dodanie książek, wypożyczenie, zwrot) wyświetlamy końcowy stan systemu. Widzimy, które książki są wypożyczone [X], a które dostępne [ ]. Listy czytelników pokazują, kto co wypożyczył.

# Uruchamiamy caly system
wyswietl_stan(biblioteka)

# Wyswietli:
# ==================================================
# STAN BIBLIOTEKI
# ==================================================
#
# Ksiazki (7):
#   [X] Pan Tadeusz (Adam Mickiewicz, 1834)
#   [ ] Lalka (Boleslaw Prus, 1890)
#   [ ] Dziady (Adam Mickiewicz, 1823)
#   [ ] Ferdydurke (Witold Gombrowicz, 1937)
#   [X] Solaris (Stanislaw Lem, 1961)
#   [ ] Quo Vadis (Henryk Sienkiewicz, 1896)
#   [ ] Przedwiosnie (Stefan Zeromski, 1924)
#
# Czytelnicy (4):
#   Anna Zielinska: ['Pan Tadeusz']
#   Piotr Nowak: ['Solaris']
#   Kasia Wisniewska: []
#   Marek Kowalczyk: []
# ==================================================

Z wyniku widać, ze: (1) Anna wypożyczyła "Pan Tadeusz", (2) Piotr wypożyczył "Solaris" (zwrócil "Lalkę"), (3) Kasia i Marek nie mają żadnych wypożyczeń, (4) "Lalka" jest dostępną po zwrocie. To potwierdza, ze wszystkie operacje biznesowe działają poprawnie, a stan systemu jest spójny.

Kompletny skrypt

Slajd zatytułowany "Kod: kompletny skrypt" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

46 Wprowadzenie - testowanie

Testówanie scenariuszy biznesowych

Po zaimplementowaniu systemu należy go przetestówac. Testujemy konkretne scenariusze - nie tylko pojedyncze metody, ale cały przepływ danych i obsługę błędów. Testy jednostkowe sprawdzaja każda klasę w izolacji, a testy integracyjne sprawdzaja wspólprace między klasami.

Każdy test sprawdza inna sytuację biznesową:

  • Wypożyczenie dostępnej książki - powinno się udac (scenariusz pozytywny)
  • Wypożyczenie niedostępnej książki - powinno rzucić wyjątek (scenariusz negatywny)
  • Zwrot nie swojej książki - błąd (walidacja uprawnien)
  • Dodanie duplikatu - błąd (walidacja unikalności)
  • Wczytywanie z CSV - alternatywny konstruktor (test integracyjny z systemem plików)

Każdy test jest niezależny - tworzy własna instancję biblioteki i nie polega na stanie pozostawionym przez inne testy. To podstawowa zasada dobrych testów: izolacja i powtarzalność.

Testówanie

Moduł ten stanowi pierwszą część kompleksowego kursu programowania obiektowego w języku Python. Jego celem jest wprowadzenie studentów w paradygmat obiektowy oraz przedstawienie fundamentalnych koncepcji, takich jak klasy, obiekty, atrybuty i metody. Materiał został zaprojektowany tak, aby stopniowo budować zrozumienie od prostych analogii do konkretnych implementacji w kodzie. Każdy slajd zawiera przykłady, które można samodzielnie przetestować w środowisku REPL. Zaleca się aktywne uczestnictwo poprzez modyfikowanie przykładów i wykonywanie ćwiczeń. Systematyczna praca z materiałem gwarantuje solidne opanowanie podstaw OOP. W kolejnych modułach wiedza ta będzie rozwijana o bardziej zaawansowane zagadnienia.

Programowanie obiektowe to nie tylko zestaw reguł składniowych, ale przede wszystkim sposób myślenia o problemach programistycznych. Kluczowe jest zrozumienie, że klasy służą do modelowania rzeczywistych bytów i relacji między nimi. Dzięki OOP kod staje się bardziej modularny, łatwiejszy w utrzymaniu i bardziej odporny na błędy. Współczesne aplikacje webowe, systemy bazodanowe i frameworki w dużym stopniu opierają się na paradygmacie obiektowym. Opanowanie OOP otwiera drzwi do zrozumienia zaawansowanych wzorców projektowych. Zachęcamy do cierpliwej i systematycznej nauki - każde nowe pojęcie będzie szczegółowo wyjaśnione i zilustrowane przykładami.

47 Kod: test wypożyczenia dostępnej książki

Test 1: Wypożyczenie dostępnej książki

Pierwszy test sprawdza scenariusz pozytywny - wypożyczenie dostępnej książki. Tworzymy nowa bibliotekę, dodajemy książkę, tworzymy czytelnika i wypożyczamy. Asercje sprawdzaja, czy stan obiektów został poprawnie zaktualizowany.

def test_wypozycz_dostepna():
    bibl = Biblioteka()
    ks = Ksiazka("Test123", "Autor", 2000)
    bibl.dodaj_ksiazke(ks)
    cz = Czytelnik("Jan", "Test")

    bibl.wypozycz("Test123", cz)

    assert ks.wypozyczona == True
    assert ks in cz.wypozyczone
    assert cz.liczba_ksiazek == 1
    print("test_wypozycz_dostepna: OK")

test_wypozycz_dostepna()
# test_wypozycz_dostepna: OK

Test weryfikuje trzy rzeczy: (1) atrybut wypożyczona książki został ustawiony na True , (2) książka znajduje się na liscie wypożyczonych czytelnika, (3) property liczba_ksiazek zwraca 1. To podstawowe asercje, które potwierdzaja poprawność działania metody wypożycz .

Test wypożyczenia

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

48 Kod: test wypożyczenia niedostępnej książki

Test 2: Wypożyczenie niedostępnej książki

Scenariusz negatywny - próba wypożyczenia książki, która jest już wypożyczona przez inną osobe. Oczekujemy, ze system rzuci wyjątek BladBiblioteki z kodem 1.

def test_wypozycz_niedostepna():
    bibl = Biblioteka()
    ks = Ksiazka("Test123", "Autor", 2000)
    bibl.dodaj_ksiazke(ks)
    cz1 = Czytelnik("Jan", "A")
    cz2 = Czytelnik("Anna", "B")

    bibl.wypozycz("Test123", cz1)  # pierwsze wypozyczenie OK

    try:
        bibl.wypozycz("Test123", cz2)  # powinno byc BladBiblioteki
        assert False, "Powinien byc blad"
    except BladBiblioteki as e:
        assert e.kod == 1
    print("test_wypozycz_niedostepna: OK")

test_wypozycz_niedostepna()

Test używa dwóch czytelników: cz1 wypożycza książkę, a cz2 próbuje wypożyczyć tę samą. Linia assert False w bloku try to standardowa technika - jeśli wyjątek nie zostanie rzucony, asercja nie przejdzie i test zakończy się niepowodzeniem. Sprawdzamy również, czy kod błędu wynosi 1 (książka niedostępna).

Test niedostępnej

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

49 Kod: test zwrotu nie swojej książki

Test 3: Zwrot nie swojej książki

Test sprawdza, czy system nie pozwoli na zwrot książki przez osobe, która jej nie wypożyczyła. To ważne zabezpieczenie biznesowe - zapobiega sytuacjom, w których ktos zwraca książki wypożyczone na konto innych.

def test_zwrot_nie_swojej():
    bibl = Biblioteka()
    ks = Ksiazka("Test123", "Autor", 2000)
    bibl.dodaj_ksiazke(ks)
    cz1 = Czytelnik("Jan", "A")
    cz2 = Czytelnik("Anna", "B")

    bibl.wypozycz("Test123", cz1)

    try:
        bibl.zwroc("Test123", cz2)  # cz2 nie wypozyczyl tej ksiazki
        assert False, "Powinien byc blad"
    except BladBiblioteki as e:
        assert e.kod == 2
    print("test_zwrot_nie_swojej: OK")

test_zwrot_nie_swojej()

cz1 wypożycza książkę, a cz2 próbuje ja zwrócic. System powinien odrzucić taka operację, bo cz2 nie ma tej książki na liscie wypożyczonych. Test sprawdza kod błędu 2 (czytelnik nie ma takiej książki). To ważny test walidacji uprawnien - bez niego system pozwalalby na zwrot dowolnej książki przez dowolną osobe.

Test zwrotu

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

50 Kod: test dodawania duplikatu

Test 4: Dodanie duplikatu książki

Test sprawdza, czy system wykrywa duplikaty na podstawie tytułu. Używamy dwóch róznych obiektów Książka z tym samym tytułem, ale róznymi autorami (skrócona i pełna wersja). System powinien odrzucić duplikat, bo porównuje tylko tytuly.

def test_dodaj_duplikat():
    bibl = Biblioteka()
    ks1 = Ksiazka("Pan Tadeusz", "Mickiewicz", 1834)
    ks2 = Ksiazka("Pan Tadeusz", "Adam Mickiewicz", 1834)

    bibl.dodaj_ksiazke(ks1)

    try:
        bibl.dodaj_ksiazke(ks2)
        assert False, "Powinien byc blad duplikatu"
    except BladBiblioteki as e:
        assert e.kod == 3
        assert "juz istnieje" in e.komunikat
    print("test_dodaj_duplikat: OK")

test_dodaj_duplikat()

W teście używamy dwóch obiektów Książka z identycznym tytułem, ale rózniacych się autorem (jeden ma samo nazwisko, drugi pełne imie i nazwisko). To pokazuje, że walidacja duplikatów opiera się wyłącznie na tytule - zgodnie z założeniami projektowymi. Test sprawdza kod błędu 3 (duplikat) oraz czy komunikat zawiera slowo "już istnieje".

Test duplikatu

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

51 Kod: testowanie z @classmethod from_csv

Test 5: Wczytywanie z pliku CSV

Ostatni test sprawdza alternatywny konstruktor from_csv . Tworzymy tymczasowy plik CSV z dwoma ksiazkami, wczytujemy go i sprawdzamy, czy biblioteka została poprawnie wypełniona. Po teście usuwamy plik tymczasowy.

# Przykladowy plik books.csv:
# tytul,autor,rok
# Pan Tadeusz,Adam Mickiewicz,1834
# Lalka,Boleslaw Prus,1890

def test_from_csv():
    # Tworzymy tymczasowy plik CSV
    with open("test_books.csv", "w", encoding="utf-8") as f:
        f.write("tytul,autor,rok\n")
        f.write("Pan Tadeusz,Adam Mickiewicz,1834\n")
        f.write("Lalka,Boleslaw Prus,1890\n")

    bibl = Biblioteka.from_csv("test_books.csv")
    assert len(bibl.ksiazki) == 2
    assert bibl.ksiazki[0].tytul == "Pan Tadeusz"

    import os
    os.remove("test_books.csv")
    print("test_from_csv: OK")

test_from_csv()

To test integracyjny - korzysta z systemu plików (tworzy i usuwa plik). W większych projektach używa się tymczasowych plików z modułu tempfile lub biblioteki pytest z fixture. Test weryfikuje, ze @classmethod działa poprawnie i ze dane z CSV są prawidłowo wczytywane. Użycie encoding='utf-8' zapewnia obsługę polskich znaków.

Test from_csv

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

52 Nowa funkcja: wyszukiwanie książek

Rozszerzenie: wyszukiwanie książek

Dodajemy nowa funkcjonalność - możliwość wyszukiwania książek po fragmencie tytułu lub nazwiska autora. To rozszerza system bez naruszania istniejących metod (zasada otwarte-zamknięte). Metoda szukaj_ksiazki jest dodatkiem do klasy Biblioteka , który nie wymaga modyfikacji żadnej innej klasy.

Metoda szukaj_ksiazki przyjmuje fraze i przeszukuje liste książek. Zwraca liste pasujących wyników (może być pusta). Wyszukiwanie jest nieczułe na wielkość liter - dzięki lower() fraza "MICKIEWICZ" znajdzie te same książki co "mickiewicz".

To przykład rozszerzania systemu bez modyfikacji istniejącego kodu (zasada OCP - Open/Closed Principle). Istniejace metody dodaj_ksiazke , wypożycz i zwroc pozostaja niezmienione, a nowa funkcjonalność jest dodana jako nowa metoda.

Wyszukiwanie książek

Przekazywanie obiektów jako argumentów do funkcji i zwracanie ich z funkcji to podstawowe mechanizmy komunikacji między modułami programu. W Pythonie obiekty są przekazywane przez referencję, co oznacza, że funkcja otrzymuje dostęp do oryginalnego obiektu, a nie jego kopii. Ma to istotne konsekwencje dla projektowania - funkcja modyfikująca obiekt wpływa na stan widoczny w miejscu wywołania. Wzorzec fabryki polega na wydzieleniu logiki tworzenia obiektów do osobnej funkcji, co pozwala ukryć złożoność konstruktora i zapewnić centralne miejsce do zarządzania procesem tworzenia. Funkcje fabryczne są szczególnie przydatne przy walidacji danych, transformacji formatów i konfiguracji.

Funkcje operujące na obiektach przez ich atrybuty nie muszą znać wewnętrznej struktury klasy - to przejaw polimorfizmu i enkapsulacji. Jeśli funkcja modyfikuje przekazane obiekty, warto jasno dokumentować ten fakt w docstringu, aby uniknąć nieoczekiwanych skutków ubocznych. W podejściu funkcyjnym preferuje się tworzenie nowych obiektów zamiast modyfikacji istniejących, co prowadzi do kodu bardziej przewidywalnego. W OOP modyfikacja przez funkcję zewnętrzną jest akceptowalna, ale należy stosować ją z umiarem. Dobrze zaprojektowana fabryka może znacząco uprościć kod kliencki i zwiększyć jego czytelność.

53 Kod: metoda szukaj_ksiazki

Implementacja szukaj_ksiazki

Metoda przeszukuje liste książek i zwraca wszystkie, których tytuł lub autor zawiera podana fraze. Używa operatora in do sprawdzenia, czy fraza jest częścią napisu. Wyszukiwanie jest nieczułe na wielkość liter.

def szukaj_ksiazki(self, fraza: str):
    fraza = fraza.lower()
    wyniki = []
    for ks in self.ksiazki:
        if (fraza in ks.tytul.lower()
                or fraza in ks.autor.lower()):
            wyniki.append(ks)
    return wyniki

Metoda jest prosta, ale skuteczna. Przeszukuje zarowno tytuł, jak i autora. Zwraca liste wszystkich pasujących książek (może być pusta). Złożoność obliczeniowa to O(n) - dla naszego zbioru 7 książek to natychmiastowe.

Przykład użycia:

wyniki = biblioteka.szukaj_ksiazki("mickiewicz")
for ks in wyniki:
    print(f"{ks.tytul} - {ks.autor}")
# Pan Tadeusz - Adam Mickiewicz
# Dziady - Adam Mickiewicz

Wynik pokazuje dwie książki Adama Mickiewicza. Gdybysmy wpisali "ta", wynik zawieralby "Pan Tadeusz" (bo "ta" występuje w "Tadeusz"). To wyszukiwanie częśćiowe (substring), a nie pełne dopasowanie - bardziej elastyczne dla użytkownika.

Szukaj książki

Metody są funkcjami zdefiniowanymi w przestrzeni nazw klasy, które otrzymują automatycznie pierwszy argument w postaci referencji do obiektu. Dzięki temu mogą odczytywać i modyfikować stan obiektu, na którym zostały wywołane. W Pythonie istnieje kilka rodzajów metod: instancyjne (z self), klasowe (z cls i dekoratorem @classmethod), statyczne (z @staticmethod) oraz abstrakcyjne (z @abstractmethod w klasach ABC). Metody instancyjne są najczęściej używane i to one będą głównym tematem tej części kursu. Każda metoda instancyjna przyjmuje self jako pierwszy parametr, a Python automatycznie przekazuje obiekt w momencie wywołania przez notację kropkową. Metody mogą przyjmować dodatkowe argumenty, mieć wartości domyślne i zwracać wartości za pomocą instrukcji return.

Dobrą praktyką projektowania metod jest zasada pojedynczej odpowiedzialności - każda metoda powinna wykonywać jedną, dobrze określoną operację. Metody tylko do odczytu stanu, nazywane getterami, nie powinny modyfikować atrybutów obiektu. Metody modyfikujące stan, nazywane setterami, powinny walidować dane wejściowe przed wprowadzeniem zmian. Przestrzeganie tych zasad prowadzi do kodu łatwiejszego w testówaniu i debugowaniu. Warto również stosować metody pomocnicze, aby uniknąć powielania kodu wewnątrz klasy. Dobrze zaprojektowane metody sprawiają, że klasa jest intuicyjna w użyciu i trudna do niepoprawnego wykorzystania.

54Kod: test wyszukiwania

Testówanie wyszukiwania

Test sprawdza trzy scenariusze: wyszukiwanie po autorze (fraza "mickiewicz" zwraca 2 książki), wyszukiwanie po tytule (fraza "lalka" zwraca 1 książkę) oraz wyszukiwanie bez wyników (fraza "nieistniejąca" zwraca pusta liste).

def test_szukaj():
    bibl = Biblioteka()
    bibl.dodaj_ksiazke(Ksiazka("Pan Tadeusz", "Mickiewicz", 1834))
    bibl.dodaj_ksiazke(Ksiazka("Dziady", "Mickiewicz", 1823))
    bibl.dodaj_ksiazke(Ksiazka("Lalka", "Prus", 1890))

    # Szukaj po autorze
    wyn = bibl.szukaj_ksiazki("mickiewicz")
    assert len(wyn) == 2

    # Szukaj po tytule
    wyn = bibl.szukaj_ksiazki("lalka")
    assert len(wyn) == 1
    assert wyn[0].tytul == "Lalka"

    # Szukaj bez wynikow
    wyn = bibl.szukaj_ksiazki("nieistniejaca")
    assert len(wyn) == 0

    print("test_szukaj: OK")

test_szukaj()

Test weryfikuje, ze wyszukiwanie działa zarówno dla autora, jak i tytułu. Ważne: używamy frazy "mickiewicz" (mala litera), podczas gdy w danych autor zapisany jest z dużej ("Mickiewicz"). To potwierdza, ze wyszukiwanie jest nieczułe na wielkość liter. Test sprawdza również, ze brak wyników zwraca pusta liste, a nie np. None .

Test wyszukiwania

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

55Podsumowanie testów

Wyniki testów

Uruchamiamy wszystkie testy po kolei. Każdy test wypisuje "OK" po pomyslnym przejsciu. Jeśli wszystkie testy przejda, system jest gotowy. Testy pokrywaja zarówno scenariusze pozytywne, jak i negatywne, co daje pewnosc, ze system działa poprawnie w typowych sytuacjach i odpowiednio reaguje na błędy.

# Uruchomienie wszystkich testow:
test_wypozycz_dostepna()
test_wypozycz_niedostepna()
test_zwrot_nie_swojej()
test_dodaj_duplikat()
test_from_csv()
test_szukaj()

# Wynik:
# test_wypozycz_dostepna: OK
# test_wypozycz_niedostepna: OK
# test_zwrot_nie_swojej: OK
# test_dodaj_duplikat: OK
# test_from_csv: OK
# test_szukaj: OK
#
# Wszystkie testy przeszly pomyslnie!

Wszystkie scenariusze biznesowe działają poprawnie. System biblioteczny jest gotowy do użycia. Testy można uruchomić jako pojedynczy skrypt Python - jeśli wszystkie przejda, system działa poprawnie. To podstawa automatyzacji testów, która w większych projektach jest realizowana przez frameworki takie jak pytest czy unittest .

Podsumowanie testów

Podsumowanie to dobry moment na refleksję nad przyswojonym materiałem i identyfikację obszarów wymagających dodatkowej pracy. Wymienione punkty stanowią esencję przerobionej części kursu - od definicji klasy, przez tworzenie obiektów, aż po kompozycję i wzorzec fabryki. Każdy z tych punktów będzie rozwijany w kolejnych modułach, dlatego warto upewnić się, że są dobrze zrozumiane. Zachęcamy do tworzenia własnych notatek i map myśli, które pomagają w usystematyzowaniu wiedzy. Regularne powtórki są kluczowe dla trwałego zapamiętania materiału.

Mapy myśli są skutecznym narzędziem wizualizacji złożonych koncepcji i relacji między nimi. Przedstawiona mapa obrazuje najważniejsze pojęcia omówione w tej części kursu oraz ich wzajemne powiązania. Tworzenie własnych map myśli podczas nauki programowania aktywuje inne obszary mózgu niż czytanie liniowego tekstu, co przekłada się na lepsze zapamiętywanie. Studenci, którzy regularnie tworzą mapy myśli, osiągają lepsze wyniki w testach koncepcyjnych i szybciej łączą nowe informacje z już posiadaną wiedzą. Zachęcamy do wykorzystania tej techniki.

56 Czego się nauczyliśmy - przegląd 12 części

Podsumowanie kursu

Dwunastoczęśćiowy kurs OOP w Pythonie prowadzi od podstawowych koncepcji (klasy, obiekty) poprzez zaawansowane mechanizmy (dekoratory, wzorce projektowe), az po praktyczny projekt łączący wszystko w jedna całość. Oto przeglad każdej części:

  • Część 1: Wprowadzenie - klasy, obiekty, metody, atrybuty. Podstawy programowania obiektowego.
  • Część 2: Konstruktor __init__ , atrybuty instancji, self . Inicjalizacja obiektów.
  • Część 3: Metody, atrybuty klasowe, @classmethod , @staticmethod . Metody na poziomie klasy.
  • Część 4: Dziedziczenie, super() , wielodziedziczenie. Budowanie hierarchii klas.
  • Część 5: Hermetyzacja, _protected , __private . Ukrywanie implementacji.
  • Część 6: @property , gettery, settery, walidacja. Inteligentny dostęp do atrybutów.
  • Część 7: Polimorfizm, __str__ , __repr__ , __eq__ . Metody specjalne Pythona.
  • Część 8: Własne wyjątki, raise , try/except . Obsługa błędów w OOP.
  • Część 9: @dataclass , field , __post_init__ . Automatyczne generowanie kodu.
  • Część 10:Kompozycja, agregacja, zależności. Relacje między obiektami.
  • Część 11:Wzorce projektowe: Singleton, Factory, Obserwator. Sprawdzone rozwiązania.
  • Część 12:Mini-projekt - system biblioteczny (tu i teraz). Praktyczne zastosowanie.
Przeglad 12 części

Slajd zatytułowany "Czego się nauczyliśmy - przegląd 12 części" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

57 Dalsze kroki: co uczyć się dalej

Co dalej po kursie OOP?

Gratulacje! Opanowales podstawy programowania obiektowego w Pythonie. Oto sugerowane kolejne tematy, które rozwiną Twoje umiejętności i przygotuja do tworzenia profesjonalnych aplikacji:
  • Type Hints - adnotacje typów, TypedDict , Protocol . Statyczna analiza kodu, mypy, pylance.
  • Unit testing - pytest , unittest , mocki i fixture. Automatyzacja testowania.
  • Wzorce projektowe - więcej wzorców: Builder, Decorator, Strategy. Zaawansowane techniki projektowe.
  • Python zaawansowany- dekoratory, generatory, context managers. Funkcję wyzszego rzedu.
  • Bazy danych- SQLAlchemy, ORM, integracja z OOP. Trwalosc danych.
  • Frameworki- Django, FastAPI (pełne projekty webowe). Aplikację internetowe.

Każdy z tych tematów stanowi naturalne rozszerzenie wiedzy zdobytej w trakcie kursu. Type Hints i unit testing są szczególnie polecane jako następny krok - znacznie poprawiaja jakosc i niezawodnosc kodu. Frameworki webowe to dobry wybór, jeśli chcesz zobaczyć, jak OOP stosuje się w praktyce komercyjnej.

Dalsze kroki

Slajd zatytułowany "Dalsze kroki: co uczyć się dalej" przedstawia istotne zagadnieńie z zakresu programowania obiektowego w Pythonie. Zrozumienie przedstawionych tu koncepcji jest niezbędne do dalszej nauki i praktycznego stosowania OOP w codziennej pracy programisty. Zaleca się dokładne przeanalizowanie przykładów kodu i samodzielne ich przetestowanie w środowisku REPL lub edytorze. Warto również zwrócić uwagę na powiązania między tym tematem a innymi zagadnieniami omawianymi w kursie. Systematyczne budowanie wiedzy krok po kroku to klucz do opanowania programowania obiektowego w Pythonie. Nie pomijaj żadnego slajdu, nawet jeśli wydaje Ci się, że temat jest Ci już znany - powtórka utrwala wiedzę i pozwala dostrzec nowe szczegóły w znajomym materiale.

Zachęcamy do samodzielnego eksperymentowania z omawianymi mechanizmami i modyfikowania przykładowego kodu. Praktyczne ćwiczenia i własne projekty to najskuteczniejsza metoda nauki programowania, ponieważ wymagają aktywnego stosowania wiedzy. Pamiętaj, że błędy są naturalną częścią procesu uczenia się i każda pomyłka przybliża Cię do mistrzostwa. Warto prowadzić własny dziennik błędów, w którym zapisujesz napotkane problemy i ich rozwiązania. Taki zeszyt stanie się z czasem bezcennym źródłem wiedzy i referencją na przyszłość. Korzystaj z dokumentacji oficjalnej Pythona oraz społeczności programistycznych - to nieocenione źródła wiedzy i wsparcia w trudnych momentach.

58 Quiz końcowy - pytanie 1

Pytanie 1: @dataclass

Które stwierdzenie o @dataclass jest PRAWDZIWE?

  1. Automatycznie generuje metody __init__ , __repr__ i __eq__
  2. Wymaga zdefiniowania __init__ recznie
  3. Nie obsługuje domyślnych wartości atrybutów
  4. Działa tylko z typami wbudowanymi

Odpowiedź: 1 - @dataclass automatycznie generuje __init__ , __repr__ , __eq__ (oraz opcjonalnie inne metody jak __hash__ , __lt__ ). Dodatkowo można użyć parametrów order=True do generowania metod porównujacych ( __lt__ , __le__ , itd.) oraz frozen=True do tworzenia niemutowalnych obiektów. To potwierdza, ze @dataclass jest elastycznym narzedziem do definiowania modeli danych.

Quiz 1

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

59 Quiz końcowy - pytanie 2

Pytanie 2: Własne wyjątki

Po jakiej klasie musi dziedziczyć własny wyjątek w Pythonie?

  1. BaseException
  2. Exception
  3. object
  4. Error

Odpowiedź: 2 - po Exception . Dziedziczenie po BaseException jest możliwe, ale odradzane (to kategoria dla wyjątków systemowych jak KeyboardInterrupt lub SystemExit ). Własne wyjątki biznesowe powinny dziedziczyć po Exception , co zapewnia poprawna obsługę przez standardowe mechanizmy Pythona. Klasa Error nie istnieje w standardowej bibliotece Pythona, a dziedziczenie po object nie daje żadnych mechanizmów obsługi błędów.

Quiz 2

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.

60 Quiz końcowy - pytanie 3 i gratulacje

Pytanie 3: Kompozycja

Czym różni się kompozycja od dziedziczenia?

  1. Kompozycja to relacja "jest", dziedziczenie to relacja "ma"
  2. Kompozycja to relacja "ma", dziedziczenie to relacja "jest"
  3. Kompozycja i dziedziczenie to to samo
  4. Kompozycja dotyczy tylko interfejsów

Odpowiedź: 2 - kompozycja to relacja "ma" (Biblioteka ma książki), dziedziczenie to relacja "jest" (Pies jest zwierzeciem). W inzynierii oprogramowania często stosuje się zasadę: "prefer composition over inheritance" (preferuj kompozycję nad dziedziczeniem), ponieważ kompozycja daje większa elastyczność i mniejsze powiązanie między klasami.

Gratulacje! Ukończyłeś 12-częściowy kurs programowania obiektowego w Pythonie. Zdobyta wiedza pozwoli Ci tworzyć dobrze zorganizowane, obiektowe aplikację. Pamiętaj, ze praktyka jest kluczem do mistrzostwa - im więcej samodzielnie napiszesz kodu, tym lepiej zrozumiesz omówione koncepcje. Powodzenia w dalszej nauce!
Gratulacje

Quiz podsumowujący to nie tylko sprawdzenie wiedzy, ale także okazja do utrwalenia najważniejszych koncepcji poprzez aktywne przypominanie sobie materiału. Badania z zakresu neurodydaktyki pokazują, że testowanie własnej wiedzy jest jedną z najskuteczniejszych metod uczenia się, znacznie efektywniejszą niż bierne czytanie. Każde pytanie quizu zostało starannie zaprojektowane, aby sprawdzić zrozumienie konkretnego zagadnienia, a nie tylko pamięciowe odtworzenie definicji. Jeżeli któreś pytanie sprawiło Ci trudność, potraktuj to jako sygnał, że dany temat wymaga powtórzenia. Wróć do odpowiedniego slajdu i przeanalizuj go jeszcze raz, tym razem zwracając uwagę na szczegóły, które mogły Ci umknąć.

Sukces w nauce programowania polega na systematycznym budowaniu wiedzy - każda kolejna część opiera się na fundamentach z części poprzednich. Jeżeli masz wątpliwości co do którejkolwiek koncepcji, nie przechodź dalej, dopóki jej nie wyjaśnisz. Wykorzystaj dostępne zasoby: dokumentację Pythona, fora społecznościowe, tutoriale wideo i oczywiście możliwość eksperymentowania we własnym środowisku programistycznym. Pamiętaj, że każdy zaawansowany programista zaczynał od podstaw i pokonał te same trudności co Ty teraz. Gratulujemy ukończenia kolejnego etapu nauki i życzymy powodzenia w dalszej części kursu.