Streszczenie Metody magiczne w Pythonie - __str__, __repr__, __len__, __eq__ i inne

Ten moduł poświęcony jest metodom magicznym (dunder methods) w Pythonie - specjalnym metodom o nazwach otoczonych podwójnym podkreślnikiem, które pozwalają nadać klasom zachowania znane z wbudowanych typów. Omówione są reprezentacje tekstowe ( __str__ , __repr__ ), długość i prawdziwość logiczna ( __len__ , __bool__ ), operatory porównania ( __eq__ , __lt__ ), arytmetyka ( __add__ ), indeksowanie ( __getitem__ , __setitem__ ), menedżery kontekstu ( __enter__ , __exit__ ), iteracja ( __iter__ , __next__ ) oraz obiekty wywoływalne ( __call__ ). Moduł wyjaśnia różnicę między __str__ a __repr__ , pokazuje praktyczne zastosowania każdej metody oraz omawia dekorator @functools.total_ordering i atrybut __slots__ . Liczne przykłady kodu i print() w REPL ilustrują działanie każdego protokołu.

  • Reprezentacja obiektów - __str__ dla użytkownika, __repr__ dla programisty, różnica i hierarchia wywołania
  • Długość i bool - __len__ dla kolekcji, __bool__ do kontroli truthiness w warunkach
  • Porównania i arytmetyka - __eq__ , __lt__ , __add__ i inne operatory, __radd__ dla odwrotnej kolejności
  • Indeksowanie i wywoływanie - __getitem__ , __setitem__ , __call__ dla obiektów funkcyjnych
  • Menedżery kontekstu i iteracja - with i for dzięki __enter__ / __exit__ oraz __iter__ / __next__

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 testowaniu 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.

1 / 50Wprowadzenie

Metody magiczne w Pythonie

__str__ , __repr__ , __len__ , __eq__ , __lt__ , __add__ i inne - jak nadać swoim klasom zachowania znane z wbudowanych typów.

Poznaj tajniki metod nazywanych "dunder" (double underscore) i naucz się pisać kod, który jest zarówno czytelny, jak i potężny.

Metody magiczne to fundament Pythonicznego programowania obiektowego. Dzięki nim twoje klasy mogą reagować na operatory arytmetyczne, porównania, indeksowanie, wywołania funkcyjne, konwersje typów i wiele innych mechanizmów języka. Zamiast pisać metody obiekt.wyswietl() wystarczy zdefiniować __str__ , a print(obiekt) zadziała automatycznie. To samo dotyczy dodawania ( __add__ ), porównań ( __eq__ , __lt__ ) czy długości ( __len__ ). W tej części poznasz wszystkie najważniejsze metody dunder i nauczysz się je stosować w praktyce.

Nazwa "dunder" pochodzi od angielskiego "double underscore" (podwójny podkreślnik). Każda metoda magiczna ma nazwę otoczoną dwoma podkreślnikami z obu stron, np. __init__ , __str__ , __add__ . Python rezerwuje ten schemat nazewniczy dla specjalnych metod, które są wywoływane automatycznie przez interpreter w odpowiedzi na określone operacje.

Metody magiczne to nie tylko wygoda - to klucz do tworzenia eleganckich interfejsów API, które są intuicyjne dla innych programistów. Gdy twoja klasa wspiera + , len() , print() i sorted() , użytkownicy twojego kodu od razu wiedzą, jak jej używać.
Metody magiczne

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.

2 / 50Cele dydaktyczne

Po tej części będziesz umiał:

  • Wyjaśnić, czym są metody magiczne i do czego służą
  • Zdefiniować __str__ i __repr__ dla czytelnej reprezentacji obiektów
  • Zaimplementować __len__ do obsługi funkcji len()
  • Porównywać obiekty za pomocą __eq__ i __lt__
  • Dodawać obiekty operatorem + przez __add__
  • Indeksować obiekty nawiasami [] przez __getitem__
  • Stosować __bool__ , __hash__ i __enter__ / __exit__
  • Definiować __call__, aby obiekty były wywoływalne jak funkcje
  • Implementować iterację przez __iter__ i __next__
  • Używać dekoratora @functools.total_ordering do automatyzacji operatorów porównania
  • Tworzyć własne menedżery kontekstu z __enter__ i __exit__
  • Budować klasy, które zachowują się jak wbudowane kolekcje (listy, słowniki)
Metody magiczne to klucz do pisania eleganckiego, Pythonicznego kodu - sprawiają, że twoje klasy zachowują się jak wbudowane typy. Opanowanie ich to krok od początkującego do zaawansowanego programisty Pythona.
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 / 50 Czym są metody magiczne?

Dunder methods (double underscore)

Metody magiczne to specjalne metody w Pythonie, których nazwy zaczynają się i kończą podwójnym podkreślnikiem, np. __init__ , __str__ , __add__ .

Python wywołuje je automatycznie w odpowiedzi na określone operacje - nie wołasz ich bezpośrednio, lecz uruchamiasz poprzez składnię języka.

  • obj + other__add__
  • str(obj)__str__
  • len(obj)__len__
  • obj == other__eq__
  • obj[key]__getitem__
  • obj(arg)__call__
  • bool(obj)__bool__
  • with obj as x:__enter__ / __exit__

Każda z tych metod definiuje tzw. protokół - zestaw zachowań, które Python oczekuje od obiektu w danej sytuacji. Na przykład protokół iteratora wymaga __iter__ i __next__ , a protokół kolekcji wymaga __len__ i __getitem__ . Implementując odpowiednie protokoły, sprawiasz, że twoje klasy stają się "pierwszorzędnymi obywatelami" języka.

W Pythonie nie ma interfejsów znanych z Javy czy C# - zamiast tego stosuje się duck typing i protokoły . Metody magiczne są właśnie tym, co definiuje te protokoły. Jeśli klasa implementuje __len__ i __getitem__ , to dla Pythona jest "listopodobna".
Dunder methods

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 testowaniu 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.

4 / 50 Dlaczego "magiczne"?

Magia kryjąca się za składnią

Nazywamy je "magicznymi", ponieważ działają w tle - programista pisze naturalną składnię, a Python automatycznie wywołuje odpowiednią metodę.

To sprawia, że kod staje się bardziej intuicyjny i zbliżony do notacji matematycznej. Nie musisz pamiętać osobnych nazw funkcji dla każdej klasy - wystarczy, że zdefiniujesz metody magiczne.

Można je postrzegać jako przeciążanie operatorów (operator overloading) znane z C++ czy C#. Python idzie jednak o krok dalej - metody magiczne obejmują nie tylko operatory arytmetyczne, ale także konwersje typów, zarządzanie kontekstem, iterację, indeksowanie i wiele innych aspektów języka.

Bez metod magicznych kod obiektowy wyglądałby tak:

# Bez metod magicznych - niewygodne
koszyk.dodaj(produkt)
print(koszyk.pokaz())
ilosc = koszyk.licz()
cena = koszyk.suma() + inny_koszyk.suma()
if koszyk.czy_pusty():
    ...

# Z metodami magicznymi - naturalne
koszyk + produkt
print(koszyk)
len(koszyk)
cena = koszyk + inny_koszyk
if not koszyk:
    ...
Bez metod magicznych musiałbyś pisać obiekt.dodaj(inny) zamiast obiekt + inny . Metody magiczne upraszczają interfejs i sprawiają, że kod jest bardziej deklaratywny - mówisz co ma się stać, nie jak .

Warto zapamiętać, że "magia" w Pythonie to nie czary, tylko konsekwentnie zaprojektowany mechanizm protokołów. Każda metoda magiczna ma ściśle określone znaczenie i zachowanie, udokumentowane w oficjalnej dokumentacji Pythona (Python Data Model).

Dlaczego magiczne

Wymienione zalety programowania obiektowego mają bezpośrednie przełożenie na praktykę inżynierii oprogramowania i codzienną pracę programisty. Czytelność kodu przekłada się na niższe koszty utrzymania, ponieważ nowi członkowie zespołu szybciej rozumieją strukturę projektu. Wielokrotne użycie klas i dziedziczenie redukują ilość duplikacji kodu, co jest jednym z głównych celów dobrych praktyk programistycznych. Organizacja kodu w klasy ułatwia nawigację po projekcie i przyspiesza wprowadzanie zmian. Łatwość utrzymania oznacza, że modyfikacje w jednym miejscu nie powodują nieoczekiwanych skutków ubocznych w innych. Bezpieczeństwo danych jest zwiększone dzięki enkapsulacji chroniącej stan obiektu przed przypadkową modyfikacją z zewnątrz.

Należy jednak pamiętać, że OOP nie jest srebrną kulą rozwiązującą wszystkie problemy programistyczne. Nadużywanie dziedziczenia prowadzi do głębokich hierarchii trudnych w utrzymaniu i zrozumieniu. Przesadna enkapsulacja może utrudniać testowanie i debugowanie kodu. W przypadku prostych zadań, takich jak jednorazowe skrypty do przetwarzania danych, podejście proceduralne jest w pełni wystarczające i często szybsze w implementacji. Dlatego tak ważne jest zrozumienie nie tylko zalet, ale też potencjalnych pułapek OOP. Świadomy programista wybiera narzędzie odpowiednie do zadania, zamiast ślepo stosować jeden paradygmat.

5 / 50 Klasa bez metod magicznych - problem

Domyślne zachowanie bywa nieczytelne

class Punkt:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Punkt(3, 5)
print(p)         # <__main__.Punkt object at 0x...>
print(len(p))     # TypeError: object of type 'Punkt' has no len()
print(p + p)     # TypeError: unsupported operand type(s) for +

Bez metod magicznych Python nie wie, jak wyświetlić, zmierzyć czy dodać nasz obiekt. Otrzymujemy domyślne, mało przydatne komunikaty.

Co gorsza, takie zachowanie może prowadzić do subtelnych błędów. Domyślne == porównuje identyfikatory obiektów, więc dwa punkty o tych samych współrzędnych nie będą równe. Domyślne print() pokazuje tylko typ i adres w pamięci - zupełnie nieprzydatne przy debugowaniu. A próba sortowania listy takich obiektów zakończy się błędem, bo Python nie wie, jak je porównać.

# Więcej problemów bez metod magicznych:
p1 = Punkt(3, 5)
p2 = Punkt(3, 5)
print(p1 == p2)   # False - porównanie id, nie wartości!

punkty = [Punkt(2,3), Punkt(1,1)]
# punkty.sort()  # TypeError: '<' not supported

Każda z tych sytuacji wymaga od programisty ręcznego pisania kodu. Metody magiczne rozwiązują wszystkie te problemy za jednym zamachem.

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 testowaniu 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.

6 / 50 Lista najważniejszych metod magicznych

Podział na kategorie

