← Назад к вопросам

Как объявляется setter?

1.6 Junior🔥 221 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Объявление Setter в Python

Setter — это метод, который позволяет присваивать значение свойству (property) с дополнительной логикой. В Python это реализуется через декоратор @property и @<property_name>.setter.

1. Базовый пример setter

class Person:
    def __init__(self, name: str):
        self._name = name  # Приватное хранилище
    
    @property
    def name(self) -> str:
        """Getter — чтение значения."""
        return self._name
    
    @name.setter
    def name(self, value: str) -> None:
        """Setter — установка значения с валидацией."""
        if not isinstance(value, str):
            raise TypeError("name должен быть строкой")
        if len(value) < 2:
            raise ValueError("name должен быть не менее 2 символов")
        self._name = value

# Использование
person = Person("John")
print(person.name)  # John (getter)
person.name = "Jane"  # Setter с валидацией
print(person.name)  # Jane

# Валидация срабатывает
try:
    person.name = "A"  # ValueError
except ValueError as e:
    print(f"Ошибка: {e}")

2. Полный цикл: getter, setter, deleter

class Temperature:
    def __init__(self, celsius: float):
        self._celsius = celsius
    
    @property
    def celsius(self) -> float:
        """Getter."""
        return self._celsius
    
    @celsius.setter
    def celsius(self, value: float) -> None:
        """Setter с валидацией."""
        if not isinstance(value, (int, float)):
            raise TypeError("Температура должна быть числом")
        if value < -273.15:
            raise ValueError("Температура ниже абсолютного нуля")
        self._celsius = value
    
    @celsius.deleter
    def celsius(self) -> None:
        """Deleter — удаление значения."""
        print("Температура удалена")
        self._celsius = None
    
    @property
    def fahrenheit(self) -> float:
        """Вычисляемое свойство (только для чтения)."""
        return self._celsius * 9/5 + 32

# Использование
temp = Temperature(25)
print(temp.celsius)     # 25 (getter)
print(temp.fahrenheit)  # 77.0 (вычисляемое)

temp.celsius = 30       # Setter
print(temp.celsius)     # 30

del temp.celsius        # Deleter
print(temp.celsius)     # None

3. Валидация в setter

class BankAccount:
    def __init__(self, balance: float):
        self._balance = balance
    
    @property
    def balance(self) -> float:
        return self._balance
    
    @balance.setter
    def balance(self, value: float) -> None:
        """Установка баланса с проверкой."""
        if value < 0:
            raise ValueError("Баланс не может быть отрицательным")
        self._balance = value
    
    def withdraw(self, amount: float) -> None:
        """Снятие денег (правильный способ)."""
        self.balance = self.balance - amount  # Использует setter для валидации

account = BankAccount(1000)
print(account.balance)  # 1000

account.withdraw(100)
print(account.balance)  # 900

try:
    account.balance = -50  # ValueError
except ValueError as e:
    print(f"Ошибка: {e}")

4. Защита от несанкционированного доступа

class SecureUser:
    def __init__(self, username: str, password: str):
        self._username = username
        self._password = password  # Никогда не выставляем password как property
    
    @property
    def username(self) -> str:
        return self._username
    
    @username.setter
    def username(self, value: str) -> None:
        if not (3 <= len(value) <= 20):
            raise ValueError("Username должен быть 3-20 символов")
        self._username = value
    
    def set_password(self, old_password: str, new_password: str) -> bool:
        """Правильный способ изменения пароля."""
        if self._password != old_password:
            raise ValueError("Неверный старый пароль")
        
        if len(new_password) < 8:
            raise ValueError("Пароль должен быть не менее 8 символов")
        
        self._password = new_password
        return True
    
    def check_password(self, password: str) -> bool:
        """Проверка пароля (не возвращаем сам пароль)."""
        return self._password == password

user = SecureUser("alice", "secure123")
print(user.username)  # alice

user.username = "alice_new"
print(user.username)  # alice_new

# Пароль не может быть прочитан как свойство
print(hasattr(user, "password"))  # False

5. Setter с побочными эффектами

