Ĉi tio estas maŝine tradukita teksto kiu povas enhavi erarojn!
En Python (kaj ankaŭ en la plejparto de aliaj lingvoj) estas eble krei proprajn objektojn, kun propraj valoroj, reguloj kaj funkcioj. Ĉi tio estas nomata klasoj (classes en la angla). Ni uzas klasojn por kunigi rilatajn datumojn kaj funkciojn en unu tutaĵo, ekz. klaso Ordre kiu havas datumojn kiel ordre_id, kunde_navn, produkter kaj funkciojn kiel legg_til_produkt(), beregn_total(), ktp.
Dictionary (JSON) vs Klasser (Objekter)
JSON (JavaScript Object Notation) estas formato por konservi kaj transdoni datumojn sendepende de programlingvoj, dum klaso estas strukturo en specifa programlingvo.
Kiam ni volas sendi datumojn trans la reton, aŭ konservi ilin en dosiero, ni ofte uzas JSON (aŭ tabelojn en datumbazoj).
Kiam ni volas labori kun strukturitaj datumoj en kodo, ni uzas klasojn.
En ĉi tiu modulo ni rigardos kiel ni povas uzi klasojn por validigi datumojn.
La plej facila estas uzi @dataclass el la biblioteko dataclasses. Tio permesas al ni eviti skribi multan ripeteman kodon por krei klasojn. (Kiel ekzemple la enkonstruitaj funkcioj __init__ kaj __repr__ (reprezentado)).
Ekzemplo de klaso sen uzi la dekoratoron dataclass:
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) # Eligo: 2020 Toyota Corolla
Ekzemplo kun datklasa dekoratorio, kiu atingas la saman kiel supre (sed kun malpli da kodo):
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
year: int
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Eligo: Car(make='Toyota', model='Corolla', year=2020)
Ekzerco 1 - Kreu Klaso
Kreu klaso nomata Person. Ĉi tiu klaso devas havi la jenajn atributojn:
name: La nomo de la personoeye_color: La okulkoloro de la personophone_number: La telefonnumero de la personoemail: La retpoŝtadreso de la persono
Instanciu (uzu) objekton de la klaso Person kun validaj valoroj por ĉiuj atributoj kiel en la ekzemplo sube.
@dataclass
class Person:
... # Via kodo ĉi tie
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
Jen ebla solvo:
from dataclasses import dataclass
@dataclass
class Person:
name: str
eye_color: str
phone_number: str
email: str
Tasko 2 - Validigo en la klaso
En la ekzemplo super ni ne aldonis validigon. Tio signifas, ke ni povas krei Person-on kun nevalidaj valoroj, kiel ekzemple:
@dataclass
class Person:
... # Via kodo ĉi tie
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')
Tio estas (eble) problema, kaj povas facile kaŭzi teknikan ŝuldon en la estonteco. Feliĉe ekzistas simplaj manieroj aldoni validigon al klasoj.
Ni komencu per rigardi retpoŝtan validigon. Ekzistas enkonstruitaj bibliotekoj en Python kiuj povas helpi nin kun tio, sed ĉar ni faras tion por lerni, ni kreos nian propran simplan validigon, kreante novan klaso por “Retpoŝto”, kaj esplorante funkcion __post_init__ (nur por datklaso).
Merk
Ni povas ankaŭ fari tion en la mem Person klaso, sed ĝi estas ofte pli bona krei proprajn klasojn por aĵoj kiuj povas esti reuzitaj.
Ekzemplo de funkcio post_init
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Validigante retpoŝton: {self.address}")
# Via kodo ĉi tie
Por simpla retpoŝta validigo, ni povas ekzemple kontroli ĉu la retpoŝto enhavas kaj @ kaj . signojn. Eble vi povas kontroli ĉu la retpoŝto kongruas kun regex ŝablono. (Pli progresinta, sed serĉu ĝin sur la reto!)
Løsning: Kode for enkel e-post validering
Jen ebla solvo, ni uzas esceptojn por “kraxigi” se la e-poŝto estas invalida, tio haltigos la programon tuj, kaj donos erarmesaĝon.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Mankas @ en la e-poŝtadreso: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Mankas . en la domajno de la e-poŝtadreso: {self.address}")
if " " in self.address:
raise ValueError(f"La e-poŝtadreso ne povas enhavi spacojn: {self.address}")
# Testu la kodon
test = Email("hei@example.com") # Valida
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # Invalida, mankas @
Tasko 3 - Telefonnumero Validigo
Kreu samforman klaso kiel vi faris por retpoŝtoj, sed nun por telefonnumeroj.
Defio kun telefonvalidigo!
Ĉu vi povas ripari la validigon por telefonnumeroj por akcepti kaj literojn (str) kaj nombrojn (int)? Ekz. 12345678 kaj "12345678" devus esti ambaŭ validaj.
Provu ankaŭ aldoni landokodojn kiel atributon (subvaloron de la klaso). Ekz. 47 por Norvegio, 46 por Svedio
Tasko 4 - Uzu validigon en la Klaso Persono
Nun kiam ni kreis validigon por retpoŝto kaj telefonnumero, ni povas uzi ilin en la klaso Persono.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # Uzu la klaso PhoneNumber
email: Email # Uzu la klaso Email
Nova defio aperas!
Nun, ĉar ni ŝanĝis la klaso Person por uzi la klasojn PhoneNumber kaj Email, ni ankaŭ devas ŝanĝi la manieron kiel ni instancigas (kreas) Person. Ni nun unue devas krei objekton PhoneNumber kaj Email, antaŭ ol ni povas krei Person.
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number=PhoneNumber("12345678"), # Notu la ŝanĝon ĉi tie
email=Email("bob_kaare@example.com")) # Notu la ŝanĝon ĉi tie
print(bob_kaare)
# Notu: ŝanĝo devas esti farita en la maniero kiel ni elprenas la valorojn ankaŭ
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
Tasko 5 - Propraĵoj en klasoj (Elektebla)
Kiam ni uzas objektojn por reprezenti valorojn kiel retpoŝto kaj telefonnumero, ni devas specifigi la subvaloron (ekz. address por retpoŝto kaj number por telefonnumero) ĉiufoje kiam ni volas elpreni la valoron. Tio povas iĝi iomete malfacila en la longa termino. Feliĉe ekzistas solvo por tio, uzante la dekoratoron @property en klaso, kiu permesas al ni elpreni la valoron rekte de la objekto, sen devi specifigi la subvaloron.
Tio tamen prezentas ankoraŭ unu defion, kaj tio estas, ke ni bezonas la funkcion __init__ en la klaso Person. Tio estas ĉar ni ne povas uzi la saman nomon por kaj propraĵo kaj atributo en dataklase.
from dataclasses import dataclass
@dataclass
class EksempelVerdi:
attributt: str
@dataclass
class Person:
name: str
_verdi: EksempelVerdi # Interna variablo (komencas per _ por indiki ke ĝi estas "privata")
def __init__(self, name: str, verdi: EksempelVerdi):
self.name = name
self._verdi = verdi
@property
def verdi(self):
return self._verdi.attributt # Akiras la subvaloron rekte
# Testu la kodon
person = Person(name="Alice", verdi=EksempelVerdi("Iu teksto"))
print(person.verdi) # Output: Iu teksto
Merk
Ecoj estas unikaj ĉar ili ne bezonas parametrojn, kaj ili ne bezonas kramojn por ekzekutiĝi. En la ekzemplo, ni elprenas person.valoro sen kramoj (ne person.valoro()), kvankam teknike ĝi estas funkcio.
Alternativa ekzemplo kiu akceptas kaj str kaj 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("verdi devas esti de tipo str aŭ EksempelVerdi")
@property
def verdi(self) -> str:
return self._verdi.attributo # Hentas la subvaloron rekte
# Testu la kodon
person = Person(name="Alice", verdi="Iu teksto")
print(person.verdi) # Eligo: Iu teksto
Tasko 6 - Propraĵoj kun logiko (Elekta)
Ĝisdatigu Person per aldono de nova atributo nomata birthday (naskiĝtago). Ĝi devas esti de tipo datetime.date (de la biblioteko datetime).
Poste, kreu la jenajn propraĵojn:
- Kreu propraĵon
agekiu kalkulas la aĝon de la persono bazite surbirthdaykaj la nuntatan daton. - Kreu propraĵon
is_adultkiu returnasTruese la persono estas 18-jara aŭ pli aĝa, alieFalse.
Løsning: Alder og voksen som properties
Jen ebla solvo:
from dataclasses import dataclass
from datetime import date
@dataclass
class Person:
name: str
birthday: date
@property
def age(self) -> int:
"""Kalkulas la aĝon bazite sur la naskiĝdato kaj la hodiaŭa dato"""
today = date.today()
age = today.year - self.birthday.year
# Ĝustigu malkielon kun 1 se la persono ankoraŭ ne havis naskiĝtagon ĉi-jare
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def is_adult(self) -> bool:
"""Returnas True se la persono estas 18-jara aŭ pli aĝa"""
return self.age >= 18
# Testu la kodon
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # Ekz. 18 se la hodiaŭa dato estas post la 15-a de majo 2023
print(person.is_adult) # True
Ankoraŭ unu defio!
Ĉu vi povas igi la instanciigon de Person akcepti kaj datetime.date kaj tekston en la formato "DD-MM-YYYY" por birthday? (Konsilo: uzu datetime.strptime por konverti la ŝnuron al datetime.date)