Python definiuje kilkadziesiąt metod magicznych. Oto najważniejsze, pogrupowane według przeznaczenia:

  • Reprezentacja: __str__, __repr__
  • Długość / bool: __len__, __bool__
  • Porównania: __eq__ , __ne__ , __lt__ , __le__ , __gt__ , __ge__
  • Arytmetyka: __add__ , __sub__ , __mul__ , __truediv__ , __floordiv__ , __mod__ , __pow__
  • Indeksowanie: __getitem__ , __setitem__ , __delitem__ , __contains__
  • Hash & context manager: __hash__ , __enter__ , __exit__
  • Iteracja: __iter__, __next__
  • Wywoływanie: __call__
  • Konwersja typów: __int__ , __float__ , __bool__ , __str__
  • Arytmetyka odwrócona: __radd__ , __rsub__ , __rmul__ (gdy nasz typ jest po prawej stronie operatora)
  • Operacje w miejscu: __iadd__ , __isub__ (dla += , -= )
Nie musisz implementować wszystkich - wybierz te, które mają sens dla twojej klasy. Dobrą praktyką jest zacząć od __repr__ i __eq__ , a potem dodawać kolejne w miarę potrzeb.

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 testowaniu 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.

7 / 50 Podsumowanie - czym są metody magiczne

Kluczowe wnioski

  • Metody magiczne to specjalne metody o nazwach __nazwa__ (dunder).
  • Są wywoływane automatycznie przez Python w odpowiedzi na operatory i funkcje wbudowane.
  • Umożliwiają dostosowanie zachowania obiektów do własnych potrzeb.
  • Bez nich kod wymagałby jawnych wywołań metod, co jest mniej czytelne.
  • Stanowią fundament Pythonicznego, eleganckiego kodu obiektowego.
  • Implementują protokoły, które pozwalają twoim klasom współdziałać z wbudowanymi funkcjami i konstrukcjami języka.
  • Należy je stosować z rozwagą - nadmiar metod magicznych może utrudnić zrozumienie kodu.

Zanim przejdziemy do szczegółów, zapamiętaj złotą zasadę: metody magiczne mają sens tylko wtedy, gdy ich użycie jest naturalne i oczekiwane . Nie dodawaj __add__ do klasy reprezentującej użytkownika - ale dodaj ją do klasy reprezentującej wektor, pieniądze czy macierz.

W Pythonie 3.7+ pojawiły się również metody magiczne na poziomie modułów oraz rozszerzenia protokołów w nowszych wersjach. Śledź zmiany w języku, aby być na bieżąco!
Podsumowanie

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 testowaniu 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.

8 / 50 __str__ - reprezentacja dla użytkownika

Co zwraca __str__?

Metoda __str__ powinna zwracać czytelną, zwięzłą reprezentację obiektu, przeznaczoną dla użytkownika końcowego.

Jest wywoływana przez funkcje print() oraz str() . Jej wynik powinien być łatwy do odczytania przez człowieka.

Różnica między __str__ a __repr__ jest subtelna, ale ważna: __str__ to "ładna wersja" dla odbiorcy, który nie musi być programistą, podczas gdy __repr__ to wersja techniczna, jednoznaczna i szczegółowa.

W praktyce __str__ jest używane wszędzie tam, gdzie obiekt jest konwertowany na łańcuch znaków w kontekście "prezentacji":

  • Funkcja print(obiekt)
  • Funkcja str(obiekt)
  • F-stringi:f"Wartość: {obiekt}"
  • Formatowanie:"{}".format(obiekt)
Zasada: __str__ → dla użytkownika (ładne, czytelne).
__repr__ → dla programisty (jednoznaczne, możliwie do odtworzenia).
Dobrą praktyką jest zawsze definiować przynajmniej __repr__ , a __str__ tylko wtedy, gdy potrzebujesz ładniejszej wersji.
__str__

Slajd zatytułowany "__str__ - reprezentacja dla użytkownika" przedstawia istotne zagadnienie 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.

9 / 50 Kod: __str__ w klasie

Definiujemy __str__

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

    def __str__(self):
        return f"Osoba: {self.imie}, lat {self.wiek}"

o = Osoba("Anna", 28)
print(o)  # Osoba: Anna, lat 28

Metoda zwraca łańcuch znaków, który jest wyświetlany przy próbie wypisania obiektu.

Zwróć uwagę, że __str__ musi zawsze zwracać obiekt typu str (nie print wewnątrz metody!). To częsty błąd początkujących - metoda magiczna ma zwracać wartość, nie wyświetlać ją.

# Błędna implementacja - NIGDY TAK NIE RÓB:
class Zle:
    def __str__(self):
        print("Jestem obiektem")  # ZLE! __str__ nie powinno printować!
        return ""

# Prawidłowa implementacja:
class Dobrze:
    def __str__(self):
        return "Jestem obiektem"  # zwracamy string
Kod __str__

Slajd zatytułowany "Kod: __str__ w klasie" przedstawia istotne zagadnienie 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.

10 / 50 Kod: print() wywołuje __str__

Mechanizm wywołania

class Produkt:
    def __init__(self, nazwa, cena):
        self.nazwa = nazwa
        self.cena = cena

    def __str__(self):
        return f"Produkt: {self.nazwa} ({self.cena} PLN)"

p = Produkt("Laptop", 3499.00)
print(p)
print(str(p))  # to samo co print(p)
tekst = f"Zamówienie: {p}"
print(tekst)  # Zamówienie: Produkt: Laptop (3499.0 PLN)

Jak widzisz, __str__ jest wywoływane automatycznie nie tylko przez print() , ale także przy interpolacji w f-stringach. To sprawia, że twoje obiekty naturalnie integrują się z mechanizmami formatowania tekstu w Pythonie.

# Dodatkowe konteksty wywołania __str__:
print(f"Nowy produkt: {p}")       # f-string
print("Produkt: {}".format(p))    # str.format()
print(str(p))                        # str()
print(p.__str__())                   # bezpośrednio (ale tak się nie robi!)
Choć możesz wywołać obiekt.__str__() bezpośrednio, nie jest to zalecane - lepiej użyć str(obiekt) lub po prostu print(obiekt) .
print __str__

Slajd zatytułowany "Kod: print() wywołuje __str__" przedstawia istotne zagadnienie 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.

11 / 50 Kod: bez i z __str__ - porównanie

Różnica w wyniku

class Karta:
    def __init__(self, kolor, wartosc):
        self.kolor = kolor
        self.wartosc = wartosc

k = Karta("kier", "A")
print(k)   # <__main__.Karta object at 0x...> (brzydko)

# --- dodajemy __str__ ---
class KartaLadna:
    def __init__(self, kolor, wartosc):
        self.kolor = kolor
        self.wartosc = wartosc
    def __str__(self):
        return f"{self.wartosc} {self.kolor}"

k2 = KartaLadna("kier", "A")
print(k2)  # A kier (ładnie!)

Różnica jest drastyczna - z __str__ dostajemy czytelną informację, bez niej - techniczny identyfikator obiektu. W praktyce oznacza to, że debugowanie i logowanie staje się znacznie prostsze, gdy każda klasa ma zdefiniowane sensowne __str__ .

Co więcej, __str__ dziedziczy się po object - każda klasa w Pythonie domyślnie ma __str__ , ale jego domyślna implementacja zwraca mało przydatny napis w stylu <__main__.Karta object at 0x...> . Nadpisując go, czynisz swoją klasę bardziej użyteczną.

Gdy nie ma __str__ , Python używa __repr__ jako awaryjnej. Jeśli nie ma i __repr__ , dostajesz domyślny format <__main__.Klasa object at 0x...> .

Porównanie programowania proceduralnego i obiektowego na konkretnych przykładach pokazuje fundamentalne różnice w organizacji kodu. Podejście proceduralne koncentruje się na sekwencji operacji i funkcjach przetwarzających dane, podczas gdy obiektowe grupuje powiązane dane i funkcje w spójne jednostki zwane klasami. W praktyce zawodowej rzadko spotyka się czyste implementacje jednego paradygmatu - nowoczesne aplikacje łączą różne podejścia w zależności od potrzeb konkretnego modułu. Python, jako język wieloparadygmatowy, doskonale wspiera taki hybrydowy styl programowania, pozwalając programiście na elastyczny wybór narzędzia. Wybór paradygmatu zależy przede wszystkim od charakteru problemu - proste skrypty często lepiej napisać proceduralnie, a złożone systemy obiektowo.

Tabelaryczne zestawienie cech obu podejść ułatwia zrozumienie, kiedy które z nich jest bardziej odpowiednie. Programowanie proceduralne sprawdza się w małych, liniowych skryptach, gdzie ważna jest prostota i szybkość wykonania. OOP dominuje w dużych projektach, gdzie kluczowe są organizacja kodu, wielokrotne użycie i łatwość utrzymania. Warto również zauważyć, że wiele języków nowej generacji, takich jak Rust czy Kotlin, łączy elementy obu podejść, oferując programistom najlepsze cechy każdego z nich. Świadomy wybór paradygmatu jest oznaką dojrzałości programistycznej i pozwala na podejmowanie optymalnych decyzji projektowych w codziennej pracy.

12 / 50 Podsumowanie __str__

Co zapamiętać?

  • __str__ zwraca czytelny tekst dla użytkownika.
  • Wywoływana przez print(), str() i f-stringi.
  • Powinna zwracać obiekt typu str.
  • Jeśli nie zdefiniujesz __str__ , Python użyje __repr__ jako fallback.
  • Stosuj ją zawsze, gdy chcesz, aby print(obiekt) wyświetlał coś sensownego.
  • Nie używaj print() wewnątrz __str__ - metoda ma zwracać string, nie wyświetlać go.
  • Pamiętaj o specyfikatorach formatu: !s w f-stringach (domyślnie) wywołuje __str__ , a !r wymusza użycie __repr__ .
# Przykład użycia !r vs domyślnego !s:
print(f"{obiekt!s}")   # __str__
print(f"{obiekt!r}")   # __repr__
Podsumowanie __str__

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.

13 / 50 __repr__ - reprezentacja dla programisty

Jednoznaczna i szczegółowa

Metoda __repr__ powinna zwracać jednoznaczną reprezentację obiektu, najlepiej taką, którą można wkleić do kodu, aby odtworzyć obiekt.

Jest wywoływana w interaktywnej konsoli (REPL) oraz przez funkcję repr() . Gdy brak __str__ , Python używa __repr__ jako awaryjnej.

Oto kilka przykładów, jak wbudowane typy implementują __repr__:

print(repr(42))              # '42'
print(repr("hello"))         # "'hello'"  (z cudzysłowami!)
print(repr([1, 2, 3]))        # '[1, 2, 3]'

Zauważ, że dla napisów repr() dodaje cudzysłowy - to dlatego, że repr() ma zwracać tekst, który można wkleić do kodu źródłowego.

Konwencja: __repr__ powinno zwracać tekst, który po ewaluacji w Pythonie da równoważny obiekt (lub coś bardzo zbliżonego). Idealnie: eval(repr(obj)) == obj .
__repr__

