0%

Dataclasses en waardeobjecten · oefening

Maak je eerste dataclass

Python kan de boilerplate uit de vorige les schrijven. Dit is de volledige Result:

Try it

Vijf regels. De __init__, de __repr__ en de __eq__ bestaan allemaal, en alle drie kennen elk attribuut omdat ze alle drie uit dezelfde lijst zijn gegenereerd.

De fout uit de vorige les is hier niet gerepareerd. Die kan niet meer ontstaan. De drie plekken kunnen niet van elkaar afwijken, omdat er maar één plek is.

De regel bovenaan

from dataclasses import dataclass is de eerste import in deze cursus, dus die verdient een zin uitleg.

Python wordt geleverd met een grote verzameling kant-en-klare hulpmiddelen: de standaardbibliotheek. Die zijn niet standaard beschikbaar, omdat alles in elk programma laden verspilling zou zijn en je naamruimte met honderden namen zou vullen. Een import-regel zegt welke je wilt. Deze vraagt om het hulpmiddel dataclass uit de verzameling dataclasses, waarna dataclass een gewone naam is die je kunt gebruiken.

Meer heb je voor dit hoofdstuk niet nodig. Hoofdstuk 10 behandelt imports uitgebreid, inclusief het schrijven van eigen modules. Het is een omvangrijk onderwerp. Beschouw deze regel tot die tijd als de schrijfwijze die bij dataclasses hoort.

Wat elke regel doet

@dataclass
class Result:
    learner: str
    score: int
    total: int

@dataclass boven een klasse heeft dezelfde vorm als @property boven een methode. Het geeft de klasse aan een hulpmiddel dat die inspecteert en methoden toevoegt.

Binnen de klasse is learner: str een annotatie: de naam van een attribuut en het type dat het hoort te bevatten. Je kwam annotaties op functies tegen in hoofdstuk 2, waar Python ze niet afdwingt en hulpmiddelen ze gebruiken voor controles. Hier geldt hetzelfde, met één toevoeging: @dataclass leest deze regels om te ontdekken welke attributen bestaan. Het is de enige voorziening in deze cursus waarbij een annotatie verandert wat het programma doet.

De typen worden dus nog steeds niet afgedwongen:

Try it

Python maakt het zonder bezwaar aan. De annotatie legt de bedoeling vast en laat een typechecker je waarschuwen. Als een waarde echt een getal moet zijn, is dat een taak uit hoofdstuk 5: valideer die.

Wat doet score: int binnen een dataclass?

Wat je wel en niet krijgt

@dataclass genereert __init__, __repr__ en __eq__. Dat is de standaard en het is nuttig precies te weten waar die stopt:

Try it

Geen __hash__, om de reden uit hoofdstuk 7: een dataclass is standaard veranderbaar, dus hashen zou onveilig zijn. Les 5 laat de instelling zien die een dataclass tegelijk onveranderbaar en hashbaar maakt.

Er is ook geen __str__, automatische validatie of ordening tenzij je erom vraagt.

Je eigen methoden horen er nog steeds bij

Een dataclass is een gewone klasse. Alles wat je weet blijft gelden:

Try it

De gegenereerde __init__ roept __post_init__ aan nadat de velden zijn toegewezen. Dat aanknopingspunt is waar een dataclass de aanmaakregels uit hoofdstuk 5 bewaart. Gegenereerde __init__, __repr__ en __eq__; handgeschreven validatie, is_correct en __str__. Voor deze methoden blijft een definitie die je zelf schrijft behouden in plaats van een gegenereerde versie.

Dat is het doel van deze voorziening. Die verwijdert de regels die je toch al zou typen en laat de ontwerpbeslissingen precies waar ze waren.

Opdracht

Vervang het handgeschreven mechanische werk door gegenereerd werk en behoud alles wat een echte beslissing was.

Result is al omgezet en de import staat er al. Gebruik die als voorbeeld om Duration en Question om te zetten in dataclasses. Elke klasse verliest de eigen __init__, __repr__ en __eq__ en krijgt een geannoteerde attribuutregel voor elke waarde die die bevat. Houd de attribuutnamen en hun volgorde precies zoals ze zijn, omdat de constructoraanroepen onderaan daarvan afhangen.

Behoud elke methode die niet werd gegenereerd: Duration.total_seconds, Question.is_correct en Question.__str__. Herstel de aanmaakregels van Question in __post_init__: de vraagtekst en het antwoord moeten niet-leeg zijn en de punten moeten groter zijn dan nul.

Voeg geen __hash__ toe. De volgende lessen leggen uit waarom dat niet simpelweg een vergissing is.

Het gedrag en de eigen weergave moeten gelijk blijven. De gegenereerde repr bevat nu veldnamen, zoals Question(prompt=..., answer=..., points=...), dus die regel verandert wel.