Hopp til innhold

Перевірка даних і класи

Skip to content

Це машинний переклад, який може містити помилки!

Валідація даних і класи

У Python (це також стосується більшості інших мов) можна створювати власні об’єкти зі своїми значеннями, правилами та функціями. Це називають класами (англійською — classes). Ми використовуємо класи для збору пов’язаних даних і функцій в єдину одиницю, наприклад, клас Ordre, який містить дані, такі як ordre_id, kunde_navn, produkter, а також функції, такі як legg_til_produkt(), beregn_total() тощо.

Словник (JSON) та класи (об’єкти)

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

Британські розробники Python кажуть: "Це конструктор, __init__"?

Приклад із декоратором 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)

Easy Завдання 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)
Рішення: Dataclass для Person

Ось можливе рішення:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    eye_color: str
    phone_number: str
    email: str

Medium Завдання 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).

Примітка

Ми можемо також зробити це в самому класі Person, але часто краще створювати окремі класи для речей, які можна використовувати повторно.

Приклад функції __post_init__

from dataclasses import dataclass

@dataclass
class Email:
    address: str

    def __post_init__(self):
        print(f"Перевірка електронної пошти: {self.address}")
        # Ваш код тут

Для простого валідації електронної пошти ми можемо, наприклад, перевірити, чи містить адреса як символ @, так і крапку . Також ви можете переконатися, що адреса відповідає регулярному виразу. (Більш складний підхід, але сміливо шукайте в інтернеті!)

Розв’язка: Код для простої валідації електронної пошти

Ось можливе рішення. Ми використовуємо винятки (exceptions), щоб «зламати» програму, якщо адреса електронної пошти є недійсною; це призведе до негайного зупинення програми та виведення повідомлення про помилку.

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) # Недійсний, відсутній @

Medium Завдання 3 — Валідація номеру телефону

Створіть клас аналогічно до того, який ви створювали для електронних поштових адрес, але тепер для телефонного номера

Виклик щодо верифікації телефону!

Чи можете ви виправити валідацию номера телефону так, щоб вона приймавала як літери (стр), так і цифри (інт)? Наприклад, 12345678 та "12345678" мають бути обидва дійсними.

Також спробуйте додати коди країн як атрибут (підзначення класу). Наприклад, 47 для Норвегії, 46 для Швеції

Medium Завдання 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(?)

Hard Завдання 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

Примітка

Властивості є унікальними тим, що їм не потрібні параметри і їх не потрібно викликати з дужками. У прикладі ми отримуємо значення person.value без дужок (а не person.value(), хоча технічно це функція).

Альтернативний приклад, який приймає як str, так і ExampleValue
@dataclass
class Person:
    name: str
    _value: str

    def __init__(self, name: str, value: str | ExampleValue):
        self.name = name
        if isinstance(value, ExampleValue):
            self._value = value
        elif isinstance(value, str):
            self._value = ExampleValue(value)
        else:
            raise TypeError("value має бути типу str або ExampleValue")

    @property
    def value(self) -> str:
        return self._value.attribute  # Отримує підзначення безпосередньо

# Тест коду
person = Person(name="Аліса", value="Деякий текст")
print(person.value)  # Вихідні дані: Деякий текст

Hard Завдання 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)