Slajd zatytułowany "__repr__ - reprezentacja dla programisty" przedstawia istotne zagadnienie 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.

14 / 50 Kod: __repr__ w klasie

Definiujemy __repr__

class Punkt2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Punkt2D({self.x}, {self.y})"

p = Punkt2D(3, 7)
print(repr(p))   # Punkt2D(3, 7)
# W REPL wystarczy wpisać: p  →  Punkt2D(3, 7)

Zwracany tekst wygląda jak wywołanie konstruktora - to zalecany wzorzec.

Dzięki takiej konwencji, w REPL możesz skopiować wynik repr() i wkleić go do kodu, aby odtworzyć obiekt. To niezwykle przydatne przy debugowaniu i testowaniu.

# Można nawet użyć eval() do odtworzenia:
p2 = eval(repr(p))
print(p2)  # Punkt2D(3, 7)

Oczywiście eval() należy stosować ostrożnie - tylko w zaufanym środowisku. Wartość __repr__ polega na czytelności i jednoznaczności, niekoniecznie na tym, by zawsze dało się wywołać eval() .

Kod __repr__

Slajd zatytułowany "Kod: __repr__ w klasie" przedstawia istotne zagadnienie 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.

15 / 50 Kod: różnica __str__ vs __repr__

Dwie perspektywy

class Czas:
    def __init__(self, h, m):
        self.h = h
        self.m = m

    def __repr__(self):
        return f"Czas({self.h}, {self.m})"

    def __str__(self):
        return f"{self.h:02d}:{self.m:02d}"

t = Czas(14, 30)
print(t)           # 14:30           (__str__)
print(repr(t))     # Czas(14, 30)    (__repr__)
# w REPL: t  →  Czas(14, 30)  (__repr__)

Różnica jest klarowna: __str__ pokazuje godzinę w formacie czytelnym dla człowieka (14:30), a __repr__ pokazuje techniczną reprezentację (Czas(14, 30)), która mówi programiście dokładnie, jaki to obiekt i jakie ma wartości atrybutów.

W REPL, gdy po prostu wpiszesz nazwę zmiennej i naciśniesz Enter, Python wywołuje __repr__ , nie __str__ . To dlatego, że REPL zakłada, że odbiorcą jest programista, który potrzebuje pełnej informacji.

Złota zasada: __repr__ jest dla programisty, __str__ dla użytkownika. Jeśli możesz zdefiniować tylko jedną, wybierz __repr__ - Python użyje jej jako awaryjnej dla __str__ .
__str__ vs __repr__

Slajd zatytułowany "Kod: różnica __str__ vs __repr__" przedstawia istotne zagadnienie 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.

16 / 50 Kod: REPL wywołuje __repr__

Zachowanie w interaktywnej konsoli

class Test:
    def __repr__(self):
        return "Test()"
    def __str__(self):
        return "opis testu"

t = Test()
t          # w REPL: Test()      (__repr__)
print(t)  # opis testu              (__str__)
str(t)    # 'opis testu'            (__str__)
repr(t)   # 'Test()'                (__repr__)

# Gdy brak __str__, print używa __repr__:
class BezStr:
    def __repr__(self):
        return "BezStr()"

print(BezStr())  # BezStr()

Ten przykład doskonale ilustruje hierarchię: Python zawsze preferuje __str__ w print() , ale jeśli go nie ma, używa __repr__ . W REPL zawsze używa __repr__ .

W praktyce oznacza to, że definiując tylko __repr__ , zapewniasz, że zarówno print() jak i REPL będą działać.

W Pythonie istnieje też konwencja, że __repr__ powinno zwracać tekst zawierający wszystkie istotne informacje o obiekcie. Dla złożonych obiektów może to być długi string - i to jest w porządku.
REPL __repr__

Slajd zatytułowany "Kod: REPL wywołuje __repr__" przedstawia istotne zagadnienie 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.

17 / 50 Podsumowanie __repr__

Co zapamiętać?

  • __repr__ to reprezentacja dla programisty - jednoznaczna, szczegółowa.
  • Idealnie, zwracany tekst powinien pozwalać na odtworzenie obiektu ( eval(repr(obj)) ).
  • Wywoływana w REPL oraz przez repr().
  • Gdy brak __str__, Python używa __repr__ jako fallback.
  • Zawsze definiuj przynajmniej __repr__ - to dobra praktyka.
  • Dla klas z wieloma atrybutami,__repr__ może być długie - to normalne.
  • W odróżnieniu od __str__ , __repr__ powinno dążyć do jednoznaczności kosztem czytelności.
# Wzorzec do zapamiętania:
class MojaKlasa:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __repr__(self):
        return f"MojaKlasa({self.a!r}, {self.b!r})"
        # Użycie !r w f-stringu zapewnia, że
        # atrybuty też są pokazane przez ich __repr__

Zwróć uwagę na użycie !r w f-stringu - to gwarantuje, że wartości atrybutów są wyświetlane przez ich własne __repr__ , a nie __str__ .

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.

18 / 50 __len__ - długość obiektu

Co zwraca __len__?

Metoda __len__ powinna zwracać nieujemną liczbę całkowitą, reprezentującą "rozmiar" lub "długość" obiektu.

Jest wywoływana przez funkcję wbudowaną len() . Jeśli zwróci 0 , obiekt jest traktowany jako False w kontekście logicznym (o ile nie ma __bool__ ).

Semantycznie __len__ powinna odpowiadać na pytanie "ile elementów zawiera ten obiekt?". Dla kolekcji jest to liczba elementów, dla łańcuchów - liczba znaków, dla struktur drzewiastych - liczba węzłów. Ważne, aby zwracana wartość była spójna z intuicją użytkownika: jeśli twój obiekt reprezentuje stos, len() powinno zwracać liczbę elementów na stosie, a nie pojemność tablicy.

Python wymaga, aby __len__ zwracała wartość typu int (lub dowolny typ całkowity implementujący __index__ ). Zwrócona wartość musi być nieujemna - zwrócenie ujemnej liczby spowoduje błąd ValueError w wielu kontekstach (choć nie zawsze natychmiastowy). Ponadto, jeśli zdefiniujesz __len__ i __getitem__ z indeksowaniem od zera, Python automatycznie uzna, że twój obiekt wspiera iterację w pętli for .

Uwaga: __len__ powinno być szybką operacją - najlepiej O(1). Python zakłada, że len() to lekka funkcja, która nie wykonuje kosztownych obliczeń. Jeśli twój obiekt wymaga policzenia długości przez iterację, rozważ buforowanie wyniku lub inną architekturę.
__len__

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ą.

19 / 50 Kod: __len__ w klasie

Definiujemy __len__

class Koszyk:
    def __init__(self):
        self.produkty = []

    def dodaj(self, produkt):
        self.produkty.append(produkt)

    def __len__(self):
        return len(self.produkty)

koszyk = Koszyk()
koszyk.dodaj("jabłko")
koszyk.dodaj("chleb")
print(len(koszyk))  # 2

__len__ deleguje do len() wewnętrznej listy - prosty i czytelny wzorzec.

Możesz jednak zdefiniować bardziej złożone zachowanie. Na przykład koszyk, w którym każdy produkt ma wagę, a len() zwraca całkowitą wagę (a nie liczbę produktów):

class KoszykWazony:
    def __init__(self):
        self.produkty = []  # listy krotek (nazwa, waga)

    def dodaj(self, nazwa, waga):
        self.produkty.append((nazwa, waga))

    def __len__(self):
        return sum(w for _, w in self.produkty)

koszyk = KoszykWazony()
koszyk.dodaj("jabłka", 3)
koszyk.dodaj("mąka", 2)
print(len(koszyk))  # 5 - całkowita waga w kg

Uwaga: __len__ musi zwracać liczbę całkowitą (typ int ). Python nie zezwala na zwracanie wartości zmiennoprzecinkowych - len() oczekuje typu całkowitego. W tym przykładzie __len__ nie zwraca liczby elementów, ale sumę wag. To pokazuje, że możesz dostosować znaczenie "długości" do swojej domeny, o ile zachowujesz spójność semantyczną i zwracasz liczbę całkowitą.

Kod __len__

Slajd zatytułowany "Kod: __len__ w klasie" przedstawia istotne zagadnienie 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.

20 / 50 Kod: len() wywołuje __len__

Działanie w praktyce

class Kolekcja:
    def __init__(self, *args):
        self.dane = list(args)

    def __len__(self):
        return len(self.dane)

k = Kolekcja(1, 2, 3, 4, 5)
print(len(k))       # 5
print(len(Kolekcja()))  # 0
print(len(Kolekcja("a", "b")))  # 2

      # __len__ wpływa też na truthiness:
if Kolekcja():
    print("prawda")  # nie wykona się (len=0 → False)

# Kolejny przykład - klasa reprezentująca talię kart:
class Talia:
    def __init__(self):
        self.karty = [(f, k) for f in range(2, 15)
                      for k in ["♠", "♥", "♦", "♣"]]

    def __len__(self):
        return len(self.karty)

    def __bool__(self):
        return len(self) > 0

talia = Talia()
print(len(talia))   # 52
print(bool(talia))  # True - talia nie jest pusta

Zauważ, że jawnie definiując __bool__ , nadpisujesz domyślne zachowanie __len__ w kontekście logicznym. To przydatne, gdy chcesz, aby obiekt był True / False z innego powodu niż długość.

W kontekście truthiness istnieje ważna hierarchia: Python najpierw sprawdza __bool__ , potem __len__ , a jeśli nie ma żadnej - obiekt jest zawsze True . To oznacza, że nawet pusta kolekcja bez __len__ jest True w if , co może prowadzić do błędów.

len wywołuje __len__

Slajd zatytułowany "Kod: len() wywołuje __len__" przedstawia istotne zagadnienie 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.

21 / 50 Kod: warunek if z __len__

Sprawdzanie, czy obiekt jest pusty

class ListaZadan:
    def __init__(self):
        self.zadania = []

    def dodaj(self, zadanie):
        self.zadania.append(zadanie)

    def __len__(self):
        return len(self.zadania)

    def __str__(self):
        return f"Lista zadań ({len(self)} pozycji)"

lista = ListaZadan()
if not lista:
    print("Lista jest pusta")  # zostanie wydrukowane

lista.dodaj("Kupić mleko")
if lista:
    print("Masz coś do zrobienia!")
    print(lista)  # Lista zadań (1 pozycji)

Zwróć uwagę, że warunek if not lista działa bez definiowania __bool__ - Python automatycznie używa __len__ do określenia prawdziwości. To wzorzec znany z wbudowanych kolekcji: pusta lista, pusty słownik czy pusty zbiór są False .

Enkapsulacja w tym przykładzie polega na tym, że zewnętrzny kod nie musi wiedzieć, że ListaZadan przechowuje zadania w wewnętrznej liście self.zadania . Użytkownik klasy po prostu sprawdza if lista: i dostaje intuicyjny rezultat. To sedno programowania obiektowego - ukrywamy implementację, a udostępniamy czytelny interfejs.

