Spis zadań w tym module

  1. Nowoczesne struktury danych (Podstawy @dataclass)
  2. Zaawansowane pola i fabryki (field, default_factory)
  3. Niezmienne punkty i ranking (frozen i order)
  4. Komunikacja tekstowa (Serializacja do JSON)
  5. System zapisu stanu gry (Kompletny cykl zapisu i odczytu)

1. Po co nam Dataclasses?

Tradycyjne klasy wymagają pisania dużej ilości powtarzalnego kodu (boilerplate) dla metod takich jak __init__, __repr__ czy __eq__. Moduł dataclasses automatyzuje ten proces, pozwalając programiście skupić się na strukturze danych, a nie na ich technicznej obsłudze.

2. Type Hints (Podpowiedzi typów)

Dekorator @dataclass opiera się na podpowiedziach typów (np. imie: str). Dzięki temu Python wie, jakie pola ma wygenerować w konstruktorze, a nowoczesne edytory kodu (IDE) mogą podpowiadać błędy jeszcze przed uruchomieniem programu.

01
Nowoczesne struktury danych (Podstawy @dataclass)
Czego student się nauczy

Definiowania klas danych, używania dekoratora @dataclass oraz korzystania z automatycznie generowanych metod __repr__ i __eq__.

Scenariusz

W dzisiejszym świecie programowania, szybkość tworzenia czystego i efektywnego kodu jest kluczowym atutem każdego dewelopera. Tradycyjne podejście do definiowania klas, które służą jedynie do przechowywania informacji, wiąże się z koniecznością pisania wielu powtarzalnych metod, takich jak konstruktory czy systemy porównawcze. Twoim zadaniem jest zrewolucjonizowanie klasy reprezentującej profil studenta poprzez zastosowanie nowoczesnego dekoratora dataclass. Dzięki temu rozwiązaniu, Python automatycznie wygeneruje za Ciebie całą technologiczną otoczkę, pozwalając Ci skupić się wyłącznie na definiowaniu potrzebnych pól danych. Musisz sprawdzić, jak ten mechanizm radzi sobie z automatycznym tworzeniem czytelnych opisów obiektów oraz jak ułatwia weryfikację tożsamości dwóch różnych instancji. Takie podejście nie tylko skraca czas produkcji oprogramowania, ale również minimalizuje ryzyko wystąpienia błędów w rutynowych częściach kodu. Twoim celem jest stworzenie lekkiej i przejrzystej struktury, która stanie się wzorcem dla całego systemu ewidencji akademickiej. Gotowy moduł będzie doskonałym przykładem na to, jak nowoczesne narzędzia języka Python podnoszą komfort pracy programisty.

Wymagania techniczne
  • Zaimportuj dekorator dataclass z wbudowanego modułu dataclasses.
  • Zdefiniuj klasę danych Student zawierającą pola: imie, nazwisko, nr_indeksu oraz srednia.
  • Wykorzystaj podpowiedzi typów (Type Hints) dla każdego zdefiniowanego pola klasy.
  • Utwórz dwie różne instancje klasy Student z identycznymi wartościami atrybutów.
  • Sprawdź działanie automatycznie wygenerowanej metody __eq__, porównując obiekty operatorem ==.
  • Wyświetl obiekty w konsoli, aby zweryfikować czytelność wygenerowanej metody __repr__.
  • Dodaj do klasy opcjonalne pole z wartością domyślną (np. kierunek='Informatyka').
  • Udowodnij, że @dataclass znacząco skraca kod, eliminując potrzebę ręcznego pisania konstruktora __init__.
