Geavanceerde Python-ontwikkelingscursus
Hoofdstuk
>
Niveau

Serialisatiemodules
Struct Module

Doelstelling

Stel de uiteindelijke gegevensverdeling voor de nieuwe landbouwgrond in met behulp van de struct-module.

Aan het einde van de weg staat een tankstation dat de al aangelegde nieuwe landbouwgrond beheert en waar de gewassen al zijn geplant. Hier zullen we de gegevens van de reeds geplante gewassen en de verwachte opbrengst van de landbouwgrond inspecteren en verwerken. Net als bij andere niveaus in dit hoofdstuk werken we met het serialiseren en deserialiseren van gegevens en introduceren we één laatste module: de struct-module.

De struct-module introduceert een reeks serialisatiefuncties die gegevens in binaire vorm verpakt. In tegenstelling tot de andere modules waarmee we hebben gewerkt, heb je meer controle over hoe de gegevens kunnen worden gestructureerd, zowel bij het serialiseren als bij het deserialiseren, wat deze module veelzijdiger maakt dan de andere. Gebruik import struct om toegang te krijgen tot de volgende functies die we zullen gebruiken om de gegevens te verwerken:

  • struct.calcsize(): Bepaalt hoeveel bytes worden gebruikt door een gegeven format-string. Deze functie neemt één (1) argument: de format die je wilt controleren op bytegrootte.
  • De formats die we zullen gebruiken zijn:
    • integer: weergegeven als 'i', de format voor gegevens in gehele getallen
    • float: weergegeven als 'f', de format voor gegevens in kommagetallen
    • double: weergegeven als 'd', de format voor complexere kommagetallen waarvoor de float-format niet voldoende is.
  • struct.pack(): Serialiseert gegevens in binaire vorm, verpakt volgens een door jou gekozen format. Deze functie neemt twee (2) of meer argumenten: de format die je wilt gebruiken en de waarden die je wilt serialiseren. De formats zijn de eerder beschreven en je moet ze toevoegen in overeenstemming met het aantal toegevoegde waarden.
  • struct.unpack(): Deserialiseert verpakte binaire gegevens. Neemt twee (2) argumenten: de format zoals gebruikt bij het serialiseren en de gepackte gegevens zelf.
  • struct.iter_unpack(): Deserialiseert verpakte binaire gegevens. Werkt hetzelfde als struct.unpack(), maar iterereert door elk gegevensblok afzonderlijk via een lus.
  • struct.pack_into(): Een geavanceerde versie van struct.pack(). Neemt vier (4) argumenten: de format die je wilt gebruiken, de databuffer waarin je de gegevens wilt plaatsen, de positie in de buffer die de gegevens moeten innemen, en tenslotte de gegevens zelf.
  • struct.unpack_from(): Een geavanceerde versie van struct.unpack(). Neemt drie (3) argumenten: de format die je wilt gebruiken, de databuffer waaruit je wilt unpacken en ten slotte de locatie in de buffer. Hiermee kun je specifieke delen van de gegevens uitpakken.

Loop naar het lichte X-teken in het tankstation en ga achter het bureau staan. Maak drie (3) variabelen aan: integer, float en double. We gebruiken deze om de grootte in bytes van elke format te verifiëren met de functie struct.calcsize(). Voor de variabele integer gebruik je de functie met 'i' als argument, voor de variabele float gebruik je 'f' en voor double gebruik je 'd'. Bijvoorbeeld: integer = struct.calcsize('i'). Voeg de drie variabelen toe aan de vooraf geschreven write()-functie.

Loop naar het gouden X-teken en gebruik de functie read() om gegevens over de nieuwe landbouwgrond te verzamelen. Noteer de datapunten en formats voor toekomstig gebruik, namelijk: Resources, Size en Estimate. Zodra je dit hebt genoteerd, loop je naar het lichte X-teken op het blauwe tapijt en maak je een variabele met de naam blue_data aan.

Sla in de variabele blue_data de waarde van de functie struct.pack() op, waarbij je als argumenten de format en de waarden gebruikt die je eerder hebt genoteerd. Bij het opgeven van de format moet je de types “packen” in één enkele tekenreeks. Bijvoorbeeld: de integer-format is aangeduid als 'i'. Als je drie integerwaarden toevoegt, gebruik je 'iii'. Evenzo schrijf je bij een integer, een float en een double 'ifd'. Geef de gegevens vervolgens als afzonderlijke argumenten door. Hier is een voorbeeld:

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)