Dodatkowo, w __str__ użyliśmy len(self) , co wewnątrz klasy wywołuje naszą własną __len__ . To dobra praktyka - zamiast odwoływać się bezpośrednio do len(self.zadania) , używamy własnej metody magicznej, co ułatwia późniejszą refaktoryzację.

if z __len__

Slajd zatytułowany "Kod: warunek if z __len__" przedstawia istotne zagadnienie 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.

22 / 50 Podsumowanie __len__

Co zapamiętać?

  • __len__ zwraca długość obiektu jako int≥ 0.
  • Wywoływana przez len().
  • Wpływa na bool(obiekt) - długość 0 daje False (o ile nie ma __bool__ ).
  • Przydatna w klasach reprezentujących kolekcje, listy, zbiory.
  • Często łączona z __getitem__ i __iter__.
  • Powinna być szybka (O(1)) - nie wykonuj kosztownych obliczeń wewnątrz __len__.
  • Możesz zdefiniować niestandardowe znaczenie "długości" (np. suma wag, liczba węzłów).
  • Razem z __getitem__ umożliwia iterację - Python automatycznie wspiera pętlę for .
# Szybki wzorzec: klasa opakowująca listę z __len__
class ProstaKolekcja:
    def __init__(self, *args):
        self._dane = list(args)

    def __len__(self):
        return len(self._dane)

    def __getitem__(self, i):
        return self._dane[i]

k = ProstaKolekcja(1, 2, 3)
print(len(k))          # 3
for x in k:
    print(x, end=" ")  # 1 2 3
Podsumowanie __len__

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.

23 / 50 __eq__ - porównanie ==

Równość obiektów

Metoda __eq__ pozwala zdefiniować, co oznacza, że dwa obiekty są "równe" ( == ).

Domyślnie Python porównuje identyfikatory obiektów ( id ) - dwa obiekty są równe tylko wtedy, gdy to ten sam obiekt w pamięci. __eq__ pozwala zmienić to zachowanie na porównanie wartości.

W Pythonie istnieje ważne rozróżnienie między tożsamością (identity, operator is ) a równością (equality, operator == ). Tożsamość sprawdza, czy dwie zmienne wskazują na ten sam obiekt w pamięci. Równość sprawdza, czy obiekty mają tę samą wartość - i to właśnie definiujesz w __eq__ . Dla dwóch różnych obiektów o tych samych danych a is b zwróci False , ale a == b może zwrócić True .

Ważna konsekwencja: gdy definiujesz __eq__ , Python automatycznie ustawia __hash__ na None . Oznacza to, że obiekt staje się niehaszowalny i nie może być używany jako klucz w słowniku ( dict ) ani element zbioru ( set ), chyba że jawnie zdefiniujesz __hash__ . To zabezpieczenie przed sytuacją, w której dwa równe obiekty miałyby różne hashe.

Pamiętaj: operator is zawsze porównuje identyfikatory i nie może być przeciążony. Operator == domyślnie robi to samo, ale możesz go zmienić przez __eq__ . Zasadnicza różnica: a is b → czy to ten sam obiekt?, a == b → czy te obiekty mają tę samą wartość?
__eq__

Porównanie programowania proceduralnego i obiektowego na konkretnych przykładach pokazuje fundamentalne różnice w organizacji kodu. Podejście proceduralne koncentruje się na sekwencji operacji i funkcjach przetwarzających dane, podczas gdy obiektowe grupuje powiązane dane i funkcje w spójne jednostki zwane klasami. W praktyce zawodowej rzadko spotyka się czyste implementacje jednego paradygmatu - nowoczesne aplikacje łączą różne podejścia w zależności od potrzeb konkretnego modułu. Python, jako język wieloparadygmatowy, doskonale wspiera taki hybrydowy styl programowania, pozwalając programiście na elastyczny wybór narzędzia. Wybór paradygmatu zależy przede wszystkim od charakteru problemu - proste skrypty często lepiej napisać proceduralnie, a złożone systemy obiektowo.

Tabelaryczne zestawienie cech obu podejść ułatwia zrozumienie, kiedy które z nich jest bardziej odpowiednie. Programowanie proceduralne sprawdza się w małych, liniowych skryptach, gdzie ważna jest prostota i szybkość wykonania. OOP dominuje w dużych projektach, gdzie kluczowe są organizacja kodu, wielokrotne użycie i łatwość utrzymania. Warto również zauważyć, że wiele języków nowej generacji, takich jak Rust czy Kotlin, łączy elementy obu podejść, oferując programistom najlepsze cechy każdego z nich. Świadomy wybór paradygmatu jest oznaką dojrzałości programistycznej i pozwala na podejmowanie optymalnych decyzji projektowych w codziennej pracy.

24 / 50 Kod: domyślne == (porównuje id)

Domyślne zachowanie bez __eq__

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

a = Osoba("Ala", 25)
b = Osoba("Ala", 25)
c = a

print(a == b)  # False − różne obiekty, różne id
print(a == c)  # True  − ten sam obiekt
print(id(a), id(b), id(c))  # różne / to samo

Domyślne == to tak naprawdę is - porównanie referencji.

To zachowanie jest dziedziczone z klasy object , która definiuje __eq__ jako porównanie id(self) == id(other) . Dopóki nie nadpiszesz tej metody, każde porównanie == między dwoma różnymi obiektami (nawet o identycznych atrybutach) zwróci False .

Dla typów wbudowanych, takich jak int , str , list , tuple , Python już ma zdefiniowane sensowne __eq__ . Problem pojawia się dopiero przy własnych klasach - wtedy domyślne zachowanie prawie zawsze nie jest tym, czego chcemy.

# Domyślne __eq__ z klasy object - implementacja poglądowa:
class object:
    def __eq__(self, other):
        return id(self) == id(other)
    # Czyli: return self is other

W praktyce oznacza to, że jeśli tworzysz klasę reprezentującą dane (np. Punkt , Osoba , Produkt ), prawie zawsze powinieneś zdefiniować __eq__ . Wyjątkiem są klasy, dla których tożsamość jest jedynym sensownym kryterium równości - na przykład unikalne encje w systemie (każdy obiekt jest niepowtarzalny).

Domyślne ==

Stosowanie analogii ze świata rzeczywistego to sprawdzona metoda dydaktyczna ułatwiająca zrozumienie abstrakcyjnych koncepcji programistycznych. Analogia domu i foremki do ciastek doskonale ilustruje relację między klasą a obiektem, ponieważ każdy ma intuicyjne pojęcie o tych przedmiotach. Dzięki takiemu podejściu student może łatwiej przejść od myślenia w kategoriach przedmiotów fizycznych do myślenia w kategoriach bytów programistycznych. Warto jednak pamiętać, że każda analogia ma swoje ograniczenia i nie należy jej rozciągać zbyt daleko poza zamierzony obszar. Mimo tych ograniczeń, analogie pozostają jednym z najskuteczniejszych narzędzi dydaktycznych w nauczaniu programowania. Kluczem jest umiejętne łączenie analogii z konkretnymi przykładami kodu.

W świecie fizycznym przedmioty nie zmieniają swoich właściwości tak dynamicznie, jak obiekty w programie. W kodzie atrybuty obiektu mogą się zmieniać w każdej chwili poprzez wywołanie metod, a metody mogą uruchamiać złożoną logikę biznesową. Mimo tych różnic, rozumienie relacji między projektem a gotowym produktem jest kluczowe dla zrozumienia OOP. Każdy programista powinien umieć odróżnić definicję klasy od konkretnej instancji, tak jak odróżnia plan architektoniczny od zbudowanego według niego domu. Te fundamentalne rozróżnienie pojawia się w każdym projekcie obiektowym i warto je solidnie utrwalić.

25 / 50 Kod: __eq__ porównuje atrybuty

Równość oparta na wartościach

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

    def __eq__(self, other):
        if not isinstance(other, Osoba):
            return NotImplemented
        return (self.imie == other.imie
                and self.wiek == other.wiek)

a = Osoba("Ala", 25)
b = Osoba("Ala", 25)
c = Osoba("Ola", 30)
print(a == b)  # True − wartości są takie same
print(a == c)  # False

Zwróć uwagę na wzorzec z NotImplemented . To specjalna wartość w Pythonie, która mówi "nie wiem, jak porównać się z tym typem - zapytaj drugą stronę". Jeśli other nie jest instancją Osoba , zwracamy NotImplemented zamiast False . Różnica jest subtelna: False mówi "obiekty nie są równe", a NotImplemented mówi "nie umiem odpowiedzieć - daj szansę drugiemu obiektowi".

Dzięki temu, jeśli zdefiniujesz dwie klasy, które mogą być ze sobą porównywane (np. Pieniadz i float ), Python najpierw zapyta lewą stronę, a jeśli ta zwróci NotImplemented , zapyta prawą stronę (o ile ma zdefiniowane __eq__ lub __ne__ ).

__eq__ atrybuty

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 struktura 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.

26 / 50 Kod: różne obiekty, te same wartości

Równość semantyczna

class Pieniadz:
    def __init__(self, waluta, kwota):
        self.waluta = waluta
        self.kwota = kwota

    def __eq__(self, other):
        if not isinstance(other, Pieniadz):
            return NotImplemented
        return (self.waluta == other.waluta
                and self.kwota == other.kwota)

    def __repr__(self):
        return f"Pieniadz('{self.waluta}', {self.kwota})"

p1 = Pieniadz("PLN", 100)
p2 = Pieniadz("PLN", 100)
p3 = Pieniadz("PLN", 50)
print(p1 == p2)  # True
print(p1 == p3)  # False
print(p1 == 100) # False (inny typ)

Ostatnie wyrażenie p1 == 100 zwraca False , ale w rzeczywistości dzieje się coś bardziej złożonego. Gdy Python wykonuje p1 == 100 , najpierw wywołuje Pieniadz.__eq__(p1, 100) , która zwraca NotImplemented (bo 100 nie jest Pieniadz ). Następnie Python sprawdza, czy int ma zdefiniowane __eq__ , które obsługuje Pieniadz - nie ma, więc Python zwraca NotImplemented również z tej strony. W ostateczności Python porównuje identyfikatory (które są różne) i zwraca False .

Ten mechanizm nazywa się odwracaniem operatorów (operator swapping) i jest kluczowy dla zrozumienia, jak Python obsługuje porównania między różnymi typami. Gdybyś chciał, aby Pieniadz można było porównywać z int , musiałbyś rozszerzyć __eq__ o obsługę liczb.

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ą.

27 / 50 Podsumowanie __eq__

