Kurs Python · Moduł 3: Programowanie obiektowe

Dekoratory - adaptacja do środowiska

13 min czytania
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 dekorowanie

Jak 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 greet

Wrapper 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:

  1. repeat(times) - zwraca dekorator
  2. decorator(func) - prawdziwy dekorator
  3. wrapper(*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) > 0

Praktyczne 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.0015s

2. 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 Serengeti

Gdyby 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,)
105

Każ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    pass

Taki 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 *args i **kwargs
  • Używania @functools.wraps do 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. 1. Dekorator w Pythonie to:

  2. 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:

Przydatne artykuły