Dátové triedy
ako na dátové triedy pomocou triedy BaseModel
Dátové triedy sú špeciálne triedy, ktoré slúžia na prenos dát. Obsahujú obyčajne iba členské premenné a minimum logiky. Často sa používajú na manažment konfigurácie alebo na správu serializovateľných údajov, ktoré sa napr. prenášajú prostredníctvom REST API alebo protokolom MQTT. Štandardná knižnica jazyka Python obsahuje priamu podporu v balíku dataclasses, ale najznámejšou knižnicou je rozhodne Pydantic.
Natívna podpora dátových tried v jazyku MicroPython však nie je a rovnako
vlastnosti tohto jazyka obmedzujú ich plnohodnotné využívanie. Pre naše
potreby budeme používať jednoduchú implementáciu v podobe triedy
BaseModel z modulu models/base.py, ktorá je
inšpirovaná práve knižnicou Pydantic. Jej kód je
súčasťou kostry projektu
chytrého zariadenia.
Vlastnosti triedy
BaseModel
- typová kontrola pri priradení hodnoty – typ členskej premennej
určuje jej predvolená hodnota v triede, prípadne parameter
typefunkcieField(); pri priradení hodnoty iného typu vznikne výnimkaValueError, - podrobnejší opis členskej premennej pomocou funkcie
Field()– predvolená hodnota, továreň na predvolenú hodnotu (default_factory), typ a voliteľnosť, - povinné a voliteľné členské premenné – premenná bez predvolenej
hodnoty je povinná, voliteľná premenná môže mať aj hodnotu
None, - slot-like správanie – nie je možné pridávať nové atribúty,
ktoré nie sú definované v triede (vznikne výnimka
AttributeError), - vnorené dátové triedy – slovník priradený do členskej premennej typu
BaseModelsa automaticky prevedie na objekt, - dekorátor
validatorna tvorbu vlastných funkcií na validáciu členských premenných – validácia sa spustí automaticky pri každom priradení novej hodnoty, - iterovateľnosť – iterácia vracia dvojice
(názov, hodnota), - export dát do slovníka pomocou metódy
.model_dump()– exportujú sa aj vnorené dátové triedy a zoznamy dátových tried, - reprezentácia objektu pomocou
__repr__()v tvareClassName(field=value, ...).
Vytvorenie triedy
Dátovú triedu vytvoríme ako potomka triedy BaseModel.
Jej členské premenné uvedieme ako triedne premenné. Typ každej premennej
určí jej predvolená hodnota, prípadne parameter type
funkcie Field(). Typové anotácie (napr.
name: str) slúžia len na lepšiu čitateľnosť kódu, pretože
jazyk MicroPython ich pri behu
ignoruje.
from models.base import BaseModel, Field
class Address(BaseModel):
city: str = Field(type=str)
zip: str = Field(type=str)
class Person(BaseModel):
name: str = Field(type=str)
age: int = 0
email: str = Field(default=None, type=str, optional=True)
address: Address = Field(default=None, type=Address, optional=True)Členské premenné triedy Person ukazujú tri možnosti:
name– povinná premenná; nemá predvolenú hodnotu, preto ju treba zadať pri vytváraní objektu, inak vznikne výnimkaTypeError,age– premenná s predvolenou hodnotou; typinturčí hodnota0,emailaaddress– voliteľné premenné; predvolená hodnotaNonetyp určiť nevie, preto ho uvedieme parametromtypea parametromoptionalpovolíme, aby premenná mohla mať aj hodnotuNone.
Ak do premennej address priradíme slovník, automaticky
sa prevedie na objekt triedy Address.
Funkcia Field()
Funkcia Field() umožňuje podrobnejšie opísať členskú
premennú. Má tieto parametre:
default– predvolená hodnota premennej,default_factory– funkcia alebo trieda, ktorá predvolenú hodnotu vytvorí pri vytváraní každého objektu zvlášť,type– typ premennej; použije sa vtedy, keď ho nie je možné určiť z predvolenej hodnoty (napr. priNone),optional– ak jeTrue, premenná môže mať okrem hodnoty daného typu aj hodnotuNone.
Ak nie je uvedený ani parameter default, ani
default_factory, premenná je povinná.
Parameter default_factory je potrebný pri hodnotách,
ktoré sa dajú meniť – napr. pri zoznamoch alebo vnorených dátových
triedach. Ak by sme použili default, všetky objekty by
zdieľali ten istý zoznam či objekt a zmena v jednom by sa prejavila vo
všetkých:
class WiFi(BaseModel):
ssid: str = Field(default=None, type=str, optional=True)
class Settings(BaseModel):
wifi: WiFi = Field(default_factory=WiFi, type=WiFi)
tags: list = Field(default_factory=list)Každý objekt triedy Settings tak dostane vlastný objekt
WiFi aj vlastný prázdny zoznam tags.
Vytvorenie inštancie
Inštanciu je možné vytvoriť priamo pomocou konštruktora:
p = Person(
name='Alice',
age=30,
address=Address(city='Paris', zip='12345')
)Rovnako ju je možné vytvoriť rozbalením slovníka. Vnorený slovník sa
pritom automaticky prevedie na objekt triedy Address:
>>> d = {
'address': {
'zip': '54321',
'city': 'Kosice'
},
'age': 23,
'name': 'Juraj'
}
>>> p2 = Person(**d)
>>> print(p2)
Person(age=23,email=None,address=Address(zip='54321',city='Kosice'),name='Juraj')Ak pri vytváraní objektu neuvedieme povinnú premennú, vznikne výnimka
TypeError:
>>> Person(age=23)
Traceback (most recent call last):
...
TypeError: Missing required field "name" for Person.Čítanie hodnôt
>>> print(f'{p.name} is {p.age} years old and lives in {p.address.city}.')
Alice is 30 years old and lives in Paris.Zápis hodnôt
Pri zápise hodnôt je dôležité, aby typ zapisovanej hodnoty zodpovedal
typu členskej premennej. V opačnom prípade dôjde k výnimke
ValueError:
# typ clenskej premennej p.age je int
>>> type(p.age)
<class 'int'>
# ak priradime hodnotu typu int, je vsetko v poriadku
>>> p.age = 32
# ak vsak priradime hodnotu ineho typu, dojde k vynimke
>>> p.age = 'twenty'
Traceback (most recent call last):
...
ValueError: Value "twenty" for attribute "age" is not of type "<class 'int'>".Do voliteľnej členskej premennej je okrem hodnoty jej typu možné
priradiť aj None:
>>> p.email = 'alice@example.com'
>>> p.email = None
>>> p.email = 23
Traceback (most recent call last):
...
ValueError: Value "23" for attribute "email" is not of type "<class 'str'>".Do objektu nie je možné pridať členskú premennú, ktorá nie je definovaná v triede:
>>> p.phone = '0905 123 456'
Traceback (most recent call last):
...
AttributeError: Attribute "phone" is not in class Person.Rovnako to platí aj pri vytváraní objektu – ak konštruktor alebo
vnorený slovník obsahuje kľúč, ktorý nezodpovedá žiadnej členskej
premennej, vznikne výnimka AttributeError:
>>> Person(name='Alice', phone='0905 123 456')
Traceback (most recent call last):
...
AttributeError: Attribute "phone" is not in class Person.Validácia hodnôt
V triede, ktorá je potomkom triedy BaseModel, je možné
vytvárať aj vlastné validátory členských premenných. Stačí vytvoriť
metódu s dekorátorom @validator(), ktorý dostane názov
validovanej premennej. Metóda dostane novú hodnotu a ak ju odmietne,
vyvolá výnimku:
from models.base import BaseModel, Field, validator
class Person(BaseModel):
name: str = Field(type=str)
age: int = 0
@validator('age')
def check_age(self, value):
if value < 0:
raise ValueError('Age is negative.')Validátor sa spustí pri každom priradení hodnoty, aj pri vytváraní objektu:
>>> p = Person(name='Alice', age=-1)
Traceback (most recent call last):
...
ValueError: Age is negative.Validátor sa spustí ešte pred uložením hodnoty. Ak hodnotu odmietne, hodnota členskej premennej sa nezmení:
>>> p = Person(name='Alice', age=30)
>>> p.age = -5
Traceback (most recent call last):
...
ValueError: Age is negative.
>>> p.age
30Serializácia a deserializácia
Objekt dátovej triedy je možné previesť na slovník pomocou metódy
.model_dump(). Prevedú sa aj vnorené dátové triedy, takže
výsledok je možné priamo uložiť napr. do súboru vo formáte JSON
(serializácia):
import json
with open('person.json', 'w') as file:
json.dump(p.model_dump(), file)Opačným postupom je vytvorenie objektu zo slovníka, napr. načítaného zo súboru vo formáte JSON (deserializácia). Slovník rozbalíme do konštruktora triedy a vnorené slovníky sa automaticky prevedú na objekty vnorených dátových tried:
import json
with open('person.json') as file:
p = Person(**json.load(file))Pri deserializácii sa uplatnia všetky pravidlá triedy – typová
kontrola, povinné premenné aj validátory. Ak slovník obsahuje kľúč,
ktorý v triede nie je, vznikne výnimka AttributeError.