Wskazówki wykonania
  • Zaimportuj dekorator za pomocą instrukcji from dataclasses import dataclass.
  • Umieść @dataclass bezpośrednio nad linią definicji Twojej klasy Student.
  • Zdefiniuj wszystkie pola wraz z ich typami, np. imie: str lub srednia: float.
  • Pamiętaj, że nie musisz pisać metody __init__ – Python wygeneruje ją automatycznie.
  • Aby dodać wartość domyślną dla pola, użyj prostego przypisania: kierunek: str = "Informatyka".
  • Zauważ, że pola z wartościami domyślnymi muszą znajdować się zawsze na samym końcu listy atrybutów.
  • Przetestuj reprezentację: print(s1) wyświetli teraz czytelny opis wszystkich pól i ich wartości.
  • Porównaj dwa obiekty: s1 == s2 zwróci True, jeśli wszystkie ich dane są identyczne.
  • Spróbuj stworzyć obiekt podając argumenty pozycyjnie – ich kolejność jest zgodna z definicją w kodzie.
  • To zadanie pokazuje, jak skutecznie wyeliminować nudny kod techniczny (boilerplate) z Twoich projektów.
Przykładowy ekran
>>> s1 = Student("Jan", "Kowalski", 12345, 4.5) >>> s2 = Student("Jan", "Kowalski", 12345, 4.5) >>> print(s1) Student(imie='Jan', nazwisko='Kowalski', nr_indeksu=12345, srednia=4.5) >>> print(s1 == s2) True
Wnioski do opracowania
  • Wyjaśnij szczegółowo, ile linii kodu technicznego oszczędza dekorator @dataclass w porównaniu do ręcznej implementacji metod __init__, __repr__ i __eq__.
  • Opisz zalety stosowania podpowiedzi typów (Type Hints) dla przejrzystości struktury danych i łatwości analizy kodu przez innych programistów.
  • Omów mechanizm automatycznego generowania metody porównawczej __eq__ i wyjaśnij, na podstawie których danych podejmowana jest decyzja o równości.
  • Przeanalizuj czytelność i przydatność domyślnej reprezentacji tekstowej (__repr__) generowanej automatycznie przez dekorator.
  • Zastanów się i uzasadnij, dlaczego pola posiadające wartości domyślne muszą bezwzględnie znajdować się na samym końcu definicji klasy.
  • Wnioskuj o znaczącym zmniejszeniu ryzyka wystąpienia błędów technicznych (boilerplate) dzięki powierzeniu generowania rutynowych metod interpreterowi.
  • Porównaj wydajność procesu tworzenia obiektów typu dataclass do tradycyjnych, ręcznie definiowanych klas w języku Python.
  • Opisz, w jaki sposób nowoczesne edytory kodu (IDE) wykorzystują metadane z dataclass do inteligentnego podpowiadania składni.
  • Sprawdź i potwierdź, czy zmiana kolejności definicji pól w kodzie wpływa na strukturę automatycznie generowanego konstruktora klasy.
Zadanie 1

Rozwiązanie

3. Fabryki wartości domyślnych

Python nie pozwala na używanie pustych list [] jako domyślnych wartości bezpośrednio w definicji pola (ponieważ lista byłaby współdzielona przez wszystkie obiekty). Rozwiązaniem jest funkcja field(default_factory=list), która tworzy nową listę dla każdej instancji.

02
Zaawansowane pola i fabryki (field, default_factory)
Czego student się nauczy

Prawidłowego definiowania domyślnych wartości dla typów mutowalnych (listy, słowniki) oraz ukrywania wybranych pól w reprezentacji tekstowej.

Scenariusz

