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.
Dictionary (JSON) vs Klasser (Objekter)
JSON (JavaScript Object Notation) on muoto datan tallentamiseen ja siirtämiseen riippumatta ohjelmointikielestä, kun taas luokka on rakenne tietyssä ohjelmointikielessä.
Kun haluamme lähettää dataa verkossa tai tallentaa sen tiedostoon, käytämme usein JSON:ia (tai tauluja tietokannoissa).
Kun haluamme työskennellä jäsennellyn datan kanssa 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)
Løsning: En dataclass for Person
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äällä
invalid_person = Person(name="",
eye_color="yes",
phone_number="12345",
email="not-an-email")
print(invalid_person)
# Output: 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
Yksinkertaisen sähköpostin validoinnin osalta voimme esimerkiksi tarkistaa, että sähköpostiosoite sisältää sekä @- että .-merkit. Halutessasi voit myös tarkistaa, että sähköpostiosoite vastaa regex-mallia. (Edistyneempää, mutta etsi tietoa 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 ilmenee!
Nyt kun olemme muuttaneet Person -luokan käyttämään PhoneNumber ja Email -luokkia, meidän on myös muutettava tapa, jolla Person -olio luodaan (tehdään). Meidän on nyt ensin luotava PhoneNumber ja Email -oliot, 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-tyypin
@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("verdi ska vara av typ str eller EksempelVerdi")
@property
def verdi(self) -> str:
return self._verdi.attributt # Henter ut underverdien direkte
# Test koden
person = Person(name="Alice", verdi="Noe tekst")
print(person.verdi) # Output: Noe tekst
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.
Løsning: Alder og voksen som 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 päivämäärän perusteella"""
today = date.today()
age = today.year - self.birthday.year
# Vähennä 1, 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-vuotias 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 15. toukokuuta 2023 jälkeen
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)