Co zapamiętać?

  • __eq__ definiuje zachowanie operatora ==.
  • Domyślnie porównuje id - dwa obiekty są równe tylko jeśli to ten sam obiekt.
  • Zaimplementuj __eq__, gdy chcesz porównywać obiekty według wartości.
  • Zwracaj NotImplemented dla nieobsługiwanych typów (nie False !).
  • Python automatycznie używa __ne__ (negacja __eq__ ), chyba że zdefiniujesz własne.
  • Definiując __eq__ , tracisz domyślny __hash__ - obiekt staje się niehaszowalny.
  • Jeśli obiekty mają być kluczami w dict / set , zdefiniuj też __hash__ (np. hash(self.pesel) ).
  • Operator is zawsze porównuje identyfikatory - nie można go przeciążyć.
# Pełny przykład: klasa z __eq__, __ne__ i __hash__
class Produkt:
    def __init__(self, kod, nazwa):
        self.kod = kod
        self.nazwa = nazwa

    def __eq__(self, other):
        if not isinstance(other, Produkt):
            return NotImplemented
        return self.kod == other.kod

    def __hash__(self):
        return hash(self.kod)

    def __ne__(self, other):
        return not self.__eq__(other)

# Teraz obiekty Produkt mogą być w zbiorach
p1 = Produkt("A001", "Mysz")
p2 = Produkt("A001", "Mysz bezprzewodowa")
print(p1 == p2)           # True (ten sam kod)
print({p1, p2})           # {Produkt('A001', 'Mysz')} - tylko jeden!
Podsumowanie __eq__

Rozróżnienie między tożsamością a równością jest kluczowe w programowaniu obiektowym i często bywa źródłem błędów u początkujących. Tożsamość, sprawdzana operatorem is, odpowiada na pytanie, czy dwie zmienne wskazują na ten sam obiekt w pamięci. Równość, sprawdzana operatorem ==, pyta, czy dwa obiekty mają takie same wartości atrybutów. Dwa różne obiekty mogą być równe, ale nie mogą być tym samym obiektem. W Pythonie operator == domyślnie zachowuje się jak is dla klas użytkownika, chyba że zdefiniujemy metodę __eq__. Definiując __eq__, warto też zdefiniować __hash__, aby obiekty mogły być używane w zbiorach.

Funkcja id() zwraca unikalny identyfikator obiektu, który w implementacji CPython odpowiada adresowi pamięci. Identyfikator ten jest stały przez cały czas życia obiektu i jednoznacznie go identyfikuje. Operator is jest wydajniejszy niż ==, ponieważ zamiast wywoływać metodę __eq__ i porównywać wszystkie atrybuty, porównuje jedynie liczby całkowite. Dlatego w sytuacjach, gdy chcemy sprawdzić tylko tożsamość, używamy is zamiast ==. Jest to również idiom pythoniczny zalecany przez PEP 8, szczególnie przy porównaniach z None. Metoda __repr__ zapewnia jednoznaczną reprezentację tekstową obiektu.

28 / 50 __lt__ - porządkowanie i sortowanie

Operator mniejszości <

Metoda __lt__ (less than) definiuje zachowanie operatora < . Jest kluczowa dla sortowania - funkcje sorted() i list.sort() używają jej do porównywania elementów.

Wraz z __eq__ pozwala na pełne uporządkowanie obiektów. Można też użyć dekoratora @functools.total_ordering , aby dopełnić pozostałe operatory.

W Pythonie 3 wystarczy zdefiniować __lt__ i __eq__ , aby sortowanie działało. Funkcje sortujące nie używają > , <= ani >= - potrzebują tylko informacji, czy jeden element jest "mniejszy" od drugiego. Algorytmy sortowania (np. TimSort) opierają się wyłącznie na operatorze < .

Dekorator @functools.total_ordering oszczędza pisanie: wystarczy zdefiniować __eq__ i jeden z operatorów porównania ( __lt__ , __le__ , __gt__ lub __ge__ ), a resztę Python wygeneruje automatycznie. Jest to jednak kosztowne wydajnościowo - każdy brakujący operator jest wywoływany przez delegację, co może spowolnić działanie przy intensywnym porównywaniu.

Uwaga: przy definiowaniu __lt__ pamiętaj o zasadzie strict weak ordering : musi być przechodnie (jeśli a < b i b < c, to a < c), asymetryczne (jeśli a < b, to nie b < a) i poprawne względem równości (jeśli a == b, to a < b i b < a są fałszywe). Naruszenie tych zasad może prowadzić do niespójnego sortowania.
__lt__

Slajd zatytułowany "__lt__ - porządkowanie i sortowanie" przedstawia istotne zagadnienie 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.

29 / 50 Kod: __lt__ w klasie

Definiujemy __lt__

class Pracownik:
    def __init__(self, imie, pensja):
        self.imie = imie
        self.pensja = pensja

    def __lt__(self, other):
        if not isinstance(other, Pracownik):
            return NotImplemented
        return self.pensja < other.pensja

    def __repr__(self):
        return f"Pracownik('{self.imie}', {self.pensja})"

p1 = Pracownik("Adam", 5000)
p2 = Pracownik("Ewa", 7000)
print(p1 < p2)  # True (5000 < 7000)
print(p2 < p1)  # False

Ten przykład ilustruje zasadę strict weak ordering : porównujemy pracowników według pensji. Jeśli dwóch pracowników ma tę samą pensję, __lt__ zwróci False w obie strony, co jest zgodne z oczekiwaniem - nie są oni "mniejsi" od siebie nawzajem.

Warto też rozważyć, co zrobić, gdy dwie pensje są równe. W obecnej implementacji kolejność takich pracowników jest nieokreślona - zależy od wewnętrznych algorytmów sortowania. Jeśli chcesz mieć stabilne sortowanie, możesz dodać drugorzędne kryterium:

# Bardziej zaawansowane __lt__ - sortowanie po pensji, potem po imieniu
def __lt__(self, other):
    if not isinstance(other, Pracownik):
        return NotImplemented
    if self.pensja != other.pensja:
        return self.pensja < other.pensja
    return self.imie < other.imie

Dzięki takiej implementacji pracownicy o tej samej pensji będą sortowani alfabetycznie według imienia. To zgodne z intuicją - gdy główne kryterium nie rozstrzyga, używamy dodatkowego.

Kod __lt__

Slajd zatytułowany "Kod: __lt__ w klasie" przedstawia istotne zagadnienie 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.

30 / 50 Kod: sortowanie listy obiektów

Sortowanie z __lt__

# klasa Pracownik z __lt__ (jak wyżej)
pracownicy = [
    Pracownik("Adam", 5000),
    Pracownik("Ewa", 7000),
    Pracownik("Jan", 4000),
    Pracownik("Ola", 6000),
]

pracownicy.sort()
print(pracownicy)
# [Pracownik('Jan', 4000), Pracownik('Adam', 5000),
#  Pracownik('Ola', 6000), Pracownik('Ewa', 7000)]

      # sortowanie odwrotne
pracownicy.sort(reverse=True)
print(pracownicy)
# [Pracownik('Ewa', 7000), Pracownik('Ola', 6000), ...]

Python oferuje też możliwość sortowania według klucza bez definiowania __lt__ w klasie. Funkcje sorted() i list.sort() przyjmują parametr key , który pozwala wskazać funkcję zwracającą wartość do porównania:

# Sortowanie przez key (bez __lt__ w klasie)
class Pracownik:
    def __init__(self, imie, pensja, stanowisko):
        self.imie = imie
        self.pensja = pensja
        self.stanowisko = stanowisko

    def __repr__(self):
        return f"Pracownik('{self.imie}', {self.pensja})"

pracownicy = [
    Pracownik("Adam", 5000, "programista"),
    Pracownik("Ewa", 7000, "kierownik"),
    Pracownik("Jan", 4000, "stażysta"),
]

# Sortowanie po pensji (malejąco)
print(sorted(pracownicy, key=lambda p: p.pensja, reverse=True))

# Sortowanie wielopoziomowe: najpierw stanowisko, potem pensja
print(sorted(pracownicy, key=lambda p: (p.stanowisko, p.pensja)))

# Z modułu operator: itemgetter, attrgetter
from operator import attrgetter
print(sorted(pracownicy, key=attrgetter("pensja")))

Parametr key jest szczególnie przydatny, gdy potrzebujesz różnych kryteriów sortowania w różnych kontekstach. __lt__ definiuje "naturalne" sortowanie, ale nie wyklucza używania key .

Sortowanie __lt__

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ą.

31 / 50 Kod: sorted() z __lt__

Funkcja sorted()

class Karta:
    def __init__(self, figura, kolor):
        self.figura = figura
        self.kolor = kolor
        self._wartosci = {"2":2, "3":3, "4":4,
                          "5":5, "6":6, "7":7,
                          "8":8, "9":9, "10":10,
                          "J":11, "D":12, "K":13, "A":14}

    def __lt__(self, other):
        return self._wartosci[self.figura] < self._wartosci[other.figura]

    def __repr__(self):
        return f"{self.figura}{self.kolor[0]}"

talia = [Karta("A","kier"), Karta("3","pik"), Karta("K","karo")]
print(sorted(talia))  # [3p, Kk, Ak]

Ten przykład pokazuje, jak __lt__ umożliwia sortowanie kart według ich wartości w grze. Zwróć uwagę na użycie słownika _wartosci do mapowania figur na liczby - to elegancki sposób na porządkowanie według arbitralnej hierarchii.

W kontekście gier karcianych możesz rozszerzyć tę klasę o pełne porównanie uwzględniające kolory (np. w brydżu kolor ma znaczenie przy licytacji). Możesz też dodać __eq__ , aby porównywać karty według figury i koloru, oraz __add__ , aby łączyć karty w układy (poker, strita itp.). Metody magiczne dają nieograniczone możliwości modelowania domeny.

# Rozszerzenie o porównanie kolorów - do gry w brydża
class KartaBrydz(Karta):
    _ranga_koloru = {"pik": 4, "kier": 3,
                     "karo": 2, "trefl": 1}

    def __lt__(self, other):
        if self.figura != other.figura:
            return super().__lt__(other)
        return (self._ranga_koloru[self.kolor] <
                self._ranga_koloru[other.kolor])

Dzięki dziedziczeniu i nadpisaniu __lt__ możemy dodać sortowanie według koloru, zachowując oryginalne porównanie figur.

sorted __lt__

Slajd zatytułowany "Kod: sorted() z __lt__" przedstawia istotne zagadnienie 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.

32 / 50 Podsumowanie __lt__

Co zapamiętać?

  • __lt__ definiuje operator < i umożliwia sortowanie.
  • sorted() i list.sort() używają __lt__ pod spodem.
  • Zwracaj NotImplemented dla nieobsługiwanego typu.
  • Możesz użyć @functools.total_ordering by automatycznie dopełnić __le__ , __gt__ , __ge__ na podstawie __eq__ i __lt__ .
  • W Python 3 wystarczy __lt__ do sortowania - pozostałe operatory są opcjonalne.
  • Przestrzegaj zasad strict weak ordering: przechodniość, asymetria, zgodność z równością.
  • Dla sortowania wielopoziomowego dodaj drugorzędne kryterium w __lt__.
  • Parametr key w sorted() to alternatywa bez modyfikowania klasy.
