Witaj ponownie, @name! Darwin tutaj z fascynującą lekcją o ukrytych mocach Pythona.
W naturze zwierzęta mają ukryte zachowania - lew ryczy, słoń trąbi, kamele on się maskuje. W Pythonie klasy też mogą mieć ukryte zachowania przez metody magiczne (magic methods) - specjalne metody z podwójnym podkreśleniem!
1lion = Animal("Lew")
2print(lion) # Jak Python wie, co wyświetlić? __str__!
3len(lion) # Jak Python wie, co zmierzyć? __len__!
4lion + elephant # Jak Python wie, co dodać? __add__!Metody magiczne (zwane też dunder methods od "double underscore") to specjalne metody z nazwami w formacie
__method__, które Python automatycznie wywołuje w określonych sytuacjach.Analogia Safari: To jak naturalne instynkty zwierząt - nie musisz ich uczyć, są wbudowane w ich naturę!
1class Animal:
2 def __init__(self, name):
3 """Wywoływane przy TWORZENIU obiektu"""
4 self.name = name
5
6 def __str__(self):
7 """Wywoływane przez print() i str()"""
8 return f"Animal: {self.name}"
9
10 def __len__(self):
11 """Wywoływane przez len()"""
12 return len(self.name)
13
14lion = Animal("Lew") # Wywołuje __init__
15print(lion) # Wywołuje __str__
16print(len(lion)) # Wywołuje __len__Dlaczego "magiczne"?
__init__ - KonstruktorJuż znasz! Wywoływana przy tworzeniu obiektu.
1class Species:
2 def __init__(self, name, population):
3 """Inicjalizacja - już to znasz!"""
4 self.name = name
5 self.population = population
6
7lion = Species("Lew", 500) # Wywołuje __init____str__ - Reprezentacja czytelna dla ludziWywoływana przez
print() i str() - zwraca string przyjazny użytkownikowi.1class Animal:
2 def __init__(self, name, age):
3 self.name = name
4 self.age = age
5
6 def __str__(self):
7 """Dla ludzi - czytelne"""
8 return f"{self.name} (wiek: {self.age} lat)"
9
10lion = Animal("Simba", 3)
11print(lion) # "Simba (wiek: 3 lat)" - wywołuje __str__
12print(str(lion)) # To samo__repr__ - Reprezentacja dla programistówWywoływana przez
repr() i w konsoli - zwraca jednoznaczną reprezentację obiektu.1class Animal:
2 def __init__(self, name, age):
3 self.name = name
4 self.age = age
5
6 def __repr__(self):
7 """Dla programistów - precyzyjne"""
8 return f"Animal(name='{self.name}', age={self.age})"
9
10 def __str__(self):
11 """Dla użytkowników - czytelne"""
12 return f"{self.name} ({self.age} lat)"
13
14lion = Animal("Simba", 3)
15
16# W konsoli Python
17>>> lion
18Animal(name='Simba', age=3) # Wywołuje __repr__
19
20# W princie
21print(lion) # "Simba (3 lat)" - wywołuje __str__
22
23# repr() explicite
24print(repr(lion)) # "Animal(name='Simba', age=3)"Zasada:
__repr__ powinien zwracać kod, którym można odtworzyć obiekt!1class Point:
2 def __init__(self, x, y):
3 self.x = x
4 self.y = y
5
6 def __repr__(self):
7 return f"Point({self.x}, {self.y})"
8
9p = Point(3, 4)
10print(repr(p)) # "Point(3, 4)" - możesz skopiować i wkleić!Pozwalają porównywać obiekty z
==, <, >, etc.1class Animal:
2 def __init__(self, name, weight):
3 self.name = name
4 self.weight = weight # kg
5
6 def __eq__(self, other):
7 """Równość: =="""
8 if not isinstance(other, Animal):
9 return False
10 return self.weight == other.weight
11
12 def __lt__(self, other):
13 """Mniejsze niż: <"""
14 if not isinstance(other, Animal):
15 return NotImplemented
16 return self.weight < other.weight
17
18 def __le__(self, other):
19 """Mniejsze lub równe: <="""
20 return self.weight <= other.weight
21
22 def __gt__(self, other):
23 """Większe niż: >"""
24 return self.weight > other.weight
25
26 def __ge__(self, other):
27 """Większe lub równe: >="""
28 return self.weight >= other.weight
29
30 def __ne__(self, other):
31 """Nierówne: !="""
32 return not self.__eq__(other)
33
34lion = Animal("Lew", 190)
35elephant = Animal("Słoń", 5000)
36rhino = Animal("Nosorożec", 2300)
37
38print(lion < elephant) # True - wywołuje __lt__
39print(elephant > rhino) # True - wywołuje __gt__
40print(lion == Animal("Tygrys", 190)) # True - taka sama wagaTip: Możesz użyć dekoratora
@functools.total_ordering - zdefiniuj tylko __eq__ i jeden z __lt__, __le__, __gt__, __ge__, reszta zostanie automatycznie wygenerowana!1from functools import total_ordering
2
3@total_ordering
4class Animal:
5 def __init__(self, name, weight):
6 self.name = name
7 self.weight = weight
8
9 def __eq__(self, other):
10 """Tylko to i __lt__ - reszta automatycznie!"""
11 if not isinstance(other, Animal):
12 return False
13 return self.weight == other.weight
14
15 def __lt__(self, other):
16 """Tylko to i __eq__ - reszta automatycznie!"""
17 if not isinstance(other, Animal):
18 return NotImplemented
19 return self.weight < other.weight
20
21 # __le__, __gt__, __ge__, __ne__ - automatycznie wygenerowane!Pozwalają używać operatorów matematycznych z obiektami!
1class Population:
2 """Reprezentuje populację gatunku"""
3
4 def __init__(self, species, count):
5 self.species = species
6 self.count = count
7
8 def __add__(self, other):
9 """Dodawanie: +"""
10 if isinstance(other, Population):
11 if self.species != other.species:
12 raise ValueError("Różne gatunki!")
13 return Population(self.species, self.count + other.count)
14 elif isinstance(other, int):
15 return Population(self.species, self.count + other)
16 return NotImplemented
17
18 def __sub__(self, other):
19 """Odejmowanie: -"""
20 if isinstance(other, int):
21 return Population(self.species, max(0, self.count - other))
22 return NotImplemented
23
24 def __mul__(self, other):
25 """Mnożenie: *"""
26 if isinstance(other, (int, float)):
27 return Population(self.species, int(self.count * other))
28 return NotImplemented
29
30 def __str__(self):
31 return f"{self.species}: {self.count} osobników"
32
33# Użycie operatorów!
34lions_north = Population("Lew", 120)
35lions_south = Population("Lew", 85)
36
37# Dodawanie populacji
38total_lions = lions_north + lions_south
39print(total_lions) # "Lew: 205 osobników"
40
41# Dodawanie liczby
42more_lions = lions_north + 30
43print(more_lions) # "Lew: 150 osobników"
44
45# Odejmowanie (zmniejszenie populacji)
46fewer_lions = lions_north - 20
47print(fewer_lions) # "Lew: 100 osobników"
48
49# Mnożenie (szacowanie wzrostu)
50projected = lions_north * 1.5 # Wzrost o 50%
51print(projected) # "Lew: 180 osobników"Sprawiają, że Twoje obiekty zachowują się jak listy, słowniki!
__len__ - Długość1class Pack:
2 """Stado zwierząt"""
3
4 def __init__(self, species):
5 self.species = species
6 self.members = []
7
8 def add(self, name):
9 self.members.append(name)
10
11 def __len__(self):
12 """Wywołane przez len()"""
13 return len(self.members)
14
15pack = Pack("Lwy")
16pack.add("Simba")
17pack.add("Nala")
18pack.add("Mufasa")
19
20print(len(pack)) # 3 - wywołuje __len____getitem__ i __setitem__ - Dostęp przez indeks1class Habitat:
2 """Siedlisko ze zwierzętami"""
3
4 def __init__(self, name):
5 self.name = name
6 self.animals = []
7
8 def __getitem__(self, index):
9 """Dostęp do odczytu: habitat[0]"""
10 return self.animals[index]
11
12 def __setitem__(self, index, value):
13 """Dostęp do zapisu: habitat[0] = "Lew" """
14 self.animals[index] = value
15
16 def __len__(self):
17 return len(self.animals)
18
19 def __contains__(self, item):
20 """Operator 'in': "Lew" in habitat"""
21 return item in self.animals
22
23 def append(self, animal):
24 self.animals.append(animal)
25
26savanna = Habitat("Sawanna")
27savanna.append("Lew")
28savanna.append("Słoń")
29savanna.append("Żyrafa")
30
31# Dostęp jak do listy!
32print(savanna[0]) # "Lew" - wywołuje __getitem__
33savanna[1] = "Nosorożec" # Wywołuje __setitem__
34
35# Operator in
36print("Lew" in savanna) # True - wywołuje __contains__
37
38# len()
39print(len(savanna)) # 3 - wywołuje __len__
40
41# Iteracja (automatyczna przez __getitem__)
42for animal in savanna:
43 print(animal)__iter__ i __next__ - Iteracja1class Expedition:
2 """Iterator po dniach ekspedycji"""
3
4 def __init__(self, days):
5 self.days = days
6 self.current_day = 0
7
8 def __iter__(self):
9 """Zwróć iterator (siebie)"""
10 self.current_day = 0
11 return self
12
13 def __next__(self):
14 """Zwróć następny element"""
15 if self.current_day >= self.days:
16 raise StopIteration # Koniec iteracji
17
18 self.current_day += 1
19 return f"Dzień {self.current_day}"
20
21expedition = Expedition(5)
22
23# Iteracja przez for
24for day in expedition:
25 print(day)
26# Dzień 1
27# Dzień 2
28# Dzień 3
29# Dzień 4
30# Dzień 5
31
32# Lub manualnie
33exp2 = Expedition(3)
34print(next(exp2)) # "Dzień 1"
35print(next(exp2)) # "Dzień 2"
36print(next(exp2)) # "Dzień 3"
37# print(next(exp2)) # StopIteration__call__ - Obiekty wywoływalneSprawia, że obiekt można wywołać jak funkcję!
1class AnimalSound:
2 """Callable - obiekt jak funkcja"""
3
4 def __init__(self, species, sound):
5 self.species = species
6 self.sound = sound
7
8 def __call__(self, times=1):
9 """Wywołanie obiektu jak funkcji"""
10 return " ".join([self.sound] * times)
11
12lion_roar = AnimalSound("Lew", "ROAR")
13elephant_trumpet = AnimalSound("Słoń", "TRUUU")
14
15# Wywołaj jak funkcję!
16print(lion_roar()) # "ROAR" - wywołuje __call__
17print(lion_roar(3)) # "ROAR ROAR ROAR"
18print(elephant_trumpet(2)) # "TRUUU TRUUU"__enter__ i __exit__Pozwala używać
with statement!1class SafariCamera:
2 """Context manager do robienia zdjęć"""
3
4 def __init__(self, location):
5 self.location = location
6 self.photos = []
7
8 def __enter__(self):
9 """Wywoływane przy wejściu do bloku 'with'"""
10 print(f"📸 Włączam kamerę w {self.location}")
11 return self # Zwróć obiekt do użycia
12
13 def __exit__(self, exc_type, exc_val, exc_tb):
14 """Wywoływane przy wyjściu z bloku 'with'"""
15 print(f"💾 Zapisuję {len(self.photos)} zdjęć")
16 print(f"📴 Wyłączam kamerę")
17 return False # Nie tłum wyjątków
18
19 def take_photo(self, subject):
20 """Zrób zdjęcie"""
21 self.photos.append(subject)
22 print(f" 📷 Zdjęcie: {subject}")
23
24# Użycie z 'with' - automatyczne __enter__ i __exit__!
25with SafariCamera("Serengeti") as camera:
26 camera.take_photo("Lew polujący")
27 camera.take_photo("Stado słoni")
28 camera.take_photo("Żyrafa przy drzewie")
29# __exit__ wywołane automatycznie na końcu bloku!
30
31# Wyjście:
32# 📸 Włączam kamerę w Serengeti
33# 📷 Zdjęcie: Lew polujący
34# 📷 Zdjęcie: Stado słoni
35# 📷 Zdjęcie: Żyrafa przy drzewie
36# 💾 Zapisuję 3 zdjęć
37# 📴 Wyłączam kamerę1from functools import total_ordering
2from datetime import datetime
3
4@total_ordering
5class Species:
6 """
7 Klasa gatunku z pełnym zestawem metod magicznych
8
9 Zachowuje się jak wbudowany typ Pythona!
10 """
11
12 all_species = [] # Rejestr wszystkich gatunków
13
14 def __init__(self, scientific_name, common_name, population, habitat):
15 """Konstruktor"""
16 self.scientific_name = scientific_name
17 self.common_name = common_name
18 self.population = population
19 self.habitat = habitat
20 self.observations = []
21
22 Species.all_species.append(self)
23
24 # === REPREZENTACJA ===
25
26 def __str__(self):
27 """Dla print() - przyjazne dla człowieka"""
28 return f"{self.common_name} ({self.population} osobników)"
29
30 def __repr__(self):
31 """Dla repr() - jednoznaczne"""
32 return (f"Species(scientific_name='{self.scientific_name}', "
33 f"common_name='{self.common_name}', "
34 f"population={self.population}, "
35 f"habitat='{self.habitat}')")
36
37 # === PORÓWNANIA (tylko __eq__ i __lt__, reszta przez @total_ordering) ===
38
39 def __eq__(self, other):
40 """Równość: =="""
41 if not isinstance(other, Species):
42 return False
43 return self.population == other.population
44
45 def __lt__(self, other):
46 """Mniejsze niż: < (porównanie po populacji)"""
47 if not isinstance(other, Species):
48 return NotImplemented
49 return self.population < other.population
50
51 def __hash__(self):
52 """Hash - żeby działało w set() i dict keys"""
53 return hash(self.scientific_name)
54
55 # === ARYTMETYKA ===
56
57 def __add__(self, other):
58 """Dodawanie: species + 50"""
59 if isinstance(other, int):
60 return Species(
61 self.scientific_name,
62 self.common_name,
63 self.population + other,
64 self.habitat
65 )
66 return NotImplemented
67
68 def __sub__(self, other):
69 """Odejmowanie: species - 20"""
70 if isinstance(other, int):
71 return Species(
72 self.scientific_name,
73 self.common_name,
74 max(0, self.population - other),
75 self.habitat
76 )
77 return NotImplemented
78
79 # === KONTENER ===
80
81 def __len__(self):
82 """len(species) - liczba obserwacji"""
83 return len(self.observations)
84
85 def __getitem__(self, index):
86 """species[0] - dostęp do obserwacji"""
87 return self.observations[index]
88
89 def __contains__(self, location):
90 """'Serengeti' in species - sprawdź lokalizację"""
91 return any(obs["location"] == location for obs in self.observations)
92
93 # === CALLABLE ===
94
95 def __call__(self, location, count):
96 """Wywołanie jak funkcja - dodaj obserwację"""
97 observation = {
98 "date": datetime.now().strftime("%Y-%m-%d"),
99 "location": location,
100 "count": count
101 }
102 self.observations.append(observation)
103 return f"✓ Dodano: {count}x {self.common_name} w {location}"
104
105 # === BOOL ===
106
107 def __bool__(self):
108 """bool(species) - czy gatunek ma jakąś populację?"""
109 return self.population > 0
110
111# === DEMONSTRACJA WSZYSTKICH MAGICZNYCH METOD ===
112
113print("=== TWORZENIE GATUNKÓW ===\n")
114
115lion = Species("Panthera leo", "Lew", 120, "sawanna")
116elephant = Species("Loxodonta africana", "Słoń", 450, "sawanna")
117rhino = Species("Diceros bicornis", "Nosorożec", 45, "sawanna")
118extinct = Species("Dodo", "Dodo", 0, "Mauritius")
119
120# __str__ i __repr__
121print("__str__ (print):")
122print(lion) # "Lew (120 osobników)"
123
124print("\n__repr__ (repr):")
125print(repr(lion))
126# Species(scientific_name='Panthera leo', common_name='Lew', population=120, habitat='sawanna')
127
128# Porównania (__eq__, __lt__, etc.)
129print("\n=== PORÓWNANIA ===")
130print(f"lion == elephant? {lion == elephant}") # False
131print(f"rhino < lion? {rhino < lion}") # True (45 < 120)
132print(f"elephant > lion? {elephant > lion}") # True (450 > 120)
133
134# Sortowanie (działa przez __lt__)
135species_list = [lion, rhino, elephant]
136species_list.sort()
137print(f"\nPosortowane (rosnąco): {[str(s) for s in species_list]}")
138# ['Nosorożec (45 osobników)', 'Lew (120 osobników)', 'Słoń (450 osobników)']
139
140# Arytmetyka (__add__, __sub__)
141print("\n=== ARYTMETYKA ===")
142more_lions = lion + 30 # Zwiększ populację
143print(f"lion + 30 = {more_lions}") # "Lew (150 osobników)"
144
145fewer_rhinos = rhino - 10
146print(f"rhino - 10 = {fewer_rhinos}") # "Nosorożec (35 osobników)"
147
148# Callable (__call__)
149print("\n=== CALLABLE - dodawanie obserwacji ===")
150print(lion("Serengeti", 12)) # Wywołaj jak funkcję!
151print(lion("Masai Mara", 8))
152print(elephant("Amboseli", 35))
153
154# Kontener (__len__, __getitem__, __contains__)
155print("\n=== KONTENER ===")
156print(f"Liczba obserwacji lwów: {len(lion)}") # __len__
157print(f"Pierwsza obserwacja: {lion[0]}") # __getitem__
158print(f"'Serengeti' in lion? {'Serengeti' in lion}") # __contains__ - True
159
160# Iteracja (przez __getitem__)
161print("\nWszystkie obserwacje lwów:")
162for obs in lion:
163 print(f" - {obs['date']} w {obs['location']}: {obs['count']} osobników")
164
165# Bool (__bool__)
166print("\n=== BOOL ===")
167print(f"bool(lion)? {bool(lion)}") # True - ma populację
168print(f"bool(extinct)? {bool(extinct)}") # False - populacja 0
169
170if lion:
171 print("Lew ma populację!")
172
173if not extinct:
174 print("Dodo wymarł...")
175
176# Hash (__hash__) - działa w set i dict
177print("\n=== HASH - użycie w set/dict ===")
178species_set = {lion, elephant, rhino}
179print(f"Zbiór gatunków: {len(species_set)} elementów")
180
181species_dict = {
182 lion: "Drapieżnik",
183 elephant: "Roślinożerca",
184 rhino: "Roślinożerca"
185}
186print(f"Typ lwa: {species_dict[lion]}")__init__(self, ...) - konstruktor__str__(self) - string dla użytkownika (print)__repr__(self) - string dla programisty (repr)__format__(self, format_spec) - formatowanie (f"{obj:spec}")__eq__(self, other) - równość ==__ne__(self, other) - nierówność !=__lt__(self, other) - mniejsze <__le__(self, other) - mniejsze lub równe <=__gt__(self, other) - większe >__ge__(self, other) - większe lub równe >=__add__(self, other) - dodawanie +__sub__(self, other) - odejmowanie -__mul__(self, other) - mnożenie *__truediv__(self, other) - dzielenie /__floordiv__(self, other) - dzielenie całkowite //__mod__(self, other) - modulo %__pow__(self, other) - potęgowanie **__len__(self) - długość len()__getitem__(self, key) - odczyt obj[key]__setitem__(self, key, value) - zapis obj[key] = value__delitem__(self, key) - usunięcie del obj[key]__contains__(self, item) - członkostwo item in obj__iter__(self) - iterator for x in obj__next__(self) - następny element__call__(self, ...) - wywołanie obj()__enter__(self) - wejście do with__exit__(self, ...) - wyjście z with__bool__(self) - konwersja do bool__hash__(self) - hash dla set/dict__del__(self) - destruktor (rzadko używane)W tej lekcji nauczyłeś/aś się:
__str__, __repr__, __eq__, __lt____add__, __sub__, __mul__)__len__, __getitem__, __contains__)__call____enter__ i __exit__Przed przejściem dalej:
__str__ a __repr____len__, __getitem__, __iter____call__Analogia Safari: Metody magiczne to instynkty zwierząt - lew nie uczy się ryczeć, po prostu to robi! Twoje klasy też mogą mieć naturalne zachowania! 🦁✨
W następnej lekcji Darwin nauczy Cię type hints - jak precyzyjnie opisywać typy danych, żeby kod był bardziej czytelny i bezpieczny! 📝🔍