Для предотвращения создания объектов с невалидными значениями, можно использовать следующие подходы в конструкторе __init__
:
__init__
проверять типы и значения аргументов. Выбрасывать исключения (ValueError
, TypeError
и т.д.) если данные невалидны._validate_data
), который вызывается из __init__
.pydantic
или marshmallow
, для декларативного описания схем валидации.Пример (Валидация внутри __init__
):
class MyClass:
def __init__(self, value: int):
if not isinstance(value, int):
raise TypeError("value должно быть целым числом")
if value < 0:
raise ValueError("value должно быть неотрицательным")
self.value = value
Предотвращение создания объектов с невалидными значениями в конструкторе через валидацию данных - важная практика для обеспечения надежности и предсказуемости кода на Python. Вот несколько способов реализации:
__init__
: Самый простой подход - выполнять проверку данных непосредственно в конструкторе. Если данные не проходят валидацию, следует возбуждать исключение (ValueError
, TypeError
или кастомное исключение) для предотвращения создания объекта.class MyClass:
def __init__(self, value):
if not isinstance(value, int):
raise TypeError("Value must be an integer")
if value < 0:
raise ValueError("Value must be non-negative")
self.value = value
def __repr__(self):
return f"MyClass(value={self.value})"
# Пример использования
try:
obj = MyClass(-5)
except ValueError as e:
print(f"Ошибка: {e}") # Вывод: Ошибка: Value must be non-negative
try:
obj = MyClass("abc")
except TypeError as e:
print(f"Ошибка: {e}") # Вывод: Ошибка: Value must be an integer
obj = MyClass(5)
print(obj) # Вывод: MyClass(value=5)
property
) с сеттерами: Можно определить свойства с сеттерами, которые будут выполнять валидацию при каждом присваивании значения атрибуту. Это полезно, если атрибут может быть изменен после создания объекта.class MyClass:
def __init__(self, value):
self._value = None # Инициализируем атрибут как None
self.value = value # Используем сеттер для валидации
@property
def value(self):
return self._value
@value.setter
def value(self, value):
if not isinstance(value, int):
raise TypeError("Value must be an integer")
if value < 0:
raise ValueError("Value must be non-negative")
self._value = value
def __repr__(self):
return f"MyClass(value={self._value})"
# Пример использования
obj = MyClass(10)
print(obj.value) # Вывод: 10
try:
obj.value = -5
except ValueError as e:
print(f"Ошибка: {e}") # Вывод: Ошибка: Value must be non-negative
print(obj) # Вывод: MyClass(value=10) - значение не изменилось, так как была ошибка валидации
class ValidatedInteger:
def __init__(self, min_value=None, max_value=None):
self.min_value = min_value
self.max_value = max_value
self._name = None
def __set_name__(self, owner, name):
self._name = name
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self._name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise TypeError(f"{self._name} must be an integer")
if self.min_value is not None and value < self.min_value:
raise ValueError(f"{self._name} must be at least {self.min_value}")
if self.max_value is not None and value > self.max_value:
raise ValueError(f"{self._name} must be at most {self.max_value}")
instance.__dict__[self._name] = value
class MyClass:
age = ValidatedInteger(min_value=0, max_value=150)
quantity = ValidatedInteger(min_value=1)
def __init__(self, age, quantity):
self.age = age
self.quantity = quantity
def __repr__(self):
return f"MyClass(age={self.age}, quantity={self.quantity})"
# Пример использования
try:
obj = MyClass(age=-10, quantity=5)
except ValueError as e:
print(f"Ошибка: {e}") # Вывод: Ошибка: age must be at least 0
try:
obj = MyClass(age=30, quantity=0)
except ValueError as e:
print(f"Ошибка: {e}") # Вывод: Ошибка: quantity must be at least 1
obj = MyClass(age=30, quantity=5)
print(obj) # Вывод: MyClass(age=30, quantity=5)
pydantic
, marshmallow
): Эти библиотеки предоставляют мощные инструменты для определения схем данных и автоматической валидации. Они особенно полезны при работе со сложными структурами данных, например, при сериализации/десериализации JSON.from pydantic import BaseModel, ValidationError
class MyClass(BaseModel):
value: int
@validator('value')
def value_must_be_positive(cls, value):
if value <= 0:
raise ValueError('Value must be positive')
return value
# Пример использования
try:
obj = MyClass(value=-5)
except ValidationError as e:
print(f"Ошибка: {e}") # Вывод: 1 validation error for MyClass\nvalue\n Value must be positive (type=value_error)
obj = MyClass(value=5)
print(obj) # Вывод: value=5
class InvalidValueException(ValueError):
pass
class MyClass:
def __init__(self, value):
if not isinstance(value, int):
raise TypeError("Value must be an integer")
if value < 0:
raise InvalidValueException("Value must be non-negative")
self.value = value
# Пример использования
try:
obj = MyClass(-5)
except InvalidValueException as e:
print(f"Ошибка: {e}") # Вывод: Ошибка: Value must be non-negative
Выбор подхода зависит от сложности валидации и требований к переиспользованию кода. Для простых случаев валидация в __init__
или использование свойств может быть достаточным. Для более сложных сценариев библиотеки валидации или дескрипторы предоставляют большую гибкость и возможности.
Важно помнить, что при выбрасывании исключений, объект не будет создан, что позволяет избежать работы с невалидными данными в дальнейшем.