# Użycie @functools.total_ordering
from functools import total_ordering

@total_ordering
class Wynik:
    def __init__(self, punkty):
        self.punkty = punkty

    def __eq__(self, other):
        return self.punkty == other.punkty

    def __lt__(self, other):
        return self.punkty < other.punkty

# total_ordering dopełnia: __le__, __gt__, __ge__, __ne__
a = Wynik(5)
b = Wynik(10)
print(a <= b)  # True (automatycznie wygenerowane)
print(a > b)   # False (automatycznie wygenerowane)
Uwaga: @total_ordering jest wygodne, ale mniej wydajne niż ręczne zdefiniowanie wszystkich operatorów. Każdy brakujący operator wymaga dodatkowego not i odwrócenia argumentów. Dla klas intensywnie porównywanych (np. w sortowaniu dużych zbiorów) lepiej zdefiniować wszystkie operatory ręcznie.
Podsumowanie __lt__

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.

33 / 50 __add__ - dodawanie +

Operator dodawania

Metoda __add__ pozwala użyć operatora + na własnych obiektach. Jest wywoływana, gdy po lewej stronie + znajduje się nasz obiekt.

Powinna zwracać nowy obiekt (nie modyfikować self ), zgodnie z zasadą niemutowalności, którą stosują wbudowane typy (np. int , str ).

Oprócz __add__ istnieją też dwie pokrewne metody:

  • __radd__ (right add) - wywoływana, gdy nasz obiekt jest po prawej stronie + , a lewa strona nie obsłużyła dodawania. Przykład: 5 + moj_obiekt wywoła moj_obiekt.__radd__(5) .
  • __iadd__ (in-place add) - wywoływana przy użyciu += . Może modyfikować obiekt w miejscu (dla mutowalnych) lub zwracać nowy (dla niemutowalnych).

Mechanizm wywołania dla a + b wygląda następująco:

  1. Python próbuje a.__add__(b)
  2. Jeśli zwróci NotImplemented, próbuje b.__radd__(a)
  3. Jeśli obie zwrócą NotImplemented , Python zgłasza TypeError

Dzięki temu możesz zdefiniować dodawanie symetryczne, np. Kwota(100) + 30 (przez __add__ ) i 30 + Kwota(100) (przez __radd__ ).

__add__

Slajd zatytułowany "__add__ - dodawanie +" przedstawia istotne zagadnienie 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.

34 / 50 Kod: __add__ w klasie

Definiujemy __add__

class Wektor2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        if not isinstance(other, Wektor2D):
            return NotImplemented
        return Wektor2D(self.x + other.x,
                         self.y + other.y)

    def __repr__(self):
        return f"Wektor2D({self.x}, {self.y})"

v1 = Wektor2D(1, 2)
v2 = Wektor2D(3, 4)
v3 = v1 + v2
print(v3)  # Wektor2D(4, 6)

Często przydatne jest też dodawanie skalara do wektora (lub wektora do skalara). W tym celu musimy rozszerzyć __add__ o obsługę liczb oraz zdefiniować __radd__ :

# Rozszerzenie Wektor2D o dodawanie skalara
class Wektor2DSkalar(Wektor2D):
    def __add__(self, other):
        if isinstance(other, Wektor2DSkalar):
            return Wektor2DSkalar(self.x + other.x, self.y + other.y)
        if isinstance(other, (int, float)):
            return Wektor2DSkalar(self.x + other, self.y + other)
        return NotImplemented

    def __radd__(self, other):
        if isinstance(other, (int, float)):
            return Wektor2DSkalar(self.x + other, self.y + other)
        return NotImplemented

v = Wektor2DSkalar(2, 3)
print(v + 5)   # Wektor2DSkalar(7, 8)  - przez __add__
print(5 + v)   # Wektor2DSkalar(7, 8)  - przez __radd__

Dzięki __radd__ operacja 5 + v działa tak samo jak v + 5 . To ważne dla symetrii operatorów - użytkownicy twojej klasy nie muszą pamiętać, po której stronie + może stać twój obiekt.

Kod __add__

Slajd zatytułowany "Kod: __add__ w klasie" przedstawia istotne zagadnienie 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.

35 / 50 Kod: dodawanie dwóch obiektów

Praktyczny przykład

class Kwota:
    def __init__(self, wartosc):
        self.wartosc = wartosc

    def __add__(self, other):
        if isinstance(other, Kwota):
            return Kwota(self.wartosc + other.wartosc)
        if isinstance(other, (int, float)):
            return Kwota(self.wartosc + other)
        return NotImplemented

    def __repr__(self):
        return f"Kwota({self.wartosc})"

k1 = Kwota(100)
k2 = Kwota(50)
print(k1 + k2)    # Kwota(150)
print(k1 + 30)   # Kwota(130)
# print(30 + k1)  # TypeError! (int nie zna Kwota)

Ostatni komentowany wiersz pokazuje problem - 30 + k1 nie działa, bo int.__add__(30, k1) zwraca NotImplemented , a Kwota nie ma zdefiniowanego __radd__ . Rozwiązaniem jest dodanie __radd__ :

# Dodajemy __radd__ do klasy Kwota
    def __radd__(self, other):
        # other to wartość liczbowa (int/float)
        return Kwota(self.wartosc + other)

# Teraz działa:
print(30 + k1)  # Kwota(130) - przez __radd__

# Działa też dodawanie wielu Kwot z int:
wynik = k1 + k2 + 20 + 5
print(wynik)  # Kwota(175)

Zauważ, że __radd__ jest wywoływane tylko wtedy, gdy lewa strona ( int ) nie umie obsłużyć dodawania z prawą stroną ( Kwota ). Python automatycznie odwraca argumenty: 30 + k1 staje się k1.__radd__(30) .

Dla operatorów arytmetycznych istnieją trzy warianty: normalny ( __add__ ), odwrócony ( __radd__ ) i w miejscu ( __iadd__ ). Ten sam schemat dotyczy __sub__ / __rsub__ / __isub__ , __mul__ / __rmul__ / __imul__ i innych.
Dodawanie obiektów

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ą.

36 / 50 Kod: łączenie łańcuchowe

Łączenie łańcuchowe z __add__

class Lancuch:
    def __init__(self, tekst):
        self.tekst = tekst

    def __add__(self, other):
        return Lancuch(self.tekst + other.tekst)

    def __str__(self):
        return self.tekst

cz1 = Lancuch("Hello")
cz2 = Lancuch(" ")
cz3 = Lancuch("World")
cz4 = Lancuch("!")

wynik = cz1 + cz2 + cz3 + cz4
print(wynik)  # Hello World!

# Działa, bo cz1 + cz2 zwraca Lancuch,
# który znów ma __add__

Łączenie łańcuchowe (chaining) działa, ponieważ każdy + zwraca nowy obiekt tej samej klasy, który ma zdefiniowane __add__ . Python oblicza wyrażenie od lewej do prawej: ((cz1 + cz2) + cz3) + cz4 . Każde pośrednie wyrażenie to pełnoprawny Lancuch , więc można do niego dodawać kolejne elementy.

To potężny wzorzec projektowy znany jako Builder Pattern lub Fluent Interface . Możesz go wykorzystać nie tylko do łączenia tekstów, ale także do budowania złożonych obiektów krok po kroku:

# Fluent interface z __add__ - budowanie zapytania SQL
class Zapytanie:
    def __init__(self, klauzula=""):
        self.klauzula = klauzula

    def __add__(self, other):
        if isinstance(other, str):
            return Zapytanie(self.klauzula + " " + other)
        return NotImplemented

    def __str__(self):
        return self.klauzula.strip()

# Budowanie zapytania przez łączenie:
q = Zapytanie() + "SELECT *" + "FROM users" + "WHERE age > 18"
print(q)  # SELECT * FROM users WHERE age > 18

Kluczowa zaleta łączenia łańcuchowego: kod staje się deklaratywny i czytelny. Zamiast serii osobnych instrukcji, mamy jedno wyrażenie, które mówi "co" ma powstać.

Łączenie łańcuchowe

Slajd zatytułowany "Kod: łączenie łańcuchowe" przedstawia istotne zagadnienie 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.

37 / 50 Podsumowanie __add__

Co zapamiętać?

  • __add__ definiuje operator +.
  • Powinna zwracać nowy obiekt, nie modyfikować self.
  • Dla symetrii możesz zdefiniować __radd__ (gdy nasz obiekt jest po prawej stronie + ).
  • Dla operatora += zdefiniuj __iadd__ (opcjonalnie - jeśli go brak, Python używa __add__ ).
  • Możliwe jest łączenie łańcuchowe (a + b + c).
  • Podobnie działają __sub__ , __mul__ , __truediv__ - każdy ma też wariant __r*__ i __i*__ .
  • __add__ powinno akceptować tylko typy, które mają sens dodawać - w przeciwnym razie zwróć NotImplemented .
  • Dla mutowalnych klas __iadd__ może modyfikować self i zwracać self (oszczędność pamięci).
# Przykład __iadd__ dla mutowalnej klasy
class MutowalnyKoszyk:
    def __init__(self):
        self.produkty = []

    def __iadd__(self, produkt):
        self.produkty.append(produkt)
        return self  # ważne: zwracamy self!

    def __str__(self):
        return f"Koszyk: {', '.join(self.produkty)}"

k = MutowalnyKoszyk()
k += "jabłko"
k += "chleb"
print(k)  # Koszyk: jabłko, chleb
# __iadd__ modyfikuje obiekt w miejscu - oszczędza alokację pamięci
Podsumowanie __add__

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.

38 / 50 __getitem__ - indeksowanie []

Dostęp przez nawiasy kwadratowe

Metoda __getitem__ pozwala używać składni obiekt[klucz] do pobierania elementów. Jest to podstawa działania list, słowników i innych kolekcji.

Przyjmuje jeden argument - klucz (może to być liczba, łańcuch, wycinek slice itd.).

__getitem__ jest częścią większego protokołu kolekcji w Pythonie. W skład tego protokołu wchodzą:

  • __getitem__ - odczyt:obj[key]
  • __setitem__ - zapis:obj[key] = value
  • __delitem__ - usuwanie:del obj[key]
  • __contains__ - operator in : value in obj
  • __len__ - rozmiar:len(obj)
  • __iter__ - iteracja:for x in obj

Co ciekawe, Python ma mechanizm awaryjny: jeśli klasa definiuje __getitem__ ale nie definiuje __iter__ , pętla for i tak będzie działać! Python będzie iterował po indeksach od 0, wywołując __getitem__ , aż do wystąpienia IndexError . To sprawia, że __getitem__ jest bardzo potężną metodą - sama w sobie umożliwia iterację.