class Widget:
    def __init__(self, width: int, height: int):
        self._width = width
        self._height = height
        self._area = width * height
    
    @property
    def width(self) -> int:
        return self._width
    
    @width.setter
    def width(self, value: int) -> None:
        """Setter обновляет площадь при изменении ширины."""
        if value <= 0:
            raise ValueError("Ширина должна быть положительной")
        
        self._width = value
        self._update_area()  # Побочный эффект
    
    @property
    def height(self) -> int:
        return self._height
    
    @height.setter
    def height(self, value: int) -> None:
        if value <= 0:
            raise ValueError("Высота должна быть положительной")
        
        self._height = value
        self._update_area()  # Побочный эффект
    
    def _update_area(self) -> None:
        """Приватный метод для обновления площади."""
        self._area = self._width * self._height
        print(f"Площадь обновлена: {self._area}")
    
    @property
    def area(self) -> int:
        """Только для чтения."""
        return self._area

widget = Widget(10, 20)
print(widget.area)  # 200

widget.width = 15  # Площадь обновлена: 300
print(widget.area)  # 300

6. Setter в наследованиях

class Animal:
    def __init__(self, age: int):
        self._age = age
    
    @property
    def age(self) -> int:
        return self._age
    
    @age.setter
    def age(self, value: int) -> None:
        if value < 0:
            raise ValueError("Возраст не может быть отрицательным")
        self._age = value

class Dog(Animal):
    @property
    def age(self) -> int:
        """Переопределяем getter."""
        return self._age
    
    @age.setter
    def age(self, value: int) -> None:
        """Переопределяем setter с дополнительной логикой."""
        if value < 0:
            raise ValueError("Возраст не может быть отрицательным")
        if value > 30:
            raise ValueError("Собака не может быть старше 30 лет")
        self._age = value

dog = Dog(5)
dog.age = 10  # OK
print(dog.age)  # 10

try:
    dog.age = 50  # ValueError
except ValueError as e:
    print(f"Ошибка: {e}")

7. Setter с логированием

class LoggedProperty:
    def __init__(self, name: str, initial_value):
        self._name = name
        self._value = initial_value
        self._history = [initial_value]
    
    @property
    def value(self):
        return self._value
    
    @value.setter
    def value(self, new_value) -> None:
        """Setter логирует все изменения."""
        print(f"[LOG] {self._name}: {self._value} -> {new_value}")
        self._value = new_value
        self._history.append(new_value)
    
    @property
    def history(self):
        return self._history

prop = LoggedProperty("count", 0)
prop.value = 5   # [LOG] count: 0 -> 5
prop.value = 10  # [LOG] count: 5 -> 10
print(prop.history)  # [0, 5, 10]

8. Setter для дефолтных значений

class Configuration:
    _defaults = {"debug": False, "timeout": 30}
    
    def __init__(self):
        self._config = self._defaults.copy()
    
    @property
    def debug(self) -> bool:
        return self._config["debug"]
    
    @debug.setter
    def debug(self, value: bool) -> None:
        if not isinstance(value, bool):
            raise TypeError("debug должен быть bool")
        self._config["debug"] = value
    
    @property
    def timeout(self) -> int:
        return self._config["timeout"]
    
    @timeout.setter
    def timeout(self, value: int) -> None:
        if value <= 0:
            raise ValueError("timeout должен быть положительным")
        self._config["timeout"] = value

config = Configuration()
print(config.debug)     # False
config.debug = True
print(config.debug)     # True
config.timeout = 60
print(config.timeout)   # 60

Лучшие практики

✅ Делай так:

  • Используй @property для чтения и @<name>.setter для записи
  • Валидируй данные в setter
  • Логируй важные изменения
  • Защищай критичные данные (пароли, токены)
  • Документируй поведение setter

❌ Не делай так:

  • Не используй setter для побочных эффектов без логирования
  • Не скрывай сложную логику в setter (это запутывает)
  • Не меняй поведение setter между версиями без警告
  • Не делай setter async (используй методы вместо этого)

Синтаксис запоминается как:

  1. @property для getter
  2. @<имя>.setter для setter
  3. @<имя>.deleter для deleter (опционально)

Setter — это мощный инструмент для инкапсуляции и валидации данных.

Как объявляется setter? | PrepBro