To jest tekst przetłumaczony maszynowo, który może zawierać błędy!
W Pythonie (dotyczy to również większości innych języków) możliwe jest tworzenie własnych obiektów, z własnymi wartościami, regułami i funkcjami. Nazywa się to klasami (classes po angielsku). Używamy klas do zgrupowania powiązanych danych i funkcji w jedną całość, np. klasa Zamówienie posiadająca dane takie jak id_zamówienia, imię_klienta, produkty i funkcje takie jak dodaj_produkt(), oblicz_sumę(), itp.
Dictionary (JSON) vs Klasser (Objekter)
JSON (JavaScript Object Notation) to format służący do przechowywania i przesyłania danych niezależnie od języka programowania, podczas gdy klasa to struktura w konkretnym języku programowania.
Gdy chcemy wysłać dane przez sieć lub zapisać je w pliku, często używamy JSON (lub tabel w bazach danych).
Gdy chcemy pracować ze strukturalnymi danymi w kodzie, używamy klas.
W tej moduł będziemy badać, jak możemy używać klas do walidacji danych.
Najłatwiej jest użyć @dataclass z biblioteki dataclasses. Pozwala to uniknąć pisania dużej ilości kodu szablonowego do tworzenia klasy. (Takich jak wbudowane funkcje __init__ i __repr__ (reprezentacja)).
Przykład klasy bez użycia dekoratora 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) # Wyjście: 2020 Toyota Corolla
Przykład z dekoratorem dataclass, który osiąga to samo, co powyżej (ale z mniejszą ilością kodu):
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
year: int
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Wyjście: Car(make='Toyota', model='Corolla', year=2020)
Zadanie 1 - Stwórz klasę
Stwórz klasę o nazwie Person. Ta klasa powinna mieć następujące właściwości (atrybuty):
name: Imię osobyeye_color: Kolor oczu osobyphone_number: Numer telefonu osobyemail: Adres e-mail osoby
Utwórz instancję (użyj) obiektu klasy Person z poprawnymi wartościami dla wszystkich właściwości, jak w przykładzie poniżej.
@dataclass
class Person:
... # Twój kod tutaj
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number="12345678",
email="bob_kaare@example.com")
print(bob_kaare)
Rozwiązanie: Klasa danych dla Osoby
Oto możliwe rozwiązanie:
from dataclasses import dataclass
@dataclass
class Person:
name: str
eye_color: str
phone_number: str
email: str
Zadanie 2 - Walidacja w klasie
W przykładzie powyżej nie dodaliśmy żadnej walidacji. Oznacza to, że możemy utworzyć Person z nieprawidłowymi wartościami, takimi jak:
@dataclass
class Person:
... # Twój kod tutaj
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')
To jest (potencjalnie) problematyczne i może łatwo prowadzić do długu technologicznego w przyszłości. Na szczęście istnieją proste sposoby na dodanie walidacji do klas.
Zaczniemy od spojrzenia na walidację adresu e-mail. Istnieją wbudowane biblioteki w Pythonie, które mogą nam w tym pomóc, ale ponieważ robimy to po to, by się uczyć, stworzymy własną prostą walidację, tworząc nową klasę „Email” i badając funkcję __post_init__ (tylko dla dataclass).
Merk
Możemy również zrobić to w samej klasie Person, ale często lepiej jest tworzyć oddzielne klasy dla rzeczy, które można ponownie wykorzystać.
Przykład funkcji post_init
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Validating email: {self.address}")
# Din kode her
Dla prostego walidowania adresu e-mail, możemy na przykład sprawdzić, czy adres zawiera zarówno znak @, jak i .. Opcjonalnie możesz sprawdzić, czy adres pasuje do wzorca wyrażenia regularnego (regex). (Bardziej zaawansowane, ale poszukaj w Internecie!)
Rozwiązanie: Kod do prostej walidacji e-mail
Oto możliwe rozwiązanie, używamy wyjątków, aby “zepsuć” działanie, jeśli e-mail jest nieprawidłowy, co natychmiast zatrzyma program i wyświetli komunikat o błędzie.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Brakuje @ w adresie e-mail: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Brakuje . w domenie adresu e-mail: {self.address}")
if " " in self.address:
raise ValueError(f"Adres e-mail nie może zawierać spacji: {self.address}")
# Test kodu
test = Email("hei@example.com") # Poprawny
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # Nieprawidłowy, brakuje @
Zadanie 3 - Walidacja numerów telefonów
Stwórz klasę analogiczną do tej, którą stworzyłeś dla adresów e-mail, ale teraz dla numerów telefonów.
Wyzwanie z walidacją numeru telefonu!
Czy możesz naprawić walidację numerów telefonów, aby akceptowała zarówno litery (str), jak i cyfry (int)? Na przykład 12345678 i "12345678" powinny być oba poprawne.
Spróbuj również dodać kody krajowe jako atrybut (podwartość klasy). Na przykład 47 dla Norwegii, 46 dla Szwecji
Zadanie 4 - Użyj walidacji w klasie Osoba
Teraz, gdy stworzyliśmy walidację dla adresu e-mail i numeru telefonu, możemy użyć ich w klasie Osoba.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # Użyj klasy PhoneNumber
email: Email # Użyj klasy Email
Pojawia się nowe wyzwanie!
Teraz, gdy zmieniliśmy klasę Person tak, aby używała klas PhoneNumber i Email, musimy również zmienić sposób tworzenia instancji (tworzenia) Person. Musimy teraz najpierw utworzyć obiekt PhoneNumber i Email, zanim będziemy mogli utworzyć Person.
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number=PhoneNumber("12345678"), # Zauważ zmianę tutaj
email=Email("bob_kaare@example.com")) # Zauważ zmianę tutaj
print(bob_kaare)
# Uwaga: konieczna jest zmiana w sposobie pobierania wartości
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
Zadanie 5 - Właściwości w klasach (Opcjonalne)
Kiedy używamy obiektów do reprezentowania wartości takich jak adres e-mail i numer telefonu, musimy określić podwartość (np. address dla adresu e-mail i number dla numeru telefonu) za każdym razem, gdy chcemy pobrać wartość. Może to być nieco uciążliwe w dłuższej perspektywie. Na szczęście istnieje rozwiązanie, polegające na użyciu dekoratora @property w klasie, który pozwala nam pobierać wartość bezpośrednio z obiektu, bez konieczności określania podwartości.
To jednak stwarza kolejną przeszkodę, a mianowicie potrzebę funkcji __init__ w klasie Person. Dzieje się tak, ponieważ nie możemy użyć tej samej nazwy zarówno dla właściwości, jak i atrybutu w dataclass.
from dataclasses import dataclass
@dataclass
class EksempelVerdi:
attributt: str
@dataclass
class Person:
name: str
_verdi: EksempelVerdi # Zmienna wewnętrzna (zaczyna się od _ aby wskazać, że jest "prywatna")
def __init__(self, name: str, verdi: EksempelVerdi):
self.name = name
self._verdi = verdi
@property
def verdi(self):
return self._verdi.attributt # Pobiera podwartość bezpośrednio
# Testuj kod
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # Output: Noe tekst
Merk
Właściwości są unikalne, ponieważ nie wymagają parametrów i nie potrzebują nawiasów, aby się uruchomić. W przykładzie pobieramy person.verdi bez nawiasów (nie person.verdi()), mimo że technicznie rzecz biorąc jest to funkcja.
Alternatywny przykład, który akceptuje zarówno str, jak i 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 musi być typu str lub EksempelVerdi")
@property
def verdi(self) -> str:
return self._verdi.attributt # Pobiera podwartość bezpośrednio
# Test kodu
person = Person(name="Alice", verdi="Noe tekst")
print(person.verdi) # Output: Noe tekst
Zadanie 6 - Właściwości z logiką (Opcjonalne)
Zaktualizuj Person dodając nowy atrybut o nazwie birthday (data urodzenia). Powinien on być typu datetime.date (z biblioteki datetime).
Następnie utwórz następujące właściwości:
- Utwórz właściwość
age(wiek), która oblicza wiek osoby na podstawiebirthdayi aktualnej daty. - Utwórz właściwość
is_adult(czy_dorosły), która zwracaTruejeśli osoba ma 18 lat lub więcej, w przeciwnym razieFalse.
Rozwiązanie: Wiek i dorosłość jako właściwości
Oto możliwe rozwiązanie:
from dataclasses import dataclass
from datetime import date
@dataclass
class Person:
name: str
birthday: date
@property
def age(self) -> int:
"""Oblicza wiek na podstawie daty urodzenia i dzisiejszej daty"""
today = date.today()
age = today.year - self.birthday.year
# Zmniejsz o 1, jeśli osoba jeszcze nie obchodziła urodzin w tym roku
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def is_adult(self) -> bool:
"""Zwraca True, jeśli osoba ma 18 lat lub więcej"""
return self.age >= 18
# Przetestuj kod
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # Np. 18, jeśli dzisiejsza data to po 15 maja 2023
print(person.is_adult) # True
Kolejne wyzwanie!
Czy potrafisz sprawić, aby instancjonowanie Person akceptowało zarówno datetime.date, jak i tekst w formacie "DD-MM-YYYY" dla birthday? (Wskazówka: użyj datetime.strptime aby przekonwertować ciąg na datetime.date)
