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!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 funkcjiSkładnia
to skrót dla:@decorator
1def say_hello():
2 print("Hello!")
3
4say_hello = my_decorator(say_hello) # Ręczne dekorowanieDekorator 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 greetAby 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!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!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:
repeat(times) - zwraca dekoratordecorator(func) - prawdziwy dekoratorwrapper(*args, **kwargs) - wrapper funkcjiJuż znasz niektóre wbudowane dekoratory Pythona!
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) > 01import 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.0015s1from 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 Serengeti1from 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# 5Uwaga: 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)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}")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)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 → boldDekoratory 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!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}")W tej lekcji nauczyłeś/aś się:
@decorator*args i **kwargs@functools.wraps do zachowania metadanych@property, @classmethod, @staticmethod)Przed przejściem dalej:
@wrapsAnalogia 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! 🎓🦁🐘