Podczas projektowania rozbudowanych systemów obsługi zamówień, często napotykamy na trudności związane z inicjalizacją zmiennych typów złożonych, takich jak listy czy słowniki. Standardowe wartości domyślne w Pythonie mogą prowadzić do niebezpiecznych sytuacji, w których obiekty niespodziewanie współdzielą te same zasoby w pamięci. Twoim wyzwaniem jest zaimplementowanie inteligentnego systemu zamówień, który wykorzystuje fabryki pól do generowania unikalnych list produktów dla każdego nowego klienta. Musisz również zadbać o aspekt prywatności, ukrywając wrażliwe dane techniczne, takie jak hasła dostępowe, przed automatycznym wyświetlaniem w logach systemowych. Dzięki precyzyjnemu konfigurowaniu parametrów pól wewnątrz dekoratora dataclass, zyskasz pełną kontrolę nad tym, jak Twoje dane są prezentowane i przechowywane. Ważnym elementem zadania jest weryfikacja, czy dodanie artykułu do jednego koszyka nie wpływa na zawartość pozostałych aktywnych sesji zakupowych. Takie podejście gwarantuje najwyższą jakość i bezpieczeństwo przetwarzanych informacji biznesowych. Poprawna realizacja tego modułu pozwoli Ci opanować zaawansowane techniki zarządzania strukturami danych w profesjonalnych aplikacjach komercyjnych.

Wymagania techniczne
  • Zaimplementuj klasę Zamowienie wykorzystującą zaawansowaną konfigurację pól za pomocą field().
  • Zdefiniuj pole produkty jako listę, używając default_factory=list do poprawnej inicjalizacji.
  • Dodaj pole haslo i ustaw w nim parametr repr=False, aby ukryć jego wartość w komunikatach diagnostycznych.
  • Skonfiguruj pole id_transakcji, przypisując mu stałą wartość domyślną (np. 0).
  • Utwórz dwa osobne obiekty zamówień i dodaj produkt tylko do jednego z nich.
  • Sprawdź, czy zawartość listy produktów w drugim obiekcie pozostała pusta (brak współdzielenia pamięci).
  • Wyświetl raport o zamówieniu za pomocą print() i potwierdź brak hasła w wygenerowanym ciągu znaków.
  • Wyjaśnij, dlaczego stosowanie pustych list [] jako wartości domyślnych w dataclass jest błędem.
Wskazówki wykonania
  • Zaimportuj funkcję konfiguracji pól: from dataclasses import dataclass, field.
  • Dla atrybutu produkty zastosuj parametr field(default_factory=list).
  • Nigdy nie używaj zapisu produkty: list = [] w klasach danych – doprowadzi to do błędów logicznych.
  • Dla pola wrażliwego haslo ustaw parametr field(repr=False) w definicji.
  • Dzięki temu hasło nie pojawi się w automatycznym wyniku funkcji print(obiekt).
  • Możesz również dodać compare=False, aby dane pole nie brało udziału w porównywaniu dwóch instancji.
  • Przetestuj niezależność list: stwórz dwa osobne zamówienia i dodaj artykuł tylko do jednego z nich.
  • Sprawdź wynik print(zamowienie) i potwierdź, że haslo zostało poprawnie zamaskowane.
  • Parametr default_factory może przyjmować dowolną funkcję bezargumentową do inicjalizacji pola.
  • To zadanie uczy profesjonalnego zarządzania pamięcią i bezpieczeństwem informacji w nowoczesnych klasach.
Przykładowy ekran
>>> z1 = Zamowienie("Jan", "sekret123") >>> z2 = Zamowienie("Ewa", "haslo456") >>> z1.produkty.append("Laptop") >>> print(z2.produkty) [] >>> print(z1) Zamowienie(klient='Jan', produkty=['Laptop'])
Wnioski do opracowania
  • Wyjaśnij precyzyjnie, dlaczego parametr repr=False jest kluczowy dla ochrony danych wrażliwych (np. haseł) w logach systemowych.
  • Opisz niebezpieczeństwo zjawiska "współdzielenia stanu" przy próbie użycia pustych list [] jako bezpośrednich wartości domyślnych.
  • Omów zasadę działania default_factory=list i wyjaśnij, dlaczego gwarantuje ona pełną unikalność list dla każdej nowej instancji.
  • Przeanalizuj szerokie możliwości konfiguracji poszczególnych pól za pomocą funkcji field() (np. wykluczanie konkretnych atrybutów z porównań).
  • Zastanów się, w jaki sposób można wykorzystać default_factory do automatycznej inicjalizacji pól obiektami innych, złożonych klas.
  • Wnioskuj o poprawie bezpieczeństwa i szczelności struktur informacji biznesowych dzięki precyzyjnej konfiguracji widoczności atrybutów.
  • Porównaj elastyczność i bezpieczeństwo fabryk pól (factories) do stosowania statycznych wartości domyślnych w konstruktorach.
  • Opisz, jak funkcja asdict radzi sobie z polami, które zostały celowo wyłączone z reprezentacji tekstowej (repr) lub inicjalizacji.
  • Sprawdź doświadczalnie, czy pole zdefiniowane z parametrem init=False może być mimo wszystko ustawione ręcznie po utworzeniu obiektu.
