Что такое @property в Python?

Ответ

@property — это декоратор в Python, который позволяет превратить метод класса в атрибут, доступный только для чтения, и при этом легко добавить логику его вычисления или валидации при попытке установки значения. Это ключевой инструмент для реализации инкапсуляции и управления доступом к данным.

Как это работает: Декоратор создает property object, который использует методы-геттер, сеттер и делитер.

Базовый пример (вычисляемый атрибут):

class Circle:
    def __init__(self, radius):
        self._radius = radius  # Приватный атрибут

    @property
    def radius(self):
        """Геттер. Вызывается при обращении к circle.radius"""
        return self._radius

    @property
    def area(self):
        """Вычисляемое свойство. Площадь зависит от радиуса."""
        return 3.14159 * self._radius ** 2

# Использование
c = Circle(5)
print(c.radius)  # 5 -> Вызывается геттер radius
print(c.area)    # 78.53975 -> Вызывается геттер area
# c.area = 10  # AttributeError: can't set attribute (у area нет сеттера)

Полный контроль с сеттером и делитером:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        """Сеттер. Позволяет добавить валидацию."""
        if value < -273.15:
            raise ValueError("Температура ниже абсолютного нуля!")
        self._celsius = value

    @property
    def fahrenheit(self):
        """Еще одно вычисляемое свойство."""
        return self._celsius * 9/5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        """Сеттер для свойства fahrenheit изменяет базовое значение celsius."""
        self._celsius = (value - 32) * 5/9

# Использование
t = Temperature(25)
print(t.fahrenheit)  # 77.0

t.fahrenheit = 100
print(t.celsius)     # 37.777... -> celsius изменилось через сеттер fahrenheit

# t.celsius = -300  # ValueError: Температура ниже абсолютного нуля!

Зачем это нужно:

  1. Инкапсуляция: Позволяет скрыть внутреннее представление данных (например, _radius), предоставив контролируемый публичный интерфейс.
  2. Валидация: Сеттер позволяет проверять и отклонять некорректные значения.
  3. Вычисляемые атрибуты: Можно создавать атрибуты, которые вычисляются на лету на основе других данных.
  4. Обратная совместимость: Если ранее у класса был публичный атрибут x, и вы решили добавить логику при его изменении, вы можете превратить его в property, не ломая существующий код, который использует obj.x.

Ответ 18+ 🔞

Э, слушай, а вот этот @property — это вообще огонь, реально удобная штука. Представь, у тебя есть класс, и ты хочешь сделать так, чтобы к какому-то значению можно было обращаться, как к простому полю, но при этом, когда его читаешь или меняешь, срабатывала твоя хитрая логика. Вот для этого он и нужен, этакий волшебный плащ для твоих методов.

Как оно, блядь, устроено: По сути, этот декоратор создаёт property object, у которого есть свои методы-обработчики: чтобы получить значение, чтобы установить и чтобы удалить. Всё по фэншую.

Простой пример, чтобы въехать (вычисляемое свойство):

class Circle:
    def __init__(self, radius):
        self._radius = radius  # Сделал приватным, чтобы не лезли

    @property
    def radius(self):
        """Геттер. Сработает, когда кто-то напишет circle.radius"""
        return self._radius

    @property
    def area(self):
        """А вот это уже вычисляемое на ходу. Площадь от радиуса зависит."""
        return 3.14159 * self._radius ** 2

# Используем
c = Circle(5)
print(c.radius)  # 5 -> Тихо вызвался геттер radius
print(c.area)    # 78.53975 -> Вызвался геттер area и всё посчитал
# c.area = 10  # AttributeError: can't set attribute (сеттера-то нет, ебать копать!)

А вот тут уже полный контроль, с сеттером и всем таким:

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        """А вот и сеттер. Тут можно впендюрить любую проверку."""
        if value < -273.15:
            raise ValueError("Температура ниже абсолютного нуля! Ты чё, ёпта?")
        self._celsius = value

    @property
    def fahrenheit(self):
        """Ещё одно свойство, которое из celsius выводится."""
        return self._celsius * 9/5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        """Установка по Фаренгейту автоматом пересчитает Цельсии. Удобно, блядь!"""
        self._celsius = (value - 32) * 5/9

# Поехали
t = Temperature(25)
print(t.fahrenheit)  # 77.0

t.fahrenheit = 100
print(t.celsius)     # 37.777... -> celsius поменялось через сеттер fahrenheit, ни хуя себе!

# t.celsius = -300  # ValueError: Температура ниже абсолютного нуля! Вот тебе и валидация.

И зачем весь этот цирк, спросишь?

  1. Инкапсуляция, ёпта: Прячешь внутреннюю кухню (типа _radius), а наружу выдаёшь красивый и безопасный фасад.
  2. Валидация — наше всё: В сеттер можно впихнуть проверки, чтобы никакой распиздяй не засунул в твой объект какую-нибудь дичь.
  3. Атрибуты на лету: Можно делать поля, которые вычисляются только когда к ним обращаются. Лениво и эффективно.
  4. Обратная совместимость — царь: Был у тебя публичный атрибут x. Решил добавить логику при его изменении. Без property пришлось бы менять весь код, который к нему обращается. А так — превратил метод в property, и весь старый код даже не заметит подмены, доверия ебать ноль у него будет. Хитрая жопа, да?