Witaj ponownie, @name! Darwin tutaj z kluczową lekcją o bezpieczeństwie danych.
Wyobraź sobie, że prowadzisz katalog gatunków zagrożonych. Dane o lokalizacji rzadkich zwierząt są wrażliwe - nie chcesz, aby kłusownicy mieli do nich bezpośredni dostęp! Potrzebujesz kontrolowanego dostępu, walidacji, ochrony.
W OOP ten mechanizm to enkapsulacja (encapsulation) - ukrywanie szczegółów implementacji i chronieniu danych przed nieautoryzowanym dostępem!
Enkapsulacja to jeden z 4 filarów OOP, który polega na:
Analogia Safari: Wrażliwe dane o lokalizacji gatunków zagrożonych są chronione - dostęp tylko dla autoryzowanego personelu, z logowaniem każdej zmiany!
1# BEZ enkapsulacji - niebezpieczne!
2class Species:
3 def __init__(self, name, population):
4 self.name = name
5 self.population = population # Publiczne - każdy może zmienić!
6
7lion = Species("Lew", 500)
8lion.population = -999999 # ❌ Nikt nie zatrzyma tego błędu!
9
10# Z enkapsulacją - bezpieczne!
11class ProtectedSpecies:
12 def __init__(self, name, population):
13 self.name = name
14 self.__population = population # Prywatne!
15
16 def set_population(self, value):
17 """Kontrolowany dostęp z walidacją"""
18 if value < 0:
19 raise ValueError("Populacja nie może być ujemna!")
20 self.__population = value
21
22 def get_population(self):
23 return self.__population
24
25lion = ProtectedSpecies("Lew", 500)
26# lion.__population = -999 # ❌ Nie działa - chronione!
27lion.set_population(550) # ✅ Działa - walidowane!Python używa konwencji nazewniczych do oznaczania poziomów dostępu:
| Konwencja | Znaczenie | Przykład | |-----------|-----------|----------| |
name | Publiczny - dostęp z każdego miejsca | self.name |
| _name | Chroniony - tylko klasa i podklasy | self._internal_id |
| __name | Prywatny - tylko wewnątrz klasy | self.__password |UWAGA: W Pythonie to są konwencje, nie twarde ograniczenia! Python ufa programiście.
Domyślnie wszystkie atrybuty są publiczne - dostępne z każdego miejsca:
1class Animal:
2 def __init__(self, name, age):
3 self.name = name # Publiczny
4 self.age = age # Publiczny
5
6lion = Animal("Simba", 3)
7
8# Pełny dostęp
9print(lion.name) # "Simba"
10lion.age = 4 # Można zmienić
11print(lion.age) # 4Użyj publicznych atrybutów gdy:
_single_underscoreKonwencja: Atrybut z jednym podkreśleniem (
_name) oznacza "chroniony" - używaj tylko wewnątrz klasy i w podklasach.1class Species:
2 def __init__(self, name):
3 self.name = name # Publiczny
4 self._internal_id = 12345 # Chroniony - konwencja!
5
6 def _calculate_risk(self):
7 """Metoda chroniona - konwencja"""
8 return self._internal_id * 2
9
10lion = Species("Lew")
11
12# Technicznie możliwe, ale narusza konwencję
13print(lion._internal_id) # 12345 - działa, ale NIE POWINNO!
14
15# Prawidłowe użycie
16class EndangeredSpecies(Species):
17 def get_risk(self):
18 # Podklasa może używać _internal_id
19 return self._calculate_risk()Konwencja _underscore:
help() domyślnie__double_underscoreName Mangling: Atrybut z dwoma podkreśleniami (
__name) jest "prywatny" - Python zmienia nazwę, żeby utrudnić dostęp!1class SecretData:
2 def __init__(self, value):
3 self.__secret = value # Prywatny!
4
5 def get_secret(self):
6 """Kontrolowany dostęp"""
7 return self.__secret
8
9data = SecretData("Top Secret")
10
11# Bezpośredni dostęp nie działa
12# print(data.__secret) # ❌ AttributeError!
13
14# Działa przez metodę
15print(data.get_secret()) # "Top Secret"
16
17# Name mangling - Python zmienił nazwę!
18print(data._SecretData__secret) # "Top Secret" - można, ale NIE RÓB TAK!Mechanizm Name Mangling:
__name na _ClassName__name1class Parent:
2 def __init__(self):
3 self.__private = "Rodzic"
4
5class Child(Parent):
6 def __init__(self):
7 super().__init__()
8 self.__private = "Dziecko" # Nie koliduje!
9
10child = Child()
11print(child._Parent__private) # "Rodzic"
12print(child._Child__private) # "Dziecko"@propertyProperty to elegancki sposób na tworzenie getterów i setterów, które wyglądają jak atrybuty!
1class Circle:
2 def __init__(self, radius):
3 self.radius = radius
4
5 @property
6 def area(self):
7 """Oblicz pole - automatycznie!"""
8 return 3.14159 * self.radius ** 2
9
10 @property
11 def diameter(self):
12 """Oblicz średnicę"""
13 return self.radius * 2
14
15circle = Circle(5)
16
17# Używaj jak atrybutu, ale wywołuje metodę!
18print(circle.area) # 78.53975 - wygląda jak atrybut
19print(circle.diameter) # 10
20
21circle.radius = 10
22print(circle.area) # 314.159 - automatycznie przeliczone!1class Temperature:
2 def __init__(self, celsius):
3 self._celsius = celsius # Wewnętrzne przechowywanie
4
5 @property
6 def celsius(self):
7 """Getter - odczyt temperatury"""
8 return self._celsius
9
10 @celsius.setter
11 def celsius(self, value):
12 """Setter - zapis z walidacją"""
13 if value < -273.15:
14 raise ValueError("Temperatura poniżej zera bezwzględnego!")
15 self._celsius = value
16
17 @property
18 def fahrenheit(self):
19 """Automatyczna konwersja"""
20 return self._celsius * 9/5 + 32
21
22 @fahrenheit.setter
23 def fahrenheit(self, value):
24 """Ustawienie przez Fahrenheita"""
25 self._celsius = (value - 32) * 5/9
26
27temp = Temperature(25)
28
29# Getter
30print(temp.celsius) # 25
31
32# Setter z walidacją
33temp.celsius = 30 # OK
34print(temp.celsius) # 30
35
36# temp.celsius = -300 # ValueError: Temperatura poniżej zera bezwzględnego!
37
38# Konwersja automatyczna
39print(temp.fahrenheit) # 86.0
40temp.fahrenheit = 32 # Ustaw przez Fahrenheita
41print(temp.celsius) # 0.0 - automatyczna konwersja!1class Person:
2 def __init__(self, birth_year):
3 self._birth_year = birth_year
4
5 @property
6 def birth_year(self):
7 """Tylko odczyt - brak settera"""
8 return self._birth_year
9
10 @property
11 def age(self):
12 """Obliczane dynamicznie"""
13 from datetime import datetime
14 return datetime.now().year - self._birth_year
15
16person = Person(1990)
17print(person.age) # 35 (w 2025)
18# person.age = 40 # ❌ AttributeError: can't set attribute1from datetime import datetime
2from typing import List, Dict
3
4class EndangeredSpecies:
5 """
6 Klasa zarządzająca gatunkiem zagrożonym z pełną enkapsulacją
7
8 Chroni wrażliwe dane o lokalizacji, populacji, obserwacjach
9 """
10
11 # Klasa przechowuje wszystkie instancje
12 _all_species: Dict[str, 'EndangeredSpecies'] = {}
13 _next_id = 1
14
15 def __init__(self, scientific_name, common_name, conservation_status):
16 # Publiczne dane - bezpieczne do udostępnienia
17 self.scientific_name = scientific_name
18 self.common_name = common_name
19
20 # Chronione dane - konwencja _underscore
21 self._id = EndangeredSpecies._next_id
22 EndangeredSpecies._next_id += 1
23 self._conservation_status = conservation_status
24 self._created_at = datetime.now()
25
26 # Prywatne dane - wrażliwe informacje
27 self.__population = 0
28 self.__secret_locations = [] # Lokalizacje - NIE dla publiki!
29 self.__access_log = [] # Log dostępu
30
31 # Rejestruj w katalogu
32 EndangeredSpecies._all_species[scientific_name] = self
33
34 # === PROPERTY: Population (z walidacją) ===
35
36 @property
37 def population(self):
38 """Odczyt populacji - logowany"""
39 self.__log_access("population", "read")
40 return self.__population
41
42 @population.setter
43 def population(self, value):
44 """Zapis populacji - walidacja + logowanie"""
45 self.__log_access("population", "write", f"Zmiana z {self.__population} na {value}")
46
47 if not isinstance(value, int):
48 raise TypeError("Populacja musi być liczbą całkowitą")
49
50 if value < 0:
51 raise ValueError("Populacja nie może być ujemna")
52
53 self.__population = value
54 self.__update_conservation_status()
55
56 # === PROPERTY: Conservation Status (tylko odczyt) ===
57
58 @property
59 def conservation_status(self):
60 """Status ochrony - tylko odczyt"""
61 return self._conservation_status
62
63 # === PROPERTY: Threat Level (obliczane dynamicznie) ===
64
65 @property
66 def threat_level(self):
67 """
68 Poziom zagrożenia (1-10) - obliczane na podstawie populacji
69 Tylko odczyt - brak settera
70 """
71 if self.__population == 0:
72 return 10 # Krytycznie zagrożony
73 elif self.__population < 50:
74 return 9
75 elif self.__population < 250:
76 return 7
77 elif self.__population < 1000:
78 return 5
79 elif self.__population < 5000:
80 return 3
81 else:
82 return 1 # Bezpieczny
83
84 # === METODY PRYWATNE ===
85
86 def __log_access(self, field, action, details=""):
87 """Prywatna metoda - logowanie dostępu"""
88 log_entry = {
89 "timestamp": datetime.now(),
90 "field": field,
91 "action": action,
92 "details": details
93 }
94 self.__access_log.append(log_entry)
95
96 def __update_conservation_status(self):
97 """Prywatna metoda - aktualizacja statusu na podstawie populacji"""
98 if self.__population == 0:
99 self._conservation_status = "Extinct in the Wild"
100 elif self.__population < 50:
101 self._conservation_status = "Critically Endangered"
102 elif self.__population < 250:
103 self._conservation_status = "Endangered"
104 elif self.__population < 1000:
105 self._conservation_status = "Vulnerable"
106 else:
107 self._conservation_status = "Least Concern"
108
109 # === METODY PUBLICZNE - Kontrolowany dostęp ===
110
111 def add_secret_location(self, latitude, longitude, authorization_code):
112 """
113 Dodaj wrażliwą lokalizację - wymaga autoryzacji!
114
115 Args:
116 latitude: Szerokość geograficzna
117 longitude: Długość geograficzna
118 authorization_code: Kod dostępu (w produkcji: hash, token, etc.)
119 """
120 # Walidacja autoryzacji
121 if authorization_code != "SAFARI_2024_SECURE":
122 self.__log_access("secret_locations", "denied", "Nieautoryzowany dostęp")
123 raise PermissionError("Nieautoryzowany dostęp do wrażliwych danych!")
124
125 # Walidacja współrzędnych
126 if not (-90 <= latitude <= 90):
127 raise ValueError("Nieprawidłowa szerokość geograficzna")
128 if not (-180 <= longitude <= 180):
129 raise ValueError("Nieprawidłowa długość geograficzna")
130
131 location = {
132 "lat": latitude,
133 "lon": longitude,
134 "added_at": datetime.now()
135 }
136
137 self.__secret_locations.append(location)
138 self.__log_access("secret_locations", "add", f"Dodano lokalizację: {latitude}, {longitude}")
139
140 return f"✓ Lokalizacja dodana (ID: {len(self.__secret_locations)})"
141
142 def get_location_count(self):
143 """Zwróć liczbę lokalizacji bez ujawniania szczegółów"""
144 return len(self.__secret_locations)
145
146 def get_general_area(self):
147 """Zwróć ogólny obszar bez precyzyjnych współrzędnych"""
148 if not self.__secret_locations:
149 return "Brak danych o lokalizacji"
150
151 # Uśrednij współrzędne dla ogólnego obszaru
152 avg_lat = sum(loc["lat"] for loc in self.__secret_locations) / len(self.__secret_locations)
153 avg_lon = sum(loc["lon"] for loc in self.__secret_locations) / len(self.__secret_locations)
154
155 # Zwróć zaokrąglone do 1 miejsca (mniej precyzyjne)
156 return f"Obszar: {avg_lat:.1f}°, {avg_lon:.1f}°"
157
158 def get_access_log(self, authorization_code):
159 """Zwróć log dostępu - wymaga autoryzacji"""
160 if authorization_code != "SAFARI_2024_SECURE":
161 raise PermissionError("Nieautoryzowany dostęp do logów!")
162
163 return self.__access_log.copy() # Zwróć kopię, nie oryginał!
164
165 def generate_public_report(self):
166 """Raport publiczny - bez wrażliwych danych"""
167 return f"""
168╔════════════════════════════════════════════════╗
169 {self.common_name} ({self.scientific_name})
170╠════════════════════════════════════════════════╣
171 Status ochrony: {self.conservation_status}
172 Populacja: {self.population} osobników
173 Poziom zagrożenia: {self.threat_level}/10
174 Znane lokalizacje: {self.get_location_count()}
175 Ogólny obszar: {self.get_general_area()}
176╚════════════════════════════════════════════════╝
177 """.strip()
178
179 def generate_classified_report(self, authorization_code):
180 """Raport szczegółowy - wymaga autoryzacji"""
181 if authorization_code != "SAFARI_2024_SECURE":
182 raise PermissionError("Nieautoryzowany dostęp do danych niejawnych!")
183
184 locations_str = "\n".join(
185 f" - Lat: {loc['lat']:.6f}, Lon: {loc['lon']:.6f} (dodano: {loc['added_at'].strftime('%Y-%m-%d %H:%M')})"
186 for loc in self.__secret_locations
187 )
188
189 return f"""
190╔════════════════════════════════════════════════╗
191 🔒 RAPORT NIEJAWNY - {self.common_name}
192╠════════════════════════════════════════════════╣
193 ID: {self._id}
194 Status: {self.conservation_status}
195 Populacja: {self.__population}
196 Zagrożenie: {self.threat_level}/10
197
198 LOKALIZACJE (NIEJAWNE):
199{locations_str if locations_str else " Brak danych"}
200
201 Dostępy do danych: {len(self.__access_log)}
202╚════════════════════════════════════════════════╝
203 """.strip()
204
205 # === METODY KLASOWE ===
206
207 @classmethod
208 def get_species_by_threat_level(cls, min_level):
209 """Zwróć gatunki o zagrożeniu >= min_level"""
210 return [
211 species for species in cls._all_species.values()
212 if species.threat_level >= min_level
213 ]
214
215# === DEMONSTRACJA UŻYCIA ===
216
217print("=== SYSTEM ZARZĄDZANIA GATUNKAMI ZAGROŻONYMI ===\n")
218
219# Tworzenie gatunków
220rhino = EndangeredSpecies(
221 scientific_name="Diceros bicornis",
222 common_name="Nosorożec czarny",
223 conservation_status="Critically Endangered"
224)
225
226mountain_gorilla = EndangeredSpecies(
227 scientific_name="Gorilla beringei beringei",
228 common_name="Goryl górski",
229 conservation_status="Endangered"
230)
231
232# Ustawienie populacji (z walidacją)
233rhino.population = 45 # OK
234mountain_gorilla.population = 880 # OK
235
236# Próba nieprawidłowej wartości
237try:
238 rhino.population = -10 # ValueError!
239except ValueError as e:
240 print(f"❌ Błąd walidacji: {e}\n")
241
242# Dodanie lokalizacji (wymaga autoryzacji)
243try:
244 rhino.add_secret_location(
245 latitude=-1.2345,
246 longitude=36.7890,
247 authorization_code="WRONG_CODE"
248 )
249except PermissionError as e:
250 print(f"❌ Brak autoryzacji: {e}\n")
251
252# Prawidłowa autoryzacja
253rhino.add_secret_location(-1.2345, 36.7890, "SAFARI_2024_SECURE")
254rhino.add_secret_location(-1.5678, 36.9012, "SAFARI_2024_SECURE")
255mountain_gorilla.add_secret_location(-1.4321, 29.6543, "SAFARI_2024_SECURE")
256
257print("✓ Lokalizacje dodane\n")
258
259# Bezpośredni dostęp do prywatnych danych - nie działa!
260try:
261 print(rhino.__population) # AttributeError
262except AttributeError:
263 print("❌ Nie można uzyskać bezpośredniego dostępu do __population\n")
264
265# Dostęp przez property - działa!
266print(f"✓ Populacja nosorożca (przez property): {rhino.population}\n")
267
268# Raport publiczny - bezpieczny
269print("=== RAPORT PUBLICZNY ===")
270print(rhino.generate_public_report())
271
272# Raport niejawny - wymaga autoryzacji
273print("\n=== RAPORT NIEJAWNY ===")
274print(rhino.generate_classified_report("SAFARI_2024_SECURE"))
275
276# Property tylko do odczytu
277print(f"\n=== WŁAŚCIWOŚCI TYLKO DO ODCZYTU ===")
278print(f"Status ochrony: {rhino.conservation_status}")
279print(f"Poziom zagrożenia: {rhino.threat_level}/10")
280
281# Próba zmiany - nie działa!
282try:
283 rhino.threat_level = 1 # AttributeError - brak settera!
284except AttributeError as e:
285 print(f"❌ Nie można ustawić threat_level: {e}")
286
287# Automatyczna aktualizacja statusu przy zmianie populacji
288print(f"\n=== AUTOMATYCZNA AKTUALIZACJA ===")
289print(f"Przed: populacja={rhino.population}, status={rhino.conservation_status}")
290rhino.population = 280 # Zwiększ populację
291print(f"Po: populacja={rhino.population}, status={rhino.conservation_status}")
292
293# Gatunki o wysokim zagrożeniu
294print(f"\n=== GATUNKI KRYTYCZNIE ZAGROŻONE ===")
295critical = EndangeredSpecies.get_species_by_threat_level(7)
296for species in critical:
297 print(f" ⚠️ {species.common_name}: Zagrożenie {species.threat_level}/10")1class Temperature:
2 def __init__(self):
3 self.__celsius = 0
4
5 @property
6 def celsius(self):
7 return self.__celsius
8
9 @celsius.setter
10 def celsius(self, value):
11 if value < -273.15:
12 raise ValueError("Poniżej zera bezwzględnego!")
13 self.__celsius = value
14
15temp = Temperature()
16# temp.celsius = -300 # ValueError - walidacja!1class BankAccount:
2 def __init__(self, balance):
3 self.__balance = balance
4
5 def withdraw(self, amount):
6 """Kontrolowany dostęp"""
7 if amount > self.__balance:
8 raise ValueError("Niewystarczające środki")
9 self.__balance -= amount
10
11account = BankAccount(1000)
12# account.__balance = 999999 # Nie działa!
13account.withdraw(500) # Tylko przez metodę!1class Circle:
2 def __init__(self, radius):
3 self.__radius = radius
4
5 @property
6 def area(self):
7 """Użytkownik nie wie, że to obliczane!"""
8 return 3.14159 * self.__radius ** 2
9
10# Możesz zmienić implementację bez zmiany interfejsu
11circle = Circle(5)
12print(circle.area) # Użytkownik używa jak atrybutu1class Database:
2 def __init__(self):
3 self.__connection = None
4 self.__cache = {}
5
6 def __connect(self):
7 """Prywatna - użytkownik nie musi znać szczegółów"""
8 print("Łączenie z bazą...")
9
10 def query(self, sql):
11 """Publiczny interfejs - prosty"""
12 if not self.__connection:
13 self.__connect()
14 # ... wykonaj zapytanie1# ❌ Styl Java - rozwlekły
2class Person:
3 def __init__(self, name):
4 self.__name = name
5
6 def get_name(self):
7 return self.__name
8
9 def set_name(self, value):
10 self.__name = value
11
12# ✅ Pythonowy sposób - elegancki
13class Person:
14 def __init__(self, name):
15 self._name = name
16
17 @property
18 def name(self):
19 return self._name
20
21 @name.setter
22 def name(self, value):
23 self._name = value1# ❌ Zbyt paranoidalne
2class Person:
3 def __init__(self, name):
4 self.__name = name # Zbędne __
5
6# ✅ Zaufaj konwencjom
7class Person:
8 def __init__(self, name):
9 self.name = name # Prosty atrybut, jeśli nie potrzebujesz ochrony1# ✅ Chroniąc wrażliwe dane
2class User:
3 def __init__(self, username, password):
4 self.username = username # Publiczny
5 self.__password_hash = hash(password) # Prywatny!
6
7 def check_password(self, password):
8 return hash(password) == self.__password_hashW tej lekcji nauczyłeś/aś się:
__name)@property, getterów i setterówPrzed przejściem dalej:
name, _name i __nameAnalogia Safari: Enkapsulacja to system ochrony dla wrażliwych danych o gatunkach zagrożonych - nie każdy powinien mieć dostęp do lokalizacji rzadkich zwierząt! 🔒🦏
W następnej lekcji Darwin nauczy Cię magicznych metod - ukrytych zachowań, które sprawiają, że Twoje klasy działają jak wbudowane typy Pythona! ✨🐍