Zadanie 2

Rozwiązanie

4. Niezmienność i sortowanie

Parametr frozen=True sprawia, że obiekt staje się "tylko do odczytu". Parametr order=True generuje metody porównania (<, >), co pozwala na automatyczne sortowanie obiektów w listach.

03
Niezmienne punkty i ranking (frozen i order)
Czego student się nauczy

Tworzenia obiektów typu Immutable oraz wykorzystywania automatycznego generowania operatorów porównania do sortowania danych.

Scenariusz

W zaawansowanej grafice komputerowej oraz systemach rankingowych, gwarancja niezmienności danych po ich utworzeniu jest fundamentem stabilności całej aplikacji. Wyobraź sobie, że pracujesz nad silnikiem 3D, w którym raz zdefiniowane współrzędne wierzchołków nie powinny ulec przypadkowej modyfikacji podczas obliczeń fizycznych. Twoim zadaniem jest stworzenie klasy reprezentującej "zamrożone" punkty w przestrzeni, które będą całkowicie odporne na jakiekolwiek próby nadpisania ich wartości. Jednocześnie musisz opracować system rankingu dla graczy, który dzięki automatycznie generowanym operatorom porównania pozwoli na błyskawiczne sortowanie wyników. System powinien samodzielnie decydować o kolejności w tabeli na podstawie zdobytych punktów i czasu osiągnięcia celu, bez konieczności pisania własnych algorytmów sortujących. Wykorzystanie parametrów frozen oraz order w klasach danych znacząco podnosi bezpieczeństwo i wydajność Twojego oprogramowania. Dzięki takiemu podejściu, Twój kod stanie się bardziej deklaratywny i łatwiejszy do analizy przez innych członków zespołu. Gotowe rozwiązanie będzie stanowiło solidną podstawę dla modułu statystyk w Twojej nowej grze komputerowej.

Wymagania techniczne
  • Skonstruuj klasę danych Punkt3D, ustawiając parametr frozen=True w dekoratorze.
  • Spróbuj zmodyfikować wartość jednej ze współrzędnych utworzonego punktu i przechwyć błąd FrozenInstanceError.
  • Opracuj klasę Wynik przechowującą punkty oraz gracz, aktywując automatyczne porównywanie przez order=True.
  • Zapewnij poprawną kolejność pól: Python porównuje obiekty, biorąc pod uwagę atrybuty od góry do dołu.
  • Stwórz listę zawierającą co najmniej trzy nieposortowane obiekty klasy Wynik.
  • Wykorzystaj wbudowaną funkcję sorted() lub metodę .sort() do automatycznego uporządkowania rankingu.
  • Sprawdź działanie operatorów porównania (np. w1 < w2) bezpośrednio na instancjach klas danych.
  • Uzasadnij zastosowanie parametrów frozen w systemach wielowątkowych oraz order w tabelach wyników.
