Zaawansowany kurs programowania w Pythonie
Rozdział
>
Poziom
Moduły serializacji
Moduł struktur
Cel
Skonfiguruj ostateczne zestawienie danych dla nowego terenu rolnego, używając modułu struct.
Na końcu drogi znajduje się stacja serwisowa, która zarządza nowym terenem rolnym, już zbudowanym i zasianymi uprawami. Tutaj będziemy analizować i przetwarzać dane dotyczące już posianych roślin oraz prognozowanej wydajności terenu rolnego. Podobnie jak w innych poziomach tego rozdziału, będziemy pracować z serializacją i deserializacją danych, wprowadzając ostatni moduł o nazwie struct.
Moduł struct wprowadza szereg funkcji do serializacji, które pakują dane w formacie binarnym. W przeciwieństwie do innych modułów, z którymi pracowaliśmy, masz jednak większą kontrolę nad tym, jak zorganizować dane zarówno podczas ich serializacji, jak i późniejszej deserializacji, co sprawia, że jest bardziej wszechstronny niż inne moduły. Użyj import struct, aby uzyskać dostęp do następujących funkcji, których będziemy używać do przetwarzania danych:
struct.calcsize(): Określa, ile bajtów zajmuje określony ciąg formatu. Przyjmuje jeden (1) argument — format, którego rozmiar bajtowy chcesz sprawdzić. Formatów, których będziemy używać, jest:- integer: oznaczony jako
'i', to format dla danych reprezentowanych liczbami całkowitymi - float: oznaczony jako
'f', to format dla danych reprezentowanych liczbami zmiennoprzecinkowymi - double: oznaczony jako
'd', to format dla danych reprezentowanych bardziej złożonymi liczbami zmiennoprzecinkowymi, gdy format float jest niewystarczający.
- integer: oznaczony jako
struct.pack(): Serializuje dane w formie binarnej, pakując je w wybranym formacie. Może przyjmować dwa (2) lub więcej argumentów: format, którego chcesz użyć, oraz wartości, które chcesz zserializować. Format to te, które zostały wcześniej opisane, i musisz dodać je odpowiadająco do liczby przekazywanych argumentów.struct.unpack(): Deserializuje spakowane dane binarne, przyjmuje dwa (2) argumenty: format, który musi odpowiadać temu, w jakim dane zostały zserializowane, oraz same zserializowane dane.struct.iter_unpack(): Deserializuje spakowane dane binarne, działa tak samo jakstruct.unpack(), ale iteruje przez każdy blok danych indywidualnie, używając pętli.struct.pack_into(): Zaawansowana wersjastruct.pack(), przyjmuje cztery (4) argumenty: format, którego chcesz użyć; bufor danych, do którego chcesz włożyć dane; pozycję w buforze, na której dane mają się znaleźć; oraz dane, które pakujesz.struct.unpack_from(): Zaawansowana wersjastruct.unpack(), przyjmuje trzy (3) argumenty: format, którego chcesz użyć; bufor danych, z którego chcesz deserializować; oraz lokalizację w buforze, z której chcesz deserializować. Pozwala to wydobyć konkretne fragmenty danych.
Przejdź do jasnego znaku X w stacji serwisowej i zwróć się w stronę biurka, stwórz trzy (3) zmienne o nazwach: integer, float i double. Posłużą nam one do sprawdzenia rozmiaru każdego formatu w bajtach za pomocą funkcji struct.calcsize(). Dla zmiennej integer użyj funkcji z argumentem 'i', dla float — argumentem 'f', a na końcu dla double — argumentem 'd'. Na przykład: integer = struct.calcsize('i'). Dodaj trzy (3) zmienne do wstępnie przygotowanej funkcji write().
Podejdź do złotego znaku X i użyj funkcji read(), aby zebrać dane na temat nowego terenu rolnego. Zwróć uwagę na punkty danych i ich format do przyszłego użytku, a mianowicie: Resources, Size i Estimate. Gdy je zanotujesz, przejdź do jasnego znaku X nad niebieskim dywanem i stwórz zmienną o nazwie blue_data.
W zmiennej blue_data przechowaj wynik funkcji struct.pack(), przekazując jako argumenty format i wartości, które wcześniej zanotowałeś. Pisząc format, musisz „spakować” typy formatu w jedną jednostkę. Na przykład format integer oznaczony jest 'i'; jeśli dodajesz trzy liczby całkowite, zapisujesz 'iii'. Podobnie, jeśli dodajesz integer, float i double, zapisz 'ifd'. Dodając dane, umieść je osobno jako argumenty. Poniżej ogólny przykład:
data_1 = 8 # is an integer data_2 = 2.25 # is a float data_3 = 900.702938103 # is a double blue_data = struct.pack('ifd', data_1, data_2, data_3)
Funkcja struct.pack() jest bardzo elastyczna, pozwala określić, jak formatować dane, oraz umożliwia jednoczesną serializację wielu punktów danych według własnego uznania. Dodaj wcześniej odczytane dane, wykorzystując powyższy przykład jako bazę. Użyj funkcji display(), przekazując blue_data jako argument, aby wyświetlić spakowane dane.
Podejdź do ciemnego znaku X na niebieskim dywanie i zwróć się w stronę terminala, utwórz zmienną o nazwie blue_unpack i zapisz w niej wynik funkcji struct.unpack(), przekazując te same argumenty: format użyty podczas pakowania danych oraz zmienną blue_data. Format zapisuje się tak samo jak w funkcji struct.pack(), dzięki czemu możesz zdeserializować dane w taki sam sposób, w jaki je pierwotnie serializowałeś. Użyj funkcji write(), przekazując blue_unpack jako argument, aby zweryfikować wcześniej spakowane dane.
W tym samym miejscu użyjemy także funkcji struct.iter_unpack(). Przyjmuje ona dokładnie te same argumenty co struct.unpack(), ale jest używana w pętli for, co pozwala iterować przez dane zamiast wypisywać je wszystkie naraz. Funkcja ma następującą postać:
for values in struct.iter_unpack(-insert value-, -insert value-): player.speak(values)
Będziemy używać funkcji speak() do wyświetlania wartości. W brakujących argumentach struct.iter_unpack() wstaw te same, które wcześniej użyłeś w struct.unpack(), ponieważ są to warianty tej samej funkcji pod względem użycia.
Podejdź do złotego znaku X na czerwonym dywanie i zwróć się w stronę biurka, użyj funkcji read(), aby przejrzeć ilości plonów. Zwróć uwagę na wszystkie ilości i format dla każdego plonu. Przejdź do jasnego znaku X na czerwonym dywanie i stwórz obiekt o nazwie buffer, przypisując mu wartość bytearray(16). To kolekcja bajtów, do której możemy się odwoływać przed jej wypełnieniem; dla naszych potrzeb działa jak bank danych, w którym można ręcznie przechowywać dane. Liczba 16 jako argument określa, ile bajtów można przechować w obiekcie buffer.
Użyj funkcji struct.pack_into(), aby wypełnić utworzony obiekt buffer. Nie ma potrzeby tworzenia zmiennej na przechowanie wyniku tej funkcji, ponieważ sama funkcja wstawi wartości bezpośrednio do obiektu buffer. Użyjemy tej funkcji dla każdego wcześniej zanotowanego parametru i wypełnimy jej argumenty tymi wartościami.
Na przykład dla pierwszej wartości plonów otrzymasz jej format, pozycję w bajtach oraz ilość. Dodaj jako pierwszy argument format, drugim argumentem podaj bufor, do którego chcesz zserializować bajty — w tym przypadku buffer. Jako trzeci argument ustaw pozycję, na której ma zostać umieszczona wartość w buffer — format określa liczbę zużywanych bajtów, więc upewnij się, że pakujesz dane w nieużywaną wcześniej przestrzeń. Na końcu dodaj wartość, którą chcesz zserializować i spakować w obiekt buffer.
struct.pack_into('i', buffer, 0, 82)
Obiekt buffer ma, jak wcześniej wspomniano, 16 bajtów. W powyższym kodzie format integer zajmuje 4 bajty i jest wstawiany na pozycję 0. Oznacza to, że po wstawieniu tej wartości pozostaje w buffer tylko 12 nieużywanych bajtów, a pozycje 0–3 są zajęte wartością 82. Zrób to samo dla wszystkich wcześniej odczytanych i zanotowanych wartości plonów — jest ich łącznie trzy (3). Użyj funkcji display() i przekaż jej buffer, aby wyświetlić zserializowane dane.
Podejdź do ciemnego znaku X na czerwonym dywanie i zwróć się w stronę terminala. Tutaj zdeserializujemy dane, aby zweryfikować ich zawartość i upewnić się, że zostały poprawnie przechowane. Utwórz trzy zmienne o nazwach: lettuce, carrots i melons; będziemy wypakowywać dane każdego z tych punktów osobno. W każdej zmiennej zapisz wynik struct.unpack_from(), przekazując argumenty odpowiadające tym, których użyłeś przy pakowaniu. Pierwszym argumentem będzie format, drugim obiekt buffer, z którego chcesz wypakować dane, a na końcu pozycja w buffer, z której chcesz odczytać dane. Na przykład:
lettuce = struct.unpack_from('i', buffer, 0)
Ten kod odpowiada wcześniejszemu przykładowi pakowania, tylko w odwrotnej kolejności. Zrób to samo dla dwóch pozostałych zmiennych i przekaż lettuce, carrots oraz melons do wstępnie przygotowanej funkcji write(), aby ukończyć poziom.