Це машинний переклад, який може містити помилки!
У Python (це стосується також більшості інших мов) є можливість створювати власні об’єкти, з власними значеннями, правилами та функціями. Це називається класами (classes англійською). Ми використовуємо класи для об’єднання пов’язаних даних та функцій в одну одиницю, наприклад, клас Замовлення (Ordre), який має дані, такі як замовлення_id (ordre_id), ім'я_клієнта (kunde_navn), продукти (produkter) та функції, такі як додати_продукт() (legg_til_produkt()), обчислити_загальну_суму() (beregn_total()), тощо.
Dictionary (JSON) vs Klasser (Objekter)
JSON (JavaScript Object Notation) – це формат для зберігання та передачі даних незалежно від мови програмування, тоді як клас є структурою в конкретній мові програмування.
Коли нам потрібно відправити дані по мережі або зберегти їх у файлі, ми часто використовуємо JSON (або таблиці в базах даних).
Коли нам потрібно працювати зі структурованими даними в коді, ми використовуємо класи.
У цьому модулі ми розглянемо, як можна використовувати класи для валідації даних.
Найпростіше – використовувати @dataclass з бібліотеки dataclasses. Це дозволяє уникнути написання великої кількості шаблонного коду для створення класу (наприклад, вбудованих функцій __init__ та __repr__ (представлення)).
Приклад класу без використання декоратора 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) # Вивід: 2020 Toyota Corolla
Приклад з декоратором dataclass, який досягає того ж самого, що й вище (але з меншою кількістю коду):
from dataclasses import dataclass
@dataclass
class Car:
make: str
model: str
year: int
my_car = Car("Toyota", "Corolla", 2020)
print(my_car) # Вивід: Car(make='Toyota', model='Corolla', year=2020)
Завдання 1 - Створіть клас
Створіть клас з назвою Person. Цей клас повинен мати наступні властивості (атрибути):
name: Ім’я особиeye_color: Колір очей особиphone_number: Номер телефону особиemail: Адреса електронної пошти особи
Створіть екземпляр (використайте) об’єкта класу Person з дійсними значеннями для всіх властивостей, як у прикладі нижче.
@dataclass
class Person:
... # Тут ваш код
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
Ось можливе рішення:
from dataclasses import dataclass
@dataclass
class Person:
name: str
eye_color: str
phone_number: str
email: str
Завдання 2 - Валідація в класі
У прикладі вище ми не додали жодної валідації. Це означає, що ми можемо створити Person з недійсними значеннями, такими як:
@dataclass
class Person:
... # Тут ваш код
invalid_person = Person(name="",
eye_color="yes",
phone_number="12345",
email="not-an-email")
print(invalid_person)
# Вивід: Person(name='', eye_color='yes', phone_number='12345', email='not-an-email')
Це (потенційно) проблематично і може легко призвести до технічного боргу в майбутньому. На щастя, існують прості способи додати валідацію в класи.
Ми почнемо з розгляду валідації електронної пошти. Існують вбудовані бібліотеки в Python, які можуть допомогти нам у цьому, але оскільки ми робимо це для навчання, ми створимо власну просту валідацію, створивши новий клас для “Email” та дослідивши функцію __post_init__ (лише для dataclass).
Merk
Ми можемо також зробити це в самому Person класі, але часто краще створювати окремі класи для речей, які можна повторно використовувати.
Приклад функції post_init
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Validating email: {self.address}")
# Ваш код тут
Для простої валідації електронної пошти, наприклад, ми можемо перевірити, чи містить електронна пошта як @, так і . символи. Можливо, ви також можете перевірити, чи відповідає електронна пошта шаблону регулярного виразу. (Більш просунуто, але пошукайте в інтернеті!)
Løsning: Kode for enkel e-post validering
Ось можливе рішення, ми використовуємо винятки, щоб “зруйнувати” програму, якщо електронна пошта недійсна, це зупинить програму відразу і дасть повідомлення про помилку.
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
if "@" not in self.address:
raise ValueError(f"Відсутній @ в адресі електронної пошти: {self.address}")
if "." not in self.address.split("@")[1]:
raise ValueError(f"Відсутній . в домені адреси електронної пошти: {self.address}")
if " " in self.address:
raise ValueError(f"Адреса електронної пошти не може містити пробіли: {self.address}")
# Перевірте код
test = Email("hei@example.com") # Дійсна
try:
test = Email("heiexample.com")
except ValueError as e:
print(e) # Недійсна, відсутній @
Завдання 3 - Валідація телефонних номерів
Створіть аналогічний клас, як ви робили для електронних адрес, але тепер для телефонних номерів.
Виклик з валідацією телефону!
Чи можете ви виправити валідацію для номерів телефонів, щоб вона приймала як літери (str), так і цифри (int)? Наприклад, 12345678 та "12345678" повинні бути обома дійсними.
Також спробуйте додати коди країн як атрибут (підклас). Наприклад, 47 для Норвегії, 46 для Швеції
Завдання 4 - Використання валідації в класі Person
Тепер, коли ми створили валідацію для електронної пошти та номера телефону, ми можемо використати їх у класі Person.
@dataclass
class Person:
name: str
eye_color: str
phone_number: PhoneNumber # Використовуйте клас PhoneNumber
email: Email # Використовуйте клас Email
Новий виклик виникає!
Тепер, коли ми змінили клас Person для використання класів PhoneNumber та Email, нам також потрібно змінити спосіб створення екземпляра (створення) Person. Тепер нам спочатку потрібно створити об’єкт PhoneNumber та Email, перш ніж ми зможемо створити Person.
bob_kaare = Person(name="Bob Kåre",
eye_color="blue",
phone_number=PhoneNumber("12345678"), # Зверніть увагу на зміну тут
email=Email("bob_kaare@example.com")) # Зверніть увагу на зміну тут
print(bob_kaare)
# Примітка: зміна повинна бути внесена в спосіб отримання значень також
print(bob_kaare.email.address)
print(bob_kaare.phone_number.number) # .country_code(?)
Завдання 5 - Властивості в класах (Необов’язково)
Коли ми використовуємо об’єкти для представлення значень, таких як електронна пошта та номери телефонів, нам потрібно вказувати підзначення (наприклад, address для електронної пошти та number для номера телефону) кожного разу, коли ми хочемо отримати значення. Це може стати трохи громіздким у довгостроковій перспективі. На щастя, є рішення цієї проблеми, яке полягає у використанні декоратора @property в класі, що дозволяє нам отримувати значення безпосередньо з об’єкта, не вказуючи підзначення.
Це, однак, створює ще одну проблему, а саме те, що нам потрібна функція __init__ в класі Person. Це тому, що ми не можемо використовувати одне й те саме ім’я як для властивості, так і для атрибута в dataclass.
from dataclasses import dataclass
@dataclass
class EksempelVerdi:
attributt: str
@dataclass
class Person:
name: str
_verdi: EksempelVerdi # Внутрішня змінна (починається з _ для позначення, що вона "приватна")
def __init__(self, name: str, verdi: EksempelVerdi):
self.name = name
self._verdi = verdi
@property
def verdi(self):
return self._verdi.attributt # Отримує підзначення безпосередньо
# Перевірте код
person = Person(name="Alice", verdi=EksempelVerdi("Noe tekst"))
print(person.verdi) # Вивід: Noe tekst
Merk
Властивості унікальні тим, що їм не потрібні параметри, і їм не потрібні дужки для виконання. У прикладі ми отримуємо person.verdi без дужок (не person.verdi()), навіть якщо технічно це функція.
Альтернативний приклад, який приймає як str, так і 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 має бути типу str або EksempelVerdi")
@property
def verdi(self) -> str:
return self._verdi.attributt # Отримує підпорядковане значення безпосередньо
# Перевірте код
person = Person(name="Alice", verdi="Noe tekst")
print(person.verdi) # Вивід: Noe tekst
Завдання 6 - Властивості з логікою (Необов’язкове)
Оновіть Person, додавши новий атрибут під назвою birthday (день народження). Він повинен бути типу datetime.date (з бібліотеки datetime).
Потім створіть наступні властивості:
- Створіть властивість
age, яка обчислює вік людини на основіbirthdayта поточної дати. - Створіть властивість
is_adult, яка повертаєTrue, якщо людині 18 років або більше, інакшеFalse.
Рішення: Вік та дорослість як властивості
Ось можливе рішення:
from dataclasses import dataclass
from datetime import date
@dataclass
class Person:
name: str
birthday: date
@property
def age(self) -> int:
"""Обчислює вік на основі дати народження та поточної дати"""
today = date.today()
age = today.year - self.birthday.year
# Зменшує на 1, якщо день народження ще не настав цього року
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def is_adult(self) -> bool:
"""Повертає True, якщо особі 18 років або більше"""
return self.age >= 18
# Перевірте код
person = Person(name="Alice", birthday=date(2005, 5, 15))
print(person.age) # Наприклад, 18, якщо поточна дата після 15 травня 2023 року
print(person.is_adult) # True
Ще одна задача!
Чи зможете ви налаштувати інстанціювання Person так, щоб воно приймало як datetime.date, так і текст у форматі "DD-MM-YYYY" для birthday? (Підказка: використовуйте datetime.strptime для перетворення рядка на datetime.date)