Wskazówki wykonania
  • Użyj dekoratora z parametrem blokującym zmiany: @dataclass(frozen=True) dla klasy punktu.
  • Sprawdź, czy próba przypisania p.x = 10 rzuci błąd FrozenInstanceError w konsoli.
  • Obiekty typu frozen są haszowalne, co pozwala na ich bezpieczne używanie jako kluczy w słownikach.
  • Dla klasy tabeli wyników użyj zapisu @dataclass(order=True) w nagłówku.
  • Python sam wygeneruje metody magiczne __lt__, __le__, __gt__ oraz __ge__.
  • Porównywanie obiektów odbywa się pole po polu, zaczynając od pierwszego zadeklarowanego atrybutu.
  • Przetestuj sortowanie rankingu: lista_wynikow.sort() automatycznie ułoży graczy według punktów.
  • Zwróć uwagę na kolejność pól – atrybut, według którego chcesz sortować, musi być zdefiniowany jako pierwszy.
  • Sprawdź działanie operatora mniejszości w1 < w2 bezpośrednio na instancjach Twoich klas danych.
  • To zadanie uczy budowania wysoce bezpiecznych i łatwo porządkowalnych struktur informacji technicznych.
Przykładowy ekran
>>> p = Punkt3D(1, 2, 3) >>> p.x = 5 dataclasses.FrozenInstanceError >>> w1 = Wynik(100, "Adam") >>> w2 = Wynik(150, "Ewa") >>> print(w1 < w2) True
Wnioski do opracowania
  • Wyjaśnij, w jaki sposób obiekty "zamrożone" (frozen) znacząco poprawiają stabilność i przewidywalność systemów pracujących wielowątkowo.
  • Opisz techniczny mechanizm automatycznego generowania kompletu operatorów porównania (<, >) przy użyciu parametru order=True.
  • Omów kluczowy wpływ kolejności definicji pól w klasie na wynik porównywania obiektów (tzw. zasada porównywania leksykograficznego).
  • Przeanalizuj komunikat błędu FrozenInstanceError i wskaż sytuacje projektowe, w których jest on wysoce pożądanym zachowaniem.
  • Zastanów się nad zaletami używania obiektów typu frozen jako unikalnych kluczy w słownikach dzięki ich wrodzonej haszowalności.
  • Wnioskuj o przydatności mechanizmu automatycznego sortowania przy budowaniu tabel wyników, rankingów graczy oraz zestawień cenowych.
  • Porównaj bezpieczeństwo danych w dataclass(frozen=True) do standardowych tupli (n-tek) pod kątem czytelności i wygody dostępu.
  • Opisz, jak aktywacja order=True w sposób natywny integruje Twoje autorskie klasy z wbudowaną systemową funkcją sorted().
  • Sprawdź, czy istnieje techniczna możliwość "odmrożenia" obiektu (np. poprzez dziedziczenie) i jakie niesie to ze sobą ryzyka dla spójności danych.
Zadanie 3

Rozwiązanie

5. Serializacja (Utrwalanie danych)

Serializacja to zamiana obiektu na format, który można zapisać (np. JSON lub binarny Pickle). JSON jest standardem wymiany danych w internecie, natomiast Pickle jest specyficzny dla Pythona i pozwala zapisywać skomplikowane obiekty.

04
Komunikacja tekstowa (Serializacja do JSON)
Czego student się nauczy

Konwersji obiektów @dataclass na słowniki oraz zapisu i odczytu danych w uniwersalnym formacie JSON.

Scenariusz

W dobie rozproszonych systemów informatycznych, umiejętność sprawnej wymiany danych między różnymi aplikacjami za pomocą standardu JSON jest absolutnie niezbędna. Twoja aplikacja monitorująca stan czujników w inteligentnym budynku musi regularnie przesyłać odczyty temperatury i wilgotności do centralnego serwera analitycznego. Wyzwanie polega na tym, że serwer nie rozumie obiektów specyficznych dla języka Python i oczekuje danych w czystym formacie tekstowym. Twoim zadaniem jest zaimplementowanie procesu serializacji, który zamieni Twoje zaawansowane klasy danych na uniwersalne słowniki, gotowe do wysyłki przez sieć. Musisz również opracować procedurę odwrotną, która pozwoli serwerowi na poprawne odtworzenie obiektu z otrzymanego ciągu znaków przy użyciu mechanizmu rozpakowywania parametrów. Takie podejście zapewnia pełną interoperacyjność Twojego systemu i pozwala na łatwą współpracę z technologiami takimi jak JavaScript czy Java. Ważne jest, aby proces ten był bezpieczny i odporny na uszkodzenia struktury przesyłanych informacji. Gotowy moduł komunikacji stanie się mostem łączącym Twoją aplikację z szerokim ekosystemem usług internetowych.

Wymagania techniczne
  • Zdefiniuj klasę danych Sensor modelującą odczyty z czujników inteligentnego domu.
  • Wykorzystaj funkcję asdict z modułu dataclasses do konwersji obiektu na czysty słownik Pythona.
  • Zaimportuj moduł json i użyj funkcji json.dumps() do transformacji słownika na ciąg znaków (string).
  • Zaimplementuj proces odwrotny: przekształć tekst JSON z powrotem na słownik za pomocą json.loads().
  • Odtwórz pełnoprawny obiekt klasy Sensor, wykorzystując operator rozpakowywania słownika **.
  • Dodaj do klasy Sensor metodę to_json(), która hermetyzuje cały proces serializacji.
  • Sprawdź poprawność transferu danych, porównując atrybuty obiektu oryginalnego z odtworzonym.
  • Wyjaśnij, jakie korzyści daje standard JSON w komunikacji między systemami napisanymi w różnych językach.
Wskazówki wykonania
  • Zaimportuj funkcję konwersji do słownika: from dataclasses import asdict.
  • Metoda to_json(self) powinna wywołać funkcję json.dumps(asdict(self)).
  • Pamiętaj, że asdict zamienia obiekt oraz wszystkie jego zagnieżdżenia na czyste słowniki Pythona.
  • Do odczytu danych tekstowych użyj json.loads(json_string), co zwróci Ci gotowy słownik.
  • Odtwórz pełny obiekt klasy: Sensor(**dane_slownika) – podwójna gwiazdka rozpakowuje klucze na argumenty.
  • Pamiętaj o różnicach typów: format JSON nie rozróżnia natywnie krotek od list (zamienia je na tablice).
  • Przetestuj symulowany przesył danych między dwoma odrębnymi instancjami w Twoim systemie.
  • Zauważ, że asdict działa rekurencyjnie, co jest nieocenione przy bardzo złożonych strukturach danych.
  • To zadanie pokazuje, jak skutecznie zintegrować Twoje klasy z uniwersalnymi formatami wymiany informacji.
  • Zastanów się, jak obsłużyć pola niestandardowe (np. obiekty daty), których JSON domyślnie nie potrafi zapisać.
Przykładowy ekran
>>> s = Sensor("Kuchnia", 22.5) >>> json_str = json.dumps(asdict(s)) >>> print(json_str) {"nazwa": "Kuchnia", "temp": 22.5} >>> dane = json.loads(json_str) >>> s_nowy = Sensor(**dane)
Wnioski do opracowania
  • Wyjaśnij szczegółowo, dlaczego funkcja json.dumps() nie potrafi bezpośrednio zapisać obiektu klasy Sensor bez uprzedniego użycia asdict.
  • Opisz mechanizm rekurencyjnego działania funkcji asdict na bardzo złożonych i głęboko zagnieżdżonych strukturach danych.
  • Omów strategiczne zalety formatu JSON jako uniwersalnego i tekstowego standardu wymiany informacji między systemami w różnych językach.
  • Przeanalizuj techniczny proces odtwarzania obiektu ze słownika przy użyciu operatora rozpakowywania ** (podwójna gwiazdka).
  • Zastanów się nad typowymi problemami serializacji typów niestandardowych (np. obiekty daty lub bajtów) do ograniczonego formatu JSON.
  • Wnioskuj o łatwości integracji Twoich aplikacji Pythonowych z nowoczesnymi usługami webowymi opartymi całkowicie na formacie tekstowym.
  • Porównaj serializację tekstową JSON do serializacji binarnej (np. Pickle) pod kątem czytelności dla człowieka oraz bezpieczeństwa danych.
  • Opisz, w jaki sposób implementacja metody to_json() bezpośrednio wewnątrz klasy poprawia enkapsulację całego procesu eksportu.
  • Sprawdź i udokumentuj, czy funkcja asdict zachowuje oryginalne typy danych pól (np. krotka zamieniona na listę) po konwersji.