__getitem__ działa również z wycinkami (slices). Gdy użyjesz składni obj[1:5:2] , Python tworzy obiekt slice(1, 5, 2) i przekazuje go jako argument do __getitem__ . Możesz obsłużyć slice w swojej implementacji, sprawdzając typ klucza: isinstance(key, slice) .
__getitem__

Slajd zatytułowany "__getitem__ - indeksowanie []" przedstawia istotne zagadnienie 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.

39 / 50 Kod: __getitem__ w klasie

Definiujemy __getitem__

class Biblioteka:
    def __init__(self):
        self.ksiazki = {}

    def dodaj(self, isbn, tytul):
        self.ksiazki[isbn] = tytul

    def __getitem__(self, klucz):
        return self.ksiazki.get(klucz,
                           f"Brak książki o ISBN {klucz}")

b = Biblioteka()
b.dodaj("978-83-01-00001-1", "Python 101")
print(b["978-83-01-00001-1"])  # Python 101
print(b["000"])                   # Brak książki o ISBN 000

W tym przykładzie zdecydowaliśmy się zwracać domyślny komunikat zamiast zgłaszać wyjątek KeyError . To kwestia wyboru projektowego - słowniki Pythona domyślnie zgłaszają KeyError , co jest zgodne z zasadą "lepiej prosić o wybaczenie niż o pozwolenie" (EAFP). Możesz jednak dostarczyć wartość domyślną, podobnie jak robi to metoda dict.get() .

Jeśli chcesz naśladować standardowe słowniki, lepiej zgłaszać KeyError dla nieistniejących kluczy:

# Wersja zgłaszająca KeyError (zgodna z konwencją Pythona)
class Biblioteka2:
    def __init__(self):
        self.ksiazki = {}

    def dodaj(self, isbn, tytul):
        self.ksiazki[isbn] = tytul

    def __getitem__(self, klucz):
        if klucz not in self.ksiazki:
            raise KeyError(f"Brak książki o ISBN {klucz}")
        return self.ksiazki[klucz]

b2 = Biblioteka2()
b2.dodaj("978-83-01-00001-1", "Python 101")
# b2["000"]  # KeyError: 'Brak książki o ISBN 000'

Które podejście wybrać? Jeśli twoja klasa ma udawać słownik lub listę, stosuj standardowe wyjątki ( KeyError , IndexError ). Jeśli tworzysz interfejs "bezpieczniejszy" dla użytkownika, wartości domyślne mogą być lepsze.

Kod __getitem__

Slajd zatytułowany "Kod: __getitem__ w klasie" przedstawia istotne zagadnienie 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.

40 / 50 Kod: obiekt[klucz]

Indeksowanie w praktyce

class ListaPakietowa:
    def __init__(self, *elementy):
        self.elementy = list(elementy)

    def __getitem__(self, indeks):
        return self.elementy[indeks]

    def __len__(self):
        return len(self.elementy)

lista = ListaPakietowa(10, 20, 30, 40, 50)
print(lista[0])     # 10
print(lista[-1])    # 50
print(lista[1:3])  # [20, 30] (slice)
print(len(lista))  # 5

Zwróć uwagę, że wycinek (slice) lista[1:3] działa automatycznie, ponieważ delegujemy __getitem__ do wewnętrznej listy, która obsługuje slice. Gdybyś implementował __getitem__ od zera, musiałbyś jawnie obsłużyć obiekt slice :

# Obsługa slice w __getitem__
class ListaRecznie:
    def __init__(self, *args):
        self.dane = list(args)

    def __getitem__(self, key):
        if isinstance(key, slice):
            start = key.start or 0
            stop = key.stop or len(self.dane)
            step = key.step or 1
            return ListaRecznie(*self.dane[start:stop:step])
        return self.dane[key]

Co więcej, mając __getitem__ i __len__ , twój obiekt automatycznie wspiera iterację. Python wywołuje __getitem__ z indeksami 0, 1, 2, ... aż do wystąpienia IndexError :

# Iteracja działa bez __iter__ dzięki __getitem__
for x in ListaPakietowa(1, 2, 3):
    print(x)  # 1 2 3

# Działa też rozpakowywanie
a, b, c = ListaPakietowa(10, 20, 30)

To zachowanie jest przykładem duck typingu w Pythonie - jeśli obiekt zachowuje się jak kolekcja (ma __getitem__ i __len__ ), to jest traktowany jak kolekcja.

obiekt[klucz]

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ą.

41 / 50 Podsumowanie __getitem__

Co zapamiętać?

  • __getitem__ umożliwia składnię obiekt[klucz].
  • Kluczem może być liczba, łańcuch,slice lub dowolny inny typ.
  • Razem z __len__ i __iter__ tworzy kompletny interfejs kolekcji.
  • Możesz też zdefiniować __setitem__ i __delitem__ dla zapisu i usuwania.
  • W połączeniu z __iter__ umożliwia pętlę for.
  • Jeśli brak __iter__ , Python używa __getitem__ z indeksami 0, 1, 2, ...
  • Dla nieistniejących kluczy zgłaszaj KeyError (słownik) lub IndexError (lista).
  • Obsługa slice wymaga sprawdzenia isinstance(key, slice).
# Klasa zachowująca się jak słownik - pełny protokół
class Rejestr:
    def __init__(self):
        self._dane = {}

    def __getitem__(self, key):
        return self._dane[key]  # KeyError jeśli brak

    def __setitem__(self, key, value):
        self._dane[key] = value

    def __delitem__(self, key):
        del self._dane[key]

    def __contains__(self, key):
        return key in self._dane

    def __len__(self):
        return len(self._dane)

r = Rejestr()
r["imie"] = "Anna"     # __setitem__
print(r["imie"])           # __getitem__ → Anna
print("imie" in r)        # __contains__ → True
print(len(r))              # __len__ → 1

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.

42 / 50 __bool__ - wartość logiczna

Obiekty w kontekście logicznym

Metoda __bool__ definiuje, jak obiekt zachowuje się w kontekście logicznym ( if , while , and , or , not ).

Jeśli nie zdefiniujesz __bool__ , Python użyje __len__ - długość 0 daje False . Jeśli nie ma obu, obiekt jest zawsze True .

class Konto:
    def __init__(self, saldo):
        self.saldo = saldo

    def __bool__(self):
        return self.saldo > 0

k = Konto(100)
if k:
    print("Konto aktywne")  # wydrukuje

Metoda __bool__ daje pełną kontrolę nad tym, co oznacza "prawda" w kontekście twojej klasy. W przykładzie konto jest True tylko wtedy, gdy saldo jest dodatnie. Gdybyśmy nie zdefiniowali __bool__ , Python użyłby __len__ - a to nie miałoby sensu dla konta, bo długość nie jest zdefiniowana.

Ważna hierarchia decyzyjna Pythona dla kontekstu logicznego:

  1. Jeśli obiekt ma __bool__, Python woła je i zwraca wynik.
  2. Jeśli nie ma __bool__ , ale ma __len__ , Python sprawdza, czy len(obiekt) != 0 .
  3. Jeśli nie ma żadnej z tych metod, obiekt jest zawsze True.

To dlatego wszystkie kolekcje w Pythonie (listy, słowniki, zbiory, krotki) są False gdy puste - definiują __len__ , a nie __bool__ . Dla twoich własnych klas zastanów się, które zachowanie jest bardziej naturalne.

# __bool__ z dodatkową logiką
class Zamowienie:
    def __init__(self, pozycje, zaplacone):
        self.pozycje = pozycje
        self.zaplacone = zaplacone

    def __bool__(self):
        return len(self.pozycje) > 0 and self.zaplacone

z1 = Zamowienie(["laptop"], True)
z2 = Zamowienie([], True)
z3 = Zamowienie(["mysz"], False)

for z in [z1, z2, z3]:
    print(bool(z))  # True, False, False

Dzięki __bool__ możesz wyrazić złożoną logikę biznesową w jednym, czytelnym warunku.

__bool__

Slajd zatytułowany "__bool__ - wartość logiczna" przedstawia istotne zagadnienie 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.

43 / 50 __hash__ - haszowanie

Obiekty jako klucze w słowniku

Metoda __hash__ zwraca liczbę całkowitą (hash), używaną do szybkiego porównywania obiektów w słownikach i zbiorach.

Jeśli definiujesz __eq__ , Python automatycznie ustawia __hash__ na None - obiekt staje się niehaszowalny. Aby móc go używać jako klucz, musisz jawnie zdefiniować __hash__ .

class Osoba:
    def __init__(self, pesel, imie):
        self.pesel = pesel
        self.imie = imie
    def __eq__(self, o):
        return self.pesel == o.pesel
    def __hash__(self):
        return hash(self.pesel)

zbior = {Osoba("123", "Ala"), Osoba("456", "Ela")}

Zwróć uwagę na kluczową zależność między __eq__ i __hash__ : jeśli dwa obiekty są równe ( __eq__ zwraca True ), muszą mieć ten sam hash. W przeciwnym razie słowniki i zbiory przestałyby działać poprawnie. Python wymaga tej spójności - jeśli definiujesz __eq__ bez __hash__ , Python ustawia __hash__ na None , czyniąc obiekt niehaszowalnym.

W przykładzie haszujemy po pesel (numer PESEL jest unikalny i niezmienny), a równość też sprawdzamy po PESEL. To idealne dopasowanie: dwa obiekty Osoba z tym samym PESElem są równe i mają ten sam hash, więc mogą być używane jako klucze w słowniku.

Kilka zasad dotyczących __hash__:

  • Hash powinien być stały przez cały czas życia obiektu - nie używaj atrybutów mutowalnych.
  • Jeśli obiekt jest mutowalny, nie powinien mieć __hash__ (lub hash powinien być oparty na niezmiennym identyfikatorze).
  • Dobry hash jest szybki do obliczenia i równomiernie rozkłada wartości.
  • Jeśli nadpisujesz __eq__ , prawie zawsze powinieneś też nadpisać __hash__ - albo jawnie, albo ustawiając __hash__ = None (jeśli obiekt ma być niehaszowalny).
Zbiory i słowniki używają hashy do szybkiego wyszukiwania. Dwa obiekty o tym samym hash trafiają do tego samego "kubełka" (bucket). Jeśli wiele obiektów ma ten sam hash (kolizja), wyszukiwanie spowalnia. Staraj się, aby hash był dobrze rozproszony - np. używając hash() na krotce atrybutów.
__hash__

Slajd zatytułowany "__hash__ - haszowanie" przedstawia istotne zagadnienie 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 / 50 __enter__, __exit__ - context manager

Obsługa with

Metody __enter__ i __exit__ pozwalają używać obiektu w konstrukcji with , automatycznie zarządzając zasobami.

To jeden z najważniejszych wzorców w Pythonie - gwarantuje, że zasoby (pliki, połączenia sieciowe, blokady) zostaną prawidłowo zwolnione nawet w przypadku wystąpienia wyjątku.

