Это машинный перевод текста, который может содержать ошибки!
В 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
Пример с декоратором 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)
Решение: Dataclass для 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).
Примечание
Мы можем сделать это и в самом классе Person, но часто лучше создавать отдельные классы для того, что можно переиспользовать.
Пример функции _postinit
from dataclasses import dataclass
@dataclass
class Email:
address: str
def __post_init__(self):
print(f"Валидация email: {self.address}")
# Ваш код здесь
Для простой валидации электронной почты мы можем, например, проверить наличие как символа @, так и точки (.). Также можно проверить соответствие адреса регулярному выражению (regex). (Более продвинутый вариант, но не стесняйтесь искать информацию в интернете!)
Решение: Код для простой валидации электронной почты
Вот возможное решение, мы используем исключения (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) # Недействительный, отсутствует @
Задание 3 — Валидация номера телефона
Создайте класс аналогично тому, как вы делали это для адресов электронной почты, но теперь для номеров телефонов
Проблема с валидацией телефона!
Можешь исправить валидацию номера телефона так, чтобы она принимала как буквы (строки), так и цифры (целые числа)? Например, оба варианта — 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. Это связано с тем, что одно и то же имя нельзя использовать одновременно для свойства и атрибута в датаклассе.
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, так и ПримерЗначения
@dataclass
class Person:
name: str
_значение: str
def __init__(self, name: str, значение: str | ПримерЗначения):
self.name = name
if isinstance(значение, ПримерЗначения):
self._значение = значение
elif isinstance(значение, str):
self._значение = ПримерЗначения(значение)
else:
raise TypeError("значение должно быть типа str или ПримерЗначения")
@property
def значение(self) -> str:
return self._значение.атрибут # Получает дочернее значение напрямую
# Тестируем код
человек = Person(name="Алиса", значение="Некоторый текст")
print(человек.значение) # Вывод: Некоторый текст
Задание 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, так и текст в формате "ДД-ММ-ГГГГ" для параметра birthday? (Подсказка: используйте datetime.strptime для преобразования строки в объект datetime.date)