Zadanie 4

Rozwiązanie

6. Post-inicjalizacja

Metoda __post_init__ pozwala na wykonanie dodatkowej logiki (np. walidacji) natychmiast po tym, jak konstruktor przypisze wartości do pól.

7. Pickle (Format binarny)

Moduł pickle pozwala "marynować" obiekty w formie binarnej. Jest szybszy i obsługuje niemal wszystkie typy danych, ale pliki te są nieczytelne dla człowieka i innych języków programowania.

8. Bezpieczeństwo serializacji

Nigdy nie używaj pickle.load() na plikach pochodzących z nieznanego źródła, ponieważ proces odczytu może wykonać złośliwy kod ukryty w pliku binarnym.

9. Rekurencyjne struktury

Dataclasses świetnie radzą sobie z zagnieżdżeniem (np. obiekt Zamowienie może zawierać listę obiektów Produkt), a funkcja asdict poprawnie zamieni całą strukturę na słowniki.

10. Podsumowanie danych

Poznałeś nowoczesne metody tworzenia struktur danych w Pythonie. Potrafisz je automatyzować, chronić przed zmianą oraz trwale zapisywać do plików. Twoje aplikacje mogą teraz bezpiecznie wymieniać dane z otoczeniem.

05
System zapisu stanu gry (Kompletny cykl zapisu i odczytu)
Czego student się nauczy

Budowania kompletnego mechanizmu trwałości danych (Persistence) w aplikacji, obsługi błędów odczytu i pracy z plikami na dysku.

Scenariusz

Każdy gracz oczekuje, że jego postępy w wirtualnym świecie zostaną trwale i bezpiecznie zapisane na dysku, aby mógł wrócić do zabawy w dowolnym momencie. Twoim zadaniem jest zaprojektowanie i wdrożenie kompletnego systemu SaveManager, który obsłuży pełny cykl życia danych Twojej postaci. Musisz stworzyć mechanizm, który nie tylko zapisze statystyki gracza do przejrzystego pliku JSON, ale również będzie potrafił je poprawnie odczytać przy starcie nowej sesji. Kluczowym elementem projektu jest inteligentna obsługa błędów, która uchroni aplikację przed zawieszeniem w przypadku braku pliku zapisu lub jego uszkodzenia. Zadbaj o to, aby dane były przechowywane w formacie czytelnym dla człowieka, co ułatwi ewentualne debugowanie i pozwoli na zaawansowaną konfigurację parametrów gry. System musi gwarantować poprawne kodowanie znaków, tak aby imiona postaci zawierające polskie litery były zawsze wyświetlane bezbłędnie. Dzięki wykorzystaniu menedżerów kontekstu, zapewnisz bezpieczeństwo operacji na plikach i unikniesz ryzyka utraty danych przy nagłym przerwaniu pracy programu. Poprawna realizacja tego zadania będzie końcowym sprawdzianem Twoich umiejętności w zakresie trwałego przechowywania stanów obiektowych.

