Kurs Python · Moduł 3: Programowanie obiektowe
Dekoratory - adaptacja do środowiska
W tej lekcji12
Witaj po raz ostatni w Module 3! Tu Darwin, czas na najbardziej zaawansowaną lekcję.
W naturze zwierzęta adaptują się do środowiska - kameleon zyskuje zdolność zmiany koloru, nietoperze echolokację, ptaki migracyjne nawigację magnetyczną. W Pythonie dekoratory pozwalają na podobną adaptację - modyfikują zachowanie funkcji i klas bez zmiany ich kodu!
1# Zwykła funkcja
2def observe_species(name):
3 print(f"Obserwacja: {name}")
4
5# Funkcja z dekoratorem - automatyczne logowanie!
6@log_observation
7def observe_species(name):
8 print(f"Obserwacja: {name}")Ciało funkcji się nie zmieniło. Jedna linia @log_observation nad definicją sprawia, że każde wywołanie jest teraz logowane automatycznie. Jak napisać taki dekorator, zobaczysz za chwilę.
Czym są dekoratory?
Dekorator to funkcja, która przyjmuje inną funkcję (lub klasę) i zwraca zmodyfikowaną wersję.
Analogia Safari: To jak wyposażenie badacza - lornetka nie zmienia badacza, ale rozszerza jego możliwości obserwacji!
1def my_decorator(func):
2 """Dekorator - przyjmuje funkcję, zwraca zmodyfikowaną"""
3 def wrapper():
4 print("Coś przed funkcją")
5 func() # Wywołaj oryginalną funkcję
6 print("Coś po funkcji")
7 return wrapper
8
9@my_decorator
10def say_hello():
11 print("Hello!")
12
13say_hello()Wywołanie say_hello() wypisze:
1Coś przed funkcją
2Hello!
3Coś po funkcjiŚrodkową linię wypisała oryginalna funkcja, a dwie pozostałe wrapper, który dekorator podstawił pod nazwę say_hello.
Składnia @decorator to skrót dla:
1def say_hello():
2 print("Hello!")
3
4say_hello = my_decorator(say_hello) # Ręczne dekorowanieJak działają dekoratory?
Dekorator to funkcja wyższego rzędu (higher-order function) - funkcja operująca na funkcjach!
1def simple_decorator(func):
2 """
3 Dekorator - wrapper pattern
4
5 1. Przyjmij funkcję
6 2. Zdefiniuj wrapper
7 3. Zwróć wrapper
8 """
9 def wrapper():
10 print(f"[PRZED] Wywołanie {func.__name__}")
11 result = func()
12 print(f"[PO] Zakończono {func.__name__}")
13 return result
14 return wrapper
15
16@simple_decorator
17def greet():
18 print("Witaj!")
19 return "Done"
20
21# greet jest teraz wrapper, nie oryginalną funkcją!
22greet()Program wypisze:
1[PRZED] Wywołanie greet
2Witaj!
3[PO] Zakończono greetWrapper zwraca też wynik oryginalnej funkcji (result), więc wartość "Done" nie ginie po drodze.
Dekoratory z argumentami funkcji
Aby dekorator działał z funkcjami przyjmującymi argumenty, użyj *args i **kwargs:
1def log_decorator(func):
2 def wrapper(*args, **kwargs):
3 """Wrapper przyjmuje dowolne argumenty"""
4 print(f"Wywołanie {func.__name__} z args={args}, kwargs={kwargs}")
5 result = func(*args, **kwargs)
6 print(f"Wynik: {result}")
7 return result
8 return wrapper
9
10@log_decorator
11def add(a, b):
12 return a + b
13
14@log_decorator
15def greet(name, greeting="Hello"):
16 return f"{greeting}, {name}!"
17
18add(5, 3)
19
20greet("Darwin", greeting="Cześć")Oba wywołania wypiszą:
1Wywołanie add z args=(5, 3), kwargs={}
2Wynik: 8
3Wywołanie greet z args=('Darwin',), kwargs={'greeting': 'Cześć'}
4Wynik: Cześć, Darwin!Ten sam wrapper obsłużył funkcję z dwoma argumentami pozycyjnymi i funkcję z argumentem nazwanym: *args zbiera argumenty pozycyjne, a **kwargs nazwane.
functools.wraps - zachowanie metadanych
Problem: dekorator zmienia __name__ i __doc__ funkcji!
1def my_decorator(func):
2 def wrapper(*args, **kwargs):
3 return func(*args, **kwargs)
4 return wrapper
5
6@my_decorator
7def important_function():
8 """Ta funkcja jest ważna"""
9 pass
10
11print(important_function.__name__) # "wrapper" - źle!
12print(important_function.__doc__) # None - brak dokumentacji!Rozwiązanie: Użyj @functools.wraps!
1from functools import wraps
2
3def my_decorator(func):
4 @wraps(func) # Zachowaj metadane oryginalnej funkcji
5 def wrapper(*args, **kwargs):
6 return func(*args, **kwargs)
7 return wrapper
8
9@my_decorator
10def important_function():
11 """Ta funkcja jest ważna"""
12 pass
13
14print(important_function.__name__) # "important_function" - dobrze!
15print(important_function.__doc__) # "Ta funkcja jest ważna" - zachowane!WAŻNE: Zawsze używaj @wraps(func) w swoich dekoratorach!
Dekoratory z parametrami
Aby dekorator przyjmował parametry, potrzebujesz trzech poziomów funkcji:
1def repeat(times):
2 """Dekorator z parametrem - powtórz funkcję N razy"""
3 def decorator(func):
4 @wraps(func)
5 def wrapper(*args, **kwargs):
6 for i in range(times):
7 result = func(*args, **kwargs)
8 return result
9 return wrapper
10 return decorator
11
12@repeat(times=3) # Parametr dekoratora!
13def say_hello():
14 print("Hello!")
15
16say_hello()Jedno wywołanie say_hello() wypisze teraz Hello! trzy razy, bo wrapper uruchamia oryginalną funkcję w pętli for tyle razy, ile podasz w times.
Struktura:
repeat(times)- zwraca dekoratordecorator(func)- prawdziwy dekoratorwrapper(*args, **kwargs)- wrapper funkcji
Wbudowane dekoratory
Już znasz niektóre wbudowane dekoratory Pythona!
@property, @classmethod, @staticmethod
Wszystkie trzy znasz z poprzednich lekcji. To zwykłe dekoratory wbudowane w Pythona: każdy zmienia sposób, w jaki wywołuje się metodę pod nim.
1class Species:
2 def __init__(self, name, population):
3 self._name = name
4 self._population = population
5
6 @property # Getter
7 def name(self):
8 return self._name
9
10 @property
11 def population(self):
12 return self._population
13
14 @population.setter # Setter
15 def population(self, value):
16 if value < 0:
17 raise ValueError("Populacja nie może być ujemna")
18 self._population = value
19
20 @classmethod # Metoda klasowa
21 def from_dict(cls, data):
22 return cls(data['name'], data['population'])
23
24 @staticmethod # Metoda statyczna
25 def is_valid_name(name):
26 return len(name) > 0Praktyczne przykłady dekoratorów
1. Dekorator pomiaru czasu wykonania
Ten dekorator zapisuje czas przed wywołaniem i po nim, a różnicę wypisuje w sekundach. Mierzonej funkcji nie trzeba w tym celu zmieniać:
1import time
2from functools import wraps
3
4def timer(func):
5 """Zmierz czas wykonania funkcji"""
6 @wraps(func)
7 def wrapper(*args, **kwargs):
8 start = time.time()
9 result = func(*args, **kwargs)
10 end = time.time()
11 print(f"⏱ {func.__name__} zajęło {end - start:.4f}s")
12 return result
13 return wrapper
14
15@timer
16def analyze_dna_sequence(sequence):
17 """Symuluj analizę DNA - czasochłonna operacja"""
18 time.sleep(2) # Symulacja
19 return f"Przeanalizowano {len(sequence)} nukleotydów"
20
21result = analyze_dna_sequence("ATCGATCG") # ⏱ analyze_dna_sequence zajęło 2.0015s2. Dekorator logujący - automatyczne logowanie
Dekorator logujący wypisuje datę i godzinę, nazwę funkcji i argumenty każdego wywołania, a potem wynik albo błąd. Wyjątku nie połyka, tylko przekazuje go dalej przez raise:
1from functools import wraps
2from datetime import datetime
3
4def log_calls(func):
5 """Loguj każde wywołanie funkcji"""
6 @wraps(func)
7 def wrapper(*args, **kwargs):
8 timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
9 print(f"[{timestamp}] Wywołanie: {func.__name__}")
10 print(f" Argumenty: args={args}, kwargs={kwargs}")
11
12 try:
13 result = func(*args, **kwargs)
14 print(f" Sukces: {result}")
15 return result
16 except Exception as e:
17 print(f" Błąd: {e}")
18 raise
19 return wrapper
20
21@log_calls
22def observe_species(name, location, count):
23 """Zapisz obserwację gatunku"""
24 if count < 0:
25 raise ValueError("Liczba nie może być ujemna")
26 return f"Zapisano: {count}x {name} w {location}"
27
28observe_species("Lew", "Serengeti", 12)W konsoli pojawi się (data i godzina będą aktualne):
1[2024-01-15 14:30:45] Wywołanie: observe_species
2 Argumenty: args=('Lew', 'Serengeti', 12), kwargs={}
3 Sukces: Zapisano: 12x Lew w SerengetiGdyby funkcja zgłosiła błąd, na przykład dla ujemnej liczby osobników, zamiast linii Sukces zobaczysz Błąd, a wyjątek trafi dalej do kodu, który ją wywołał.
3. Dekorator cache - memoizacja
Dekorator cache zapamiętuje wynik dla każdego zestawu argumentów. Przy kolejnym wywołaniu z tymi samymi argumentami zwraca gotową wartość zamiast liczyć od nowa:
1from functools import wraps
2
3def cache(func):
4 """Zapisz wyniki - nie przeliczaj tego samego dwa razy"""
5 cached_results = {}
6
7 @wraps(func)
8 def wrapper(*args):
9 if args in cached_results:
10 print(f"Cache hit dla {args}")
11 return cached_results[args]
12
13 print(f"Obliczanie dla {args}")
14 result = func(*args)
15 cached_results[args] = result
16 return result
17
18 return wrapper
19
20@cache
21def fibonacci(n):
22 """Oblicz n-tą liczbę Fibonacciego"""
23 if n < 2:
24 return n
25 return fibonacci(n - 1) + fibonacci(n - 2)
26
27print(fibonacci(5))Program wypisze:
1Obliczanie dla (5,)
2Obliczanie dla (4,)
3Obliczanie dla (3,)
4Obliczanie dla (2,)
5Obliczanie dla (1,)
6Obliczanie dla (0,)
7Cache hit dla (1,)
8Cache hit dla (2,)
9Cache hit dla (3,)
105Każda wartość od 0 do 5 jest liczona tylko raz, a drugie wywołania dla 1, 2 i 3 biorą wynik z cache. Bez dekoratora ta sama funkcja wywołałaby się 15 razy, a przy większym n liczba wywołań rośnie lawinowo.
Uwaga: Python ma wbudowany @functools.lru_cache!
1from functools import lru_cache
2
3@lru_cache(maxsize=128) # Pamięta ostatnie 128 wyników
4def fibonacci(n):
5 if n < 2:
6 return n
7 return fibonacci(n - 1) + fibonacci(n - 2)4. Dekorator kontroli dostępu - autoryzacja
Ten dekorator przyjmuje parametr, czyli oczekiwany kod, i sprawdza pierwszy argument wywołania. Funkcja dostaje pozostałe argumenty dopiero wtedy, gdy kod się zgadza:
1from functools import wraps
2
3def require_authorization(authorization_code):
4 """Dekorator z parametrem - wymaga autoryzacji"""
5 def decorator(func):
6 @wraps(func)
7 def wrapper(*args, **kwargs):
8 # Sprawdź czy pierwszy argument to poprawny kod
9 if len(args) == 0 or args[0] != authorization_code:
10 raise PermissionError(f"Nieautoryzowany dostęp do {func.__name__}")
11
12 # Usuń kod autoryzacji z argumentów
13 return func(*args[1:], **kwargs)
14 return wrapper
15 return decorator
16
17@require_authorization("SAFARI_2024")
18def access_classified_data(species_name):
19 """Dostęp do niejawnych danych - wymaga autoryzacji"""
20 return f"Niejawne dane (gatunek: {species_name}): [KLASYFIKOWANE]"
21
22# Poprawna autoryzacja
23print(access_classified_data("SAFARI_2024", "Nosorożec czarny"))
24# "Niejawne dane (gatunek: Nosorożec czarny): [KLASYFIKOWANE]"
25
26# Brak autoryzacji - błąd!
27try:
28 print(access_classified_data("WRONG_CODE", "Nosorożec czarny"))
29except PermissionError as e:
30 print(f"Błąd: {e}")5. Dekorator ponawiania - kolejne próby po błędzie
Połączenie z odległą stacją badawczą czasem się zrywa. Dekorator ponawiania łapie wyjątek, czeka chwilę i próbuje jeszcze raz, aż wyczerpie limit prób:
1from functools import wraps
2import time
3
4def retry(max_attempts=3, delay=1):
5 """Ponów operację przy błędzie"""
6 def decorator(func):
7 @wraps(func)
8 def wrapper(*args, **kwargs):
9 for attempt in range(1, max_attempts + 1):
10 try:
11 return func(*args, **kwargs)
12 except Exception as e:
13 print(f"Próba {attempt}/{max_attempts} nie powiodła się: {e}")
14 if attempt < max_attempts:
15 print(f"Ponowienie za {delay}s...")
16 time.sleep(delay)
17 else:
18 print("Wszystkie próby wyczerpane")
19 raise
20 return wrapper
21 return decorator
22
23@retry(max_attempts=3, delay=0.5)
24def unstable_connection(success_rate=0.3):
25 """Symuluj niestabilne połączenie"""
26 import random
27 if random.random() > success_rate:
28 raise ConnectionError("Połączenie przerwane")
29 return "Sukces!"
30
31# Automatycznie ponawia przy błędzie
32result = unstable_connection(success_rate=0.8)Łączenie wielu dekoratorów
Dekoratory można nakładać jeden na drugi - stosować wiele naraz!
1@decorator1
2@decorator2
3@decorator3
4def my_function():
5 passTaki zapis działa tak samo jak ręczne owinięcie my_function = decorator1(decorator2(decorator3(my_function))). Najbliżej funkcji stoi decorator3, więc to on owija ją jako pierwszy.
Kolejność ma znaczenie - wykonywane od dołu do góry!
1from functools import wraps
2
3def bold(func):
4 @wraps(func)
5 def wrapper(*args, **kwargs):
6 return f"**{func(*args, **kwargs)}**"
7 return wrapper
8
9def italic(func):
10 @wraps(func)
11 def wrapper(*args, **kwargs):
12 return f"_{func(*args, **kwargs)}_"
13 return wrapper
14
15@bold
16@italic
17def greet(name):
18 return f"Hello, {name}"
19
20print(greet("Darwin")) # "**_Hello, Darwin_**"Najpierw italic otoczył wynik podkreśleniami, potem bold dołożył gwiazdki. Kolejność wykonania to greet → italic → bold.
Dekoratory klas
Dekoratory mogą również modyfikować całe klasy!
1def singleton(cls):
2 """Dekorator klasy - wzorzec Singleton"""
3 instances = {}
4
5 @wraps(cls)
6 def get_instance(*args, **kwargs):
7 if cls not in instances:
8 instances[cls] = cls(*args, **kwargs)
9 return instances[cls]
10
11 return get_instance
12
13@singleton
14class DatabaseConnection:
15 def __init__(self, host):
16 self.host = host
17 print(f"Łączenie z {host}...")
18
19# Pierwsza instancja
20db1 = DatabaseConnection("localhost") # Drukuje: "Łączenie z localhost..."
21
22# "Druga" instancja - w rzeczywistości ta sama!
23db2 = DatabaseConnection("localhost") # Nic nie drukuje
24
25print(db1 is db2) # True - ten sam obiekt!Przykład z safari - kompletny system z dekoratorami
Na koniec klasa gatunku, w której pracuje pięć dekoratorów naraz: logowanie i pomiar czasu obserwacji, walidacja populacji, autoryzacja usuwania oraz cache obliczeń.
1from functools import wraps
2from datetime import datetime
3import time
4from typing import Callable, Any
5
6# === DEKORATORY ===
7
8def log_observation(func: Callable) -> Callable:
9 """Loguj wszystkie obserwacje gatunków"""
10 @wraps(func)
11 def wrapper(*args, **kwargs):
12 timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
13 print(f"[{timestamp}] Obserwacja: {func.__name__}")
14 result = func(*args, **kwargs)
15 print(f" Zapisano: {result}")
16 return result
17 return wrapper
18
19def measure_time(func: Callable) -> Callable:
20 """Zmierz czas wykonania operacji"""
21 @wraps(func)
22 def wrapper(*args, **kwargs):
23 start = time.time()
24 result = func(*args, **kwargs)
25 elapsed = time.time() - start
26 print(f" ⏱ Czas: {elapsed:.3f}s")
27 return result
28 return wrapper
29
30def validate_population(func: Callable) -> Callable:
31 """Waliduj dane populacji przed zapisem"""
32 @wraps(func)
33 def wrapper(self, population: int, *args, **kwargs):
34 if not isinstance(population, int):
35 raise TypeError("Populacja musi być liczbą całkowitą")
36 if population < 0:
37 raise ValueError("Populacja nie może być ujemna")
38 return func(self, population, *args, **kwargs)
39 return wrapper
40
41def require_auth(authorization_level: str):
42 """Wymagaj poziomu autoryzacji"""
43 def decorator(func: Callable) -> Callable:
44 @wraps(func)
45 def wrapper(self, auth_code: str, *args, **kwargs):
46 if auth_code != f"SAFARI_{authorization_level}":
47 raise PermissionError(
48 f"Wymagany poziom: {authorization_level}"
49 )
50 return func(self, *args, **kwargs)
51 return wrapper
52 return decorator
53
54def cache_result(func: Callable) -> Callable:
55 """Cache wyników obliczeń"""
56 cache = {}
57
58 @wraps(func)
59 def wrapper(*args):
60 if args in cache:
61 print(f" Cache hit")
62 return cache[args]
63
64 result = func(*args)
65 cache[args] = result
66 return result
67
68 return wrapper
69
70# === KLASA Z DEKORATORAMI ===
71
72class Species:
73 """
74 Klasa gatunku z dekoratorami Safari
75
76 Demonstracja: logowanie, timing, walidacja, autoryzacja, cache
77 """
78
79 _registry = {}
80
81 def __init__(self, scientific_name: str, common_name: str, population: int):
82 self.scientific_name = scientific_name
83 self.common_name = common_name
84 self._population = population
85 self.observations = []
86
87 Species._registry[scientific_name] = self
88
89 @property
90 def population(self) -> int:
91 """Getter - zwykła property"""
92 return self._population
93
94 @population.setter
95 @validate_population # Dekorator walidacji!
96 def population(self, value: int) -> None:
97 """Setter z automatyczną walidacją"""
98 self._population = value
99
100 @log_observation # Automatyczne logowanie
101 @measure_time # Pomiar czasu
102 def add_observation(self, location: str, count: int, date: str) -> str:
103 """
104 Dodaj obserwację - automatycznie logowane i mierzone
105
106 Dekoratory: @log_observation, @measure_time
107 """
108 time.sleep(0.1) # Symuluj operację
109 obs = {
110 "location": location,
111 "count": count,
112 "date": date
113 }
114 self.observations.append(obs)
115 return f"{count}x {self.common_name} w {location}"
116
117 @cache_result # Cache wyników
118 def calculate_biodiversity_score(self) -> float:
119 """
120 Oblicz wynik bioróżnorodności - z cache
121
122 Czasochłonna operacja - cache oszczędza czas!
123 """
124 print(f" Obliczanie score dla {self.common_name}...")
125 time.sleep(0.5) # Symuluj złożone obliczenia
126
127 total_obs = sum(obs["count"] for obs in self.observations)
128 unique_locations = len(set(obs["location"] for obs in self.observations))
129
130 return (total_obs * unique_locations) / (self.population + 1)
131
132 @require_auth("ADMIN") # Wymaga autoryzacji ADMIN
133 def delete_all_observations(self) -> str:
134 """
135 Usuń wszystkie obserwacje - wymaga autoryzacji ADMIN
136
137 Dekorator: @require_auth("ADMIN")
138 """
139 count = len(self.observations)
140 self.observations.clear()
141 return f"Usunięte obserwacje: {count}"
142
143 @classmethod
144 def get_total_population(cls) -> int:
145 """Łączna populacja wszystkich gatunków"""
146 return sum(s.population for s in cls._registry.values())
147
148 @staticmethod
149 @cache_result # Nawet metody statyczne można cache'ować!
150 def calculate_extinction_risk(population: int, habitat_loss: float) -> str:
151 """
152 Oblicz ryzyko wyginięcia - z cache
153
154 Args:
155 population: Liczba osobników
156 habitat_loss: Utrata siedliska (0.0-1.0)
157 """
158 print(f" Obliczanie ryzyka...")
159 time.sleep(0.3)
160
161 risk_score = (1000 - population) * habitat_loss
162
163 if risk_score > 800:
164 return "Krytyczne"
165 elif risk_score > 500:
166 return "Wysokie"
167 elif risk_score > 200:
168 return "Średnie"
169 else:
170 return "Niskie"
171
172 def __repr__(self) -> str:
173 return f"Species('{self.common_name}', pop={self.population})"
174
175# === DEMONSTRACJA ===
176
177print("=== SYSTEM SAFARI Z DEKORATORAMI ===\n")
178
179# Tworzenie gatunków
180lion = Species("Panthera leo", "Lew", 120)
181rhino = Species("Diceros bicornis", "Nosorożec", 45)
182
183# 1. Obserwacje - automatyczne logowanie i timing
184print("\n1. OBSERWACJE (z @log_observation i @measure_time):")
185lion.add_observation("Serengeti", 12, "2024-01-15")
186lion.add_observation("Masai Mara", 8, "2024-01-16")
187
188# 2. Walidacja populacji
189print("\n2. WALIDACJA (@validate_population):")
190try:
191 lion.population = -10 # Błąd - ujemna!
192except ValueError as e:
193 print(f"Błąd walidacji: {e}")
194
195try:
196 lion.population = "sto" # Błąd - nie int!
197except TypeError as e:
198 print(f"Błąd typu: {e}")
199
200lion.population = 125 # OK
201print(f"Populacja zaktualizowana: {lion.population}")
202
203# 3. Cache - biodiversity score
204print("\n3. CACHE (@cache_result):")
205print("Pierwsze wywołanie:")
206score1 = lion.calculate_biodiversity_score()
207print(f"Score: {score1:.2f}")
208
209print("\nDrugie wywołanie (z cache):")
210score2 = lion.calculate_biodiversity_score()
211print(f"Score: {score2:.2f}")
212
213# 4. Autoryzacja
214print("\n4. AUTORYZACJA (@require_auth):")
215try:
216 lion.delete_all_observations("WRONG_CODE")
217except PermissionError as e:
218 print(f"Brak dostępu: {e}")
219
220result = lion.delete_all_observations("SAFARI_ADMIN")
221print(f"{result}")
222
223# 5. Cache w metodzie statycznej
224print("\n5. CACHE W STATIC METHOD:")
225print("Pierwsze wywołanie:")
226risk1 = Species.calculate_extinction_risk(50, 0.8)
227print(f"Ryzyko: {risk1}")
228
229print("\nDrugie wywołanie (z cache):")
230risk2 = Species.calculate_extinction_risk(50, 0.8)
231print(f"Ryzyko: {risk2}")
232
233print("\nInne parametry (nowe obliczenie):")
234risk3 = Species.calculate_extinction_risk(500, 0.3)
235print(f"Ryzyko: {risk3}")Podsumowanie
W tej lekcji nauczyłeś/aś się:
- Czym są dekoratory i jak działają
- Składni
@decorator - Tworzenia dekoratorów z
*argsi**kwargs - Używania
@functools.wrapsdo zachowania metadanych - Dekoratorów z parametrami (trzy poziomy funkcji)
- Wbudowanych dekoratorów (
@property,@classmethod,@staticmethod) - Praktycznych przykładów: timing, logging, cache, autoryzacja, retry
- Nakładania wielu dekoratorów
- Dekoratorów klas
- Kompletnego systemu Safari z dekoratorami
Checkpoint
Przed przejściem dalej:
- Rozumiesz jak dekoratory modyfikują funkcje
- Potrafisz stworzyć prosty dekorator
- Wiesz kiedy używać
@wraps - Rozumiesz dekoratory z parametrami
- Potrafisz nałożyć wiele dekoratorów
Analogia Safari: Dekoratory to adaptacje ewolucyjne - kameleon z chromatoforami, nietoperz z echolokacją - rozszerzają możliwości bez zmiany podstawowej natury!
Gratulacje! Ukończyłeś/aś Moduł 3 - Programowanie Obiektowe! Opanowałeś/aś klasy, dziedziczenie, enkapsulację, metody magiczne, type hints i dekoratory. Jesteś gotowy/a na kolejne wyzwania w Safari Python!
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Dekorator w Pythonie to:
2. Jak zgodnie z konwencją nazywa się pierwszy parametr metody oznaczonej @classmethod?
To 2 z 6 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Układanie w poziomie
Ułóż elementy w prawidłowej kolejności:
- Klikanie w kolejności
Kliknij elementy w prawidłowej kolejności:
- Edytor kodu
Stwórz @classmethod from_dict(cls, data), który tworzy Animal ze słownika z kluczami 'name' i 'species', np. Animal.from_dict({'name': 'Simba', 'species': 'Lew'})
- Edytor kodu
Stwórz @staticmethod is_dangerous(species), który zwraca True dla gatunków 'Lew', 'Krokodyl' i 'Hipopotam', a dla innych False
- Układanie w poziomie
Ułóż nagłówek metody klasowej from_dict (linia pod dekoratorem @classmethod):
- Układanie w pionie
Uporządkuj, co się dzieje po wywołaniu print(lion) dla obiektu z metodą __str__:
- Klikanie w kolejności
Kliknij elementy w prawidłowej kolejności:
- Edytor kodu
Stwórz klasy: Animal (bazowa), Mammal(Animal), Bird(Animal), Tiger(Mammal), Eagle(Bird)
- Edytor kodu
W klasie Animal(name, age, health_status) zapisz age i health_status w atrybutach chronionych lub prywatnych (self._age albo self.__age) i dodaj dla nich property age i health_status
- Układanie w pionie
Uporządkuj kroki tworzenia klasy potomnej:
- Edytor kodu
Stwórz klasę Zoo z metodami: add_animal(animal), remove_animal(name) (usuwa zwierzę o tym imieniu), find_by_species(species) i list_all() (obie zwracają listy zwierząt). Zwierzęta to obiekty Animal(name, species)
- Edytor kodu
Połącz Animal i Zoo w program: utwórz zoo = Zoo() i w pętli while pobieraj wybór przez input(): 1 - dodaj (imię, gatunek), 2 - usuń (imię), 3 - szukaj po gatunku i wypisz imiona, 0 - wyjście
- Układanie w pionie
Uporządkuj typy metod według pierwszego parametru: najpierw te, które dostają obiekt (self), potem klasę (cls), na końcu bez self i cls: