Tämä on konekäännetty teksti, joka saattaa sisältää virheitä!
Pythonissa (koskee myös useimpia muita kieliä) on mahdollista luoda omia objekteja, omilla arvoilla, säännöillä ja funktioilla. Tätä kutsutaan luokiksi (classes englanniksi). Käytämme luokkia kerätäksemme liittyvää dataa ja funktioita yhteen yksikköön, esim. Ordre-luokka, jolla on dataa kuten ordre_id, kunde_navn, produkter ja funktioita kuten legg_til_produkt(), beregn_total(), jne.
Sanakirja (JSON) vs Luokat (Objektit)
JSON (JavaScript Object Notation) on formaatti tietojen tallentamiseen ja siirtämiseen ohjelmointikielestä riippumatta, kun taas luokka on rakenne tietyssä ohjelmointikielessä.
Kun lähetämme tietoa verkon yli tai tallennamme sitä tiedostoon, käytämme usein JSONia (tai taulukoita tietokannoissa).
Kun työskentelemme strukturoidun tiedon parissa koodissa, käytämme luokkia.
Tässä moduulissa tarkastelemme, miten luokkia voidaan käyttää datan validoimiseen.
Helpoin tapa on käyttää @dataclass-dekoraattoria dataclasses-kirjastosta. Tämä säästää meiltä paljon boilerplate-koodin kirjoittamista luokan luomiseksi. (Kuten esimerkiksi sisäänrakennetut __init__- ja __repr__- (esitys) -funktiot).
Esimerkki luokasta ilman dataclass-dekoraattorin käyttöä:
class Car:
def __init__(self, make: str, model: str, year: int):
self.make = make
self.model = model
self.year = year
def __repr__(self):
return f"{self.year} {self.make} {self.model}"
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Tulostaa: 2020 Toyota Corolla
Esimerkki dataclass-dekoraattorista, joka saavuttaa saman kuin yllä (mutta vähemmällä koodilla):
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
year: int
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Tulostaa: Car(make='Toyota', model='Corolla', year=2020)
Tehtävä 1 - Luo luokka
Luo luokka nimeltä Person. Tämän luokan tulee sisältää seuraavat ominaisuudet (attributes):
name: Henkilön nimieye_color: Henkilön silmien väriphone_number: Henkilön puhelinnumeroemail: Henkilön sähköpostiosoite
Luo (instansioi) luokan Person olio, jossa on kaikille ominaisuuksille pätevät arvot, kuten alla olevassa esimerkissä.
@dataclass
class Person:
... # Koodisi täällä
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number="12345678",
email="bob_kaare@example.com")
print(bob_kaare)
Ratkaisu: Dataluokka henkilölle
Tässä on mahdollinen ratkaisu:
from dataclasses import dataclass
@dataclass
class Person:
name: str
eye_color: str
phone_number: str
email: str
Tehtävä 2 – Validointi luokassa
Yllä olevassa esimerkissä emme ole lisänneet mitään validointia. Se tarkoittaa, että voimme luoda Person-olion virheellisillä arvoilla, kuten:
@dataclass
class Person:
... # Koodisi tässä
invalid_person = Person(name="",
eye_color="yes",
phone_number="12345",
email="not-an-email")
print(invalid_person)
# Tuloste: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')
Tämä on (mahdollisesti) ongelmallista ja voi helposti aiheuttaa teknistä velkaa tulevaisuudessa. Onneksi on olemassa yksinkertaisia tapoja lisätä validointia luokkiin.
Aloitamme tarkastelemalla sähköpostin validointia. Pythonissa on sisäänrakennettuja kirjastoja, jotka voivat auttaa meitä tässä, mutta koska teemme tätä oppiaksemme, luomme oman yksinkertaisen validoinnin luomalla uuden luokan nimeltä “Email” ja tutkimalla __post_init__-funktiota (vain dataclassille).
Merk
Voimme myös tehdä tämän itse Person -luokassa, mutta on usein parempi luoda omia luokkia asioille, joita voidaan käyttää uudelleen.
Esimerkki post_init -funktiosta
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Validating email: {self.address}")
# Koodisi tähän
Yksinkertaiseen sähköpostin validointiin voimme esimerkiksi tarkistaa, että sähköposti sisältää sekä @- että . -merkin. Vaihtoehtoisesti voit tarkistaa, täsmääkö sähköposti regex-kuvioon. (Edistyneempi tapa, mutta voit tutkia sitä verkosta!)
Løsning: Kode for enkel e-post validering
Tässä on mahdollinen ratkaisu, käytämme poikkeuksia “kaataaksemme” ohjelman, jos sähköpostiosoite on virheellinen, tämä pysäyttää ohjelman heti ja antaa virheilmoituksen.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Sähköpostiosoitteesta puuttuu @: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Sähköpostiosoitteen verkkotunnuksesta puuttuu .: {self.address}")
if " " in self.address:
raise ValueError(f"Sähköpostiosoite ei voi sisältää välilyöntejä: {self.address}")
# Testaa koodi
test = Email("hei@example.com") # Voimassa oleva
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # Virheellinen, puuttuu @
Tehtävä 3 – Puhelinnumeron validointi
Luo vastaava luokka kuin sähköpostiosoitteille, mutta nyt puhelinnumeroille.
Haaste puhelinnumeron validoinnissa!
Voitko korjata puhelinnumeron validoinnin siten, että se hyväksyy sekä kirjaimet (str) että numerot (int)? Esim. 12345678 ja "12345678" molempien tulisi olla kelvollisia.
Yritä myös lisätä maatunnukset attribuutiksi (luokan aliarvoksi). Esim. 47 Norjalle, 46 Ruotsille
Tehtävä 4 – Käytä validointia Person-luokassa
Nyt kun olemme luoneet validoinnin sähköpostille ja puhelinnumerolle, voimme käyttää niitä Person-luokassa.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # Käytä PhoneNumber-luokkaa
email: Email # Käytä Email-luokkaa
Uusi haaste ilmestyy!
Nyt kun olemme muuttaneet Person-luokan käyttämään PhoneNumber- ja Email-luokkia, joten meidän on myös muutettava tapaa, jolla instansioimme (luomme) Person-olion. Meidän on nyt ensin luotava PhoneNumber- ja Email-olio ennen kuin voimme luoda Person-olion.
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number=PhoneNumber("12345678"), # Huomaa muutos tässä
email=Email("bob_kaare@example.com")) # Huomaa muutos tässä
print(bob_kaare)
# Huom: muutos on tehtävä myös siihen, miten arvoja haetaan
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
Tehtävä 5 - Ominaisuudet luokissa (Valinnainen)
Kun käytämme objekteja arvojen, kuten sähköpostin ja puhelinnumeron, esittämiseen, meidän on määritettävä aliarvo (esim. address sähköpostille ja number puhelinnumerolle) joka kerta, kun haluamme hakea arvon. Tämä voi tulla hieman hankalaksi pitkällä aikavälillä. Onneksi tähän on ratkaisu käyttämällä @property -koristetta luokassa, jolloin voimme hakea arvon suoraan objektista ilman, että meidän tarvitsee määritellä aliarvoa.
Tämä tuo kuitenkin vielä yhden haasteen, ja se on, että tarvitsemme __init__ -funktion Person -luokassa. Tämä johtuu siitä, että emme voi käyttää samaa nimeä sekä ominaisuudelle että attribuutille dataluokassa.
from dataclasses import dataclass
@dataclass
class EksempelVerdi:
attributt: str
@dataclass
class Person:
name: str
_verdi: EksempelVerdi # Sisäinen muuttuja (alkaa _:lla osoittaakseen, että se on "yksityinen")
def __init__(self, name: str, verdi: EksempelVerdi):
self.name = name
self._verdi = verdi
@property
def verdi(self):
return self._verdi.attributt # Hakee aliarvon suoraan
# Testaa koodi
person = Person(name="Alice", verdi=EksempelVerdi("Jotain tekstiä"))
print(person.verdi) # Output: Jotain tekstiä
Merk
Ominaisuudet ovat ainutlaatuisia siinä mielessä, että ne eivät tarvitse parametreja, eivätkä sulkeita suorittaakseen. Esimerkissä haetaan person.verdi ilman sulkeita (ei person.verdi()), vaikka se teknisesti ottaen on funktio.
Vaihtoehtoinen esimerkki, joka hyväksyy sekä str että EksempelVerdi
@dataclass
class Person:
name: str
_verdi: str
def __init__(self, name: str, verdi: str | EksempelVerdi):
self.name = name
if isinstance(verdi, EksempelVerdi):
self._verdi = verdi
elif isinstance(verdi, str):
self._verdi = EksempelVerdi(verdi)
else:
raise TypeError("verdin on oltava tyyppiä str tai EksempelVerdi")
@property
def verdi(self) -> str:
return self._verdi.attributt # Hakee aliarvon suoraan
# Testaa koodi
person = Person(name="Alice", verdi="Jokin teksti")
print(person.verdi) # Output: Jokin teksti
Tehtävä 6 - Ominaisuudet logiikalla (Valinnainen)
Päivitä Person lisäämällä uusi attribuutti nimeltä birthday (syntymäpäivä). Sen tyypin tulee olla datetime.date (datetime-kirjastosta).
Luo sen jälkeen seuraavat ominaisuudet:
- Luo ominaisuus
age, joka laskee henkilön iänbirthday- ja tämän päivän päivämäärän perusteella. - Luo ominaisuus
is_adult, joka palauttaaTrue, jos henkilö on 18 vuotta tai vanhempi, muutenFalse.
Ratkaisu: Ikä ja aikuinen ominaisuuksina (properties)
Tässä on mahdollinen ratkaisu:
from dataclasses import dataclass
from datetime import date
@dataclass
class Person:
name: str
birthday: date
@property
def age(self) -> int:
"""Laskee iän syntymäpäivän ja tämän päivän perusteella"""
today = date.today()
age = today.year - self.birthday.year
# Vähennä yhdellä, jos henkilöllä ei ole vielä ollut syntymäpäivää tänä vuonna
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def is_adult(self) -> bool:
"""Palauttaa True, jos henkilö on 18 vuotta tai vanhempi"""
return self.age >= 18
# Testaa koodi
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # Esim. 18, jos tämän päivän päivämäärä on jälkeen 15. toukokuuta 2023
print(person.is_adult) # True
Vielä yksi haaste!
Pystytkö saamaan Person-instansoinnin hyväksymään sekä datetime.date-tyypin että tekstin muodossa "DD-MM-YYYY" olevan syntymäpäivän? (Vihje: käytä datetime.strptime-funktiota muuntaaksesi merkkijonon datetime.date-tyypiksi)