Wymagania techniczne
  • Opracuj klasę Player reprezentującą stan bohatera w grze (imie, poziom, ekwipunek).
  • Stwórz klasę narzędziową SaveManager posiadającą statyczne metody save() oraz load().
  • Zaimplementuj w metodzie save() bezpieczny zapis obiektu do pliku savegame.json przy użyciu with open().
  • Zastosuj kodowanie utf-8 oraz parametr indent=4, aby plik był czytelny i poprawnie obsługiwał znaki specjalne.
  • Dodaj w metodzie load() obsługę błędów przy użyciu bloku try...except, reagując na brak pliku (FileNotFoundError).
  • Zapewnij automatyczne odtworzenie instancji klasy Player na podstawie danych wczytanych z dysku.
  • Dodaj weryfikację spójności danych po odczycie (np. sprawdzenie czy poziom jest liczbą dodatnią).
  • Przeprowadź test kompletnego cyklu: utwórz postać, zapisz ją, usuń z pamięci i wczytaj ponownie z pliku.
  • Klasa Player powinna być zdefiniowana jako standardowa, lekka @dataclass.
  • W metodzie save(player) otwórz plik tekstowy: with open("save.json", "w", encoding="utf-8") as f:.
  • Wykorzystaj funkcję json.dump(asdict(player), f, indent=4) w celu uzyskania czytelnego formatowania.
  • W metodzie load() zastosuj blok try...except FileNotFoundError: dla ochrony przed awarią.
  • Jeśli plik zapisu nie istnieje, Twoja metoda load() może zwracać nową, domyślną instancję gracza.
  • Po wczytaniu surowego słownika z pliku, przekaż go do konstruktora klasy: return Player(**dane).
  • Przetestuj zapisywanie imion postaci z polskimi znakami i zweryfikuj ich poprawność w pliku wynikowym.
  • Dodaj pomocniczą metodę info() do klasy gracza, aby móc szybko sprawdzić jego stan po operacji odczytu.
  • Sprawdź odporność systemu: spróbuj ręcznie usunąć lub zmienić nazwę pliku save.json przed startem.
  • To zadanie uczy budowania profesjonalnych i kompletnych mechanizmów trwałości danych w Twoich aplikacjach.
Przykładowy ekran
>>> p = Player("Aragorn", 100, 5) >>> SaveManager.save(p) Stan gry zapisany pomyślnie. >>> p_loaded = SaveManager.load() Wczytano postać: Aragorn (Poziom 5)
Wnioski do opracowania
  • Wyjaśnij kluczowe zalety oraz potencjalne wady pozwalania użytkownikom na swobodną, ręczną edycję plików zapisu w formacie JSON.
  • Opisz rolę menedżera kontekstu with open() w gwarantowaniu absolutnie poprawnego i bezpiecznego zamknięcia plików na dysku.
  • Omów krytyczne znaczenie kodowania utf-8 dla bezbłędnej obsługi polskich znaków w imionach postaci oraz opisach przedmiotów.
  • Przeanalizuj skuteczną strategię obsługi błędu FileNotFoundError (np. poprzez automatyczne tworzenie domyślnego profilu gracza).
  • Zastanów się i uzasadnij, jak parametr indent=4 wpływa na czytelność plików konfiguracyjnych i łatwość debugowania stanów gry.
  • Wnioskuj o bezwzględnej konieczności weryfikacji spójności i poprawności danych (walidacja) natychmiast po ich wczytaniu z zewnętrznego pliku.
  • Porównaj trwałość danych (persistence) opartą na prostych plikach lokalnych do profesjonalnego przechowywania stanu w relacyjnej bazie danych.
  • Opisz proces "marynowania" (Pickle) jako wydajną alternatywę dla JSON przy pracy z bardzo złożonymi grafami powiązanych ze sobą obiektów.
  • Sprawdź odporność Twojego systemu na celowe uszkodzenie struktury pliku JSON (np. poprzez usunięcie klamry zamykającej lub przecinka).
Zadanie 5