class Drukarka:
    def __enter__(self):
        print("Otwieram połączenie z drukarką")
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("Zamykam połączenie z drukarką")

    def drukuj(self, tekst):
        print(f"Drukuję: {tekst}")

with Drukarka() as d:
    d.drukuj("Hello World")
# Otwieram połączenie z drukarką
# Drukuję: Hello World
# Zamykam połączenie z drukarką

Bardziej realistyczny przykład - menedżer kontekstu dla pliku z automatycznym logowaniem:

import time

class LogowanyPlik:
    def __init__(self, nazwa, tryb):
        self.nazwa = nazwa
        self.tryb = tryb
        self.plik = None

    def __enter__(self):
        print(f"[{time.ctime()}] Otwieram {self.nazwa}")
        self.plik = open(self.nazwa, self.tryb)
        return self.plik

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"[{time.ctime()}] Zamykam {self.nazwa}")
        if self.plik:
            self.plik.close()
        if exc_type:
            print(f"Wystąpił błąd: {exc_val}")
        return False  # nie tłumimy wyjątku

# Użycie - nawet przy błędzie plik zostanie zamknięty
try:
    with LogowanyPlik("dane.txt", "r") as f:
        dane = f.read()
except FileNotFoundError:
    print("Plik nie istnieje, ale i tak został zamknięty")

Parametry __exit__ ( exc_type , exc_val , exc_tb ) dostarczają informacji o wyjątku, jeśli wystąpił wewnątrz bloku with . Gdy nie ma wyjątku, wszystkie są None . Jeśli __exit__ zwróci True , wyjątek zostanie stłumiony - używaj tego ostrożnie!

Context manager

Slajd zatytułowany "__enter__, __exit__ - context manager" przedstawia istotne zagadnienie 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.

45 / 50 Podsumowanie - inne przydatne metody

Co zapamiętać?

  • __bool__ - określa prawdziwość obiektu (if obiekt:).
  • __hash__ - niezbędny do używania obiektów jako kluczy w dict / set .
  • __enter__ / __exit__ - implementują menedżer kontekstu ( with ).
  • Jeśli definiujesz __eq__ , rozważ też __hash__ (lub zaakceptuj, że obiekt będzie niehaszowalny).
  • Context manager jest idealny do zarządzania plikami, połączeniami sieciowymi, blokadami.
  • Hierarchia truthiness: __bool__ > __len__ > zawsze True .
  • Hash i równość muszą być spójne: jeśli a == b , to hash(a) == hash(b) .
  • __exit__ otrzymuje trzy argumenty: typ wyjątku, wartość i traceback - możesz na nie reagować.
  • Zwracając True z __exit__ , tłumisz wyjątek - rób to tylko świadomie.
# Wszystkie trzy metody w jednej klasie
class BezpieczneDane:
    def __init__(self, wartosc):
        self.wartosc = wartosc
        self._zablokowany = False

    def __bool__(self):
        return self.wartosc is not None

    def __hash__(self):
        return hash(id(self))

    def __enter__(self):
        self._zablokowany = True
        return self

    def __exit__(self, *args):
        self._zablokowany = False

d = BezpieczneDane(42)
print(bool(d))      # True
print(hash(d))       # unikalna liczba
with d as bd:
    print(bd.wartosc)  # 42
Podsumowanie inne

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 testowaniu 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.

46 / 50 Praktyczny przykład: Wektor2D

Pełna klasa Wektor2D z metodami magicznymi

Zobaczymy, jak wiele metod magicznych można zaimplementować w jednej klasie, aby zachowywała się jak wbudowany typ.

Celem jest uzyskanie wektora, który można: wyświetlić, porównać, dodać, odjąć, pomnożyć przez skalar, sprawdzić długość i indeksować.

Klasa Wektor2D to klasyczny przykład użycia metod magicznych w praktyce. Reprezentuje punkt w przestrzeni dwuwymiarowej i demonstruje, jak przeciążanie operatorów czyni kod matematycznie naturalnym. Zamiast pisać wektor.dodaj(inny) piszemy v1 + v2 , zamiast wektor.pokaz() - print(wektor) .

W tej klasie zaimplementujemy następujące metody magiczne:

  • __init__ - konstruktor
  • __repr__ i __str__ - reprezentacja
  • __eq__ - porównanie
  • __lt__ - sortowanie po długości
  • __add__ i __sub__ - dodawanie i odejmowanie
  • __mul__ i __rmul__ - mnożenie przez skalar
  • __getitem__ - indeksowanie (0 → x, 1 → y)
  • __len__ - zawsze 2 (wymiar wektora)
  • __bool__ - wektor zerowy → False

To pokazuje, że nawet prosta klasa może zyskać bogaty interfejs dzięki metodom magicznym.

Wektor2D

Slajd zatytułowany "Praktyczny przykład: Wektor2D" przedstawia istotne zagadnienie 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.

47 / 50 Kod: pełna definicja Wektor2D

Implementacja

class Wektor2D:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Wektor2D({self.x}, {self.y})"

    def __str__(self):
        return f"[{self.x}, {self.y}]"

    def __eq__(self, other):
        if not isinstance(other, Wektor2D):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __lt__(self, other):
        if not isinstance(other, Wektor2D):
            return NotImplemented
        return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)

    def __sub__(self, other):
        if not isinstance(other, Wektor2D):
            return NotImplemented
        return Wektor2D(self.x - other.x, self.y - other.y)

    def __mul__(self, other):
        if isinstance(other, (int, float)):
            return Wektor2D(self.x * other, self.y * other)
        return NotImplemented

    def __rmul__(self, other):
        return Wektor2D(self.x * other, self.y * other)

    def __bool__(self):
        return self.x != 0 or self.y != 0

Teraz nasz wektor obsługuje odejmowanie ( __sub__ ), mnożenie przez skalar ( __mul__ i __rmul__ ) oraz test prawdziwości ( __bool__ ). __rmul__ zapewnia, że zarówno v * 3 jak i 3 * v działają poprawnie.

Zauważ, że __bool__ zwraca False tylko dla wektora zerowego (0, 0). To intuicyjne - każdy niezerowy wektor jest "prawdziwy".

Kod Wektor2D

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.

48 / 50 Kod: testowanie Wektor2D

Wszystkie operacje w akcji

# kontynuacja klasy Wektor2D...

    def __add__(self, other):
        if not isinstance(other, Wektor2D):
            return NotImplemented
        return Wektor2D(self.x + other.x, self.y + other.y)

    def __getitem__(self, i):
        if i == 0: return self.x
        if i == 1: return self.y
        raise IndexError("Indeks spoza zakresu")

    def __len__(self):
        return 2

v1 = Wektor2D(3, 4)
v2 = Wektor2D(1, 2)
print(v1)             # [3, 4]           (__str__)
print(v1 + v2)       # Wektor2D(4, 6)   (__repr__ po __add__)
print(v1 == Wektor2D(3, 4))  # True   (__eq__)
print(v1 < v2)       # False  (__lt__)
print(v1[0], v1[1])   # 3 4    (__getitem__)
print(len(v1))        # 2      (__len__)

Testujemy nowe operacje:

# Dodatkowe testy nowych metod
print(v1 - v2)                  # Wektor2D(2, 2)   (__sub__)
print(v1 * 3)                  # Wektor2D(9, 12)  (__mul__)
print(3 * v1)                  # Wektor2D(9, 12)  (__rmul__)
print(bool(Wektor2D(0, 0)))     # False           (__bool__ - zerowy)
print(bool(Wektor2D(1, 0)))     # True            (__bool__ - niezerowy)
print(len(v1))                  # 2              (__len__)

# Sortowanie wektorów po długości
wektory = [Wektor2D(3, 4), Wektor2D(1, 1), Wektor2D(0, 5)]
wektory.sort()
print(wektory)  # [Wektor2D(1, 1), Wektor2D(3, 4), Wektor2D(0, 5)]
# Posortowane według długości: sqrt(2) < 5 < 5 (długości: 1.41, 5.0, 5.0)

Nasza klasa Wektor2D to teraz w pełni funkcjonalny typ danych, który zachowuje się jak wbudowany. Można go wyświetlać, porównywać, sortować, dodawać, odejmować, mnożyć przez skalar, indeksować, sprawdzać jego długość i używać w kontekście logicznym - wszystko dzięki metodom magicznym.

To jest właśnie siła metod magicznych: jedna klasa z kilkunastoma metodami dunder może zastąpić dziesiątki funkcji pomocniczych, oferując przy tym znacznie czystszy i bardziej intuicyjny interfejs.
Testowanie Wektor2D

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 / 50 Mapa myśli metod magicznych

Mapa myśli - kategorie metod dunder

Reprezentacja: __str__print() , __repr__repr()

Kontener: __len__len() , __getitem__obj[k] , __setitem__ , __delitem__

Porównania: __eq__== , __ne__!= , __lt__< , plus __le__ , __gt__ , __ge__

Arytmetyka: __add__+ , __sub__- , __mul__* , __truediv__/

Konwersja: __int__ , __float__ , __bool__ , __str__

Zarządzanie zasobami: __enter__ , __exit__with

Inne: __hash__ , __call__ , __iter__ , __next__

Arytmetyka odwrócona i w miejscu: __radd__ , __rsub__ , __rmul__ (gdy nasz typ po prawej), __iadd__ , __isub__ (dla += , -= )

Obsługa atrybutów: __getattr__ ← gdy brak atrybutu, __setattr__obj.a = val , __delattr__del obj.a

Mapa myśli pokazuje, jak bogaty jest ekosystem metod dunder. Każda kategoria odpowiada za inny aspekt zachowania obiektu. W codziennej praktyce najczęściej używanymi metodami są: __init__ , __str__ , __repr__ , __eq__ , __len__ i __getitem__ . Resztę dodawaj w miarę potrzeb swojej domeny.

Nie wszystkie kategorie muszą być zaimplementowane w każdej klasie. Wybieraj te metody magiczne, które są naturalne dla twojego typu danych. Wektor naturalnie wspiera + i - , ale klasa Uzytkownik już nie. Kieruj się zdrowym rozsądkiem i zasadą najmniejszego zaskoczenia.
Mapa myśli

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 testowaniu 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.

50 / 50Quiz - 3 pytania

Sprawdź swoją wiedzę

Pytanie 1: Która metoda magiczna jest wywoływana przez funkcję print() ?

a) __repr__
b) __str__
c) __len__


Pytanie 2: Co zwróci Karta('kier','A') == Karta('kier','A') jeśli w klasie Karta zdefiniowano __eq__ porównujące kolor i wartość?

a) True
b) False
c) None


Pytanie 3: Która metoda musi być zdefiniowana, aby obiekty danej klasy można było sortować?

a) __add__
b) __eq__
c) __lt__

Odpowiedzi: 1b, 2a, 3c. Metody magiczne to potężne narzędzie - ćwicz ich używanie w swoich projektach!
Quiz

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.