Usamos cookies para mejorar tu experiencia en el sitio
CodeWorlds

Dekoratory - adaptacja do środowiska

Witaj po raz ostatni w Module 3, @name! Darwin tutaj z finalną, zaawansowaną lekcją.

W naturze zwierzęta adaptują się do środowiska - kamele on 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}")
9# Teraz każde wywołanie jest logowane automatycznie!

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()
14# Wynik:
15# Coś przed funkcją
16# Hello!
17# Coś po funkcji

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()
23# [PRZED] Wywołanie greet
24# Witaj!
25# [PO] Zakończono greet

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# Wywołanie add z args=(5, 3), kwargs={}
20# Wynik: 8
21
22greet("Darwin", greeting="Cześć")
23# Wywołanie greet z args=('Darwin',), kwargs={'greeting': 'Cześć'}
24# Wynik: Cześć, Darwin!

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()
17# Hello!
18# Hello!
19# Hello!

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

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. Timing decorator - pomiar czasu wykonania

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")
22# ⏱️  analyze_dna_sequence zajęło 2.0015s

2. Logger decorator - automatyczne logowanie

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)
29# [2024-01-15 14:30:45] Wywołanie: observe_species
30#   Argumenty: args=('Lew', 'Serengeti', 12), kwargs={}
31#   ✓ Sukces: Zapisano: 12x Lew w Serengeti

3. Cache decorator - memoizacja

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))
28# 🔄 Obliczanie dla (5,)
29# 🔄 Obliczanie dla (4,)
30# ...
31# 💾 Cache hit dla (2,)  # Reużycie!
32# 5

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. Access control decorator - autoryzacja

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 o {species_name}: [KLASYFIKOWANE]"
21
22# Poprawna autoryzacja
23print(access_classified_data("SAFARI_2024", "Nosorożec czarny"))
24# "Niejawne dane o 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. Retry decorator - automatyczne ponawianie

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 stackować - stosować wiele naraz!

1@decorator1
2@decorator2
3@decorator3
4def my_function():
5    pass
6
7# Równoważne:
8# my_function = decorator1(decorator2(decorator3(my_function)))

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"))
21# "**_Hello, Darwin_**"
22# Kolejność: 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!

Safari przykład - kompletny system z dekoratorami

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, auth_code: str) -> 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ęto {count} obserwacji"
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
  • ✅ Stackowania 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 stackować wiele dekoratorów

Analogia Safari: Dekoratory to adaptacje ewolucyjne - kamele on z chromatoforami, nietoperz z echolokacją - rozszerzają możliwości bez zmiany podstawowej natury! 🦎✨

Gratulacje! Ukończyłeś/aś Module 3 - Programowanie Obiektowe! Opanowałeś/aś klasy, dziedziczenie, enkapsulację, magic methods, type hints i dekoratory. Jesteś gotowy/a na kolejne wyzwania w Safari Python! 🎓🦁🐘

Ir a CodeWorlds