Ĉi tio estas maŝine tradukita teksto kiu povas enhavi erarojn!
En Python (validas ankaŭ por la plej multaj aliaj lingvoj) estas eble krei propraj objektojn, kun propraj valoroj, reguloj kaj funkcioj. Tio nomiĝas klasoj (classes en la angla). Ni uzas klasojn por kolekti rilatajn datumojn kaj funkciojn en unuĵo, ekzemple, klason Ordre kiu havas datumojn kiel ordre_id, kunde_navn, produkter kaj funkciojn kiel legg_til_produkt(), beregn_total(), ktp.
Vortaro (JSON) kontra Klasoj (Objektoj)
JSON (JavaScript Object Notation) estas formato por konservi kaj transdoni datumojn sendepende de programlinglando, dum klaso estas strukturo en specifa programlinglando.
Kiam ni devas sendi datumojn tra la reto, aŭ konservi ilin en dosiero, ni ofte uzas JSON (aŭ tabelojn en datumbazoj).
Kiam ni laboras kun strukturitaj datumoj en kodo, ni uzas klasojn.
En ĉi tiu modulo ni vidos kiel ni povas uzi klasojn por validegi datumojn.
La plej facila maniero estas uzi @dataclass el la biblioteko dataclasses. Tio permesas al ni eviti skribi multan bultan kodon (boilerplate) por krei klason. (Kiel ekzemple la integritaj __init__ kaj __repr__ (representacio) funkcioj).
Ekzemplo de klaso sen uzi la dataclass dekoratoron:
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)
Solvo: Datklaso por Persono
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 supre ni ne aldonis ajnan validigon. Tio signifas, ke ni povas krei Person 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!)
Solvo: Kodo por simpla retpoŝt-validigo
Jen ebla solvo, ni uzas esceptojn por “kraŝi” se la retpoŝto estas nevalidiga, tio ĉesigos la programon tuj kaj donos erarmesaĝon.
```python
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Manĝas @ en la retpoŝtadreso: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Manĝas . en la domajno de la retpoŝtadreso: {self.address}")
La retpoŝtadreso ne povas enhavi spacojn: {self.address}”)
if " " in self.address: raise ValueError(f"La retpoŝtadreso ne povas enhavi spacojn: {self.address}")Testu la kodon
test = Email(“hei@example.com”) # Validiga
try:
test = Email(“heiexample.com”)
except ValueError as e:
print(e) # Nevalidiga, mankas @
```
Tasko 3 - Telefona nombra validigo
Krea ekvivalentan klason kiel vi faris por retpoŝtoj, sed nun por telefonaj numeroj
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, kiam ni ŝanĝis la Person klason por uzi PhoneNumber kaj Email klasojn, ni devas ankaŭ ŝanĝi kiel ni instancigas (kreas) Person. Ni nun devas unue krei PhoneNumber kaj Email objektojn, 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 # Intern 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 # Elprenas la subvaloron rekte
# Testu la kodon
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # Eligo: Noe tekst
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.attributt # Elprenas la subvaloron rekte
# Testu la kodon
person = Person(name="Alice", verdi="Iom da teksto")
print(person.verdi) # Output: Iom da 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.
Solvo: Aĝo kaj plenkuleco kiel propriectoj
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 bazante sur naskiĝdato kaj hodiaŭa dato"""
today = date.today()
age = today.year - self.birthday.year
# Regardigu malsupren kun 1 se la persono ankoraŭ ne havas 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:
"""Rekonligas True se la persono estas 18 jarojn aŭ pli 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 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)