De functie struct.pack() biedt veel flexibiliteit, omdat je zelf kunt bepalen hoe je de gegevens formatteert en meerdere datapunten tegelijk kunt serialiseren. Voeg de eerder gelezen gegevens toe op basis van het bovenstaande voorbeeld. Gebruik de functie display() met blue_data als argument om de verpakte gegevens te bekijken.

Loop naar het donkere X-teken op het blauwe tapijt en ga bij de terminal staan. Maak een variabele blue_unpack aan en sla de waarde van de functie struct.unpack() hierin op, waarbij je dezelfde format gebruikt als bij het packen en de variabele blue_data als argumenten meegeeft. De format schrijf je op dezelfde manier als bij struct.pack(), zodat je de gegevens op dezelfde manier kunt deserialiseren als je ze hebt geserialiseerd. Gebruik de functie write() met blue_unpack als argument om de eerder verpakte gegevens te verifiëren.

Op dezelfde locatie zullen we ook de functie struct.iter_unpack() gebruiken. Deze neemt exact dezelfde argumenten als struct.unpack(), maar wordt nu gebruikt in een for-lus, waardoor we door de data kunnen itereren in plaats van alles in één keer te schrijven. De functie wordt als volgt geschreven:

for values in struct.iter_unpack(-insert value-, -insert value-): player.speak(values)

We gebruiken de functie speak() om de waarden weer te geven. Vul de ontbrekende argumenten in de functie struct.iter_unpack() in met dezelfde waarden die je eerder bij struct.unpack() hebt gebruikt, aangezien dit varianten van dezelfde functie zijn wat betreft het gebruik.

Loop naar het gouden X-teken op het rode tapijt en ga bij het bureau staan. Gebruik de functie read() om de hoeveelheden gewassen te bekijken. Noteer alle hoeveelheden en de bijbehorende format voor elk gewas. Loop vervolgens naar het lichte X-teken op het rode tapijt en maak een object buffer aan met de waarde bytearray(16). Dit is een verzameling bytes die we kunnen adresseren voordat we ze vullen; voor onze doeleinden werkt dit als een databank waarin je de gegevens handmatig kunt opslaan. Het argument 16 geeft aan hoeveel bytes je in het object buffer kunt opslaan.

Gebruik de functie struct.pack_into() om het eerder gemaakte object buffer te vullen. Je hoeft geen variabele te maken om de returnwaarde op te slaan, want de functie schrijft de waarden direct in het object buffer. We zullen de functie voor elk van de waarden gebruiken die we eerder hebben genoteerd en deze als argumenten invullen.

Bijvoorbeeld: voor de eerste gewaswaarde krijg je de format, de bytepositie en de bijbehorende hoeveelheid. Voeg de format toe als eerste argument, geef de locatie op waar je de bytes wilt serialiseren (in dit geval buffer). Voor het derde argument stel je de positie in waar je de waarde in de buffer wilt opnemen; de format bepaalt hoeveel bytes er worden gebruikt, dus zorg ervoor dat je gegevens niet overlappen. Voeg tenslotte de waarde toe die je wilt serialiseren en packen in het object buffer.

struct.pack_into('i', buffer, 0, 82)

Het object buffer heeft, zoals eerder vermeld, 16 bytes. In de bovenstaande code heeft de integer-format 4 bytes en wordt deze op positie 0 geplaatst. Dit betekent dat er nog 12 bytes vrij zijn in de buffer (posities 0-3 zijn bezet door de waarde 82). Doe dit voor alle eerder genoteerde gewasdatapunten; er zijn er in totaal drie (3). Gebruik de functie display() en geef buffer mee om de geserialiseerde gegevens weer te geven.

Loop naar het donkere X-teken op het rode tapijt en ga bij de terminal staan. Hier gaan we de gegevens deserialiseren om de inhoud te controleren en zo de juiste opslag te waarborgen. Maak drie variabelen aan: lettuce, carrots en melons. We zullen de gegevens van elk datapunt afzonderlijk uitpakken. Voor elke variabele sla je de waarde van struct.unpack_from() op en geef je als argumenten de eerder genoteerde gegevens voor het packen op. Als eerste argument gebruik je de format, als tweede geef je het object buffer op als bron en tenslotte de positie in de buffer waarvandaan je wilt uitpakken. Bijvoorbeeld:

lettuce = struct.unpack_from('i', buffer, 0)

Deze code komt overeen met het unpacken van het eerder gepackte voorbeeld. Doe hetzelfde voor de andere twee variabelen en voeg lettuce, carrots en melons toe aan de vooraf geschreven write()-functie om het niveau te voltooien.

Codeboek