Декоратор @property в Python

17 июля 2026
Автор

Антон Ларичев

Что такое @property

Декоратор @property — это встроенный инструмент Python, который позволяет определять методы класса, работающие как обычные атрибуты. Благодаря ему можно добавить логику при чтении, записи или удалении атрибута, не меняя внешний интерфейс класса.

Представьте, что вы выпустили библиотеку с классом, у которого есть публичный атрибут age. Пользователи обращаются к нему напрямую: person.age = 25. Через время вам потребовалось добавить валидацию — проверять, что возраст не отрицательный. Если переименовать атрибут в метод set_age(), весь код пользователей сломается. @property решает эту проблему: снаружи всё выглядит как обычный атрибут, а внутри работает метод.

Проблема без @property

Сначала посмотрим, как выглядит код без использования @property:

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age

person = Person("Иван", 30)
person.age = -5  # никакой валидации — ошибка пройдёт незамеченной
print(person.age)  # -5

Добавить проверку можно через явные методы:

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self._age = age

    def get_age(self) -> int:
        return self._age

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

person = Person("Иван", 30)
person.set_age(25)       # неудобно
print(person.get_age())  # неудобно

Это работает, но пользователям приходится запоминать названия методов. @property даёт элегантную альтернативу.

Базовый синтаксис геттера

Минимальное использование @property — это создание геттера, метода для чтения значения:

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self._age = age  # приватный атрибут по соглашению

    @property
    def age(self) -> int:
        return self._age

person = Person("Иван", 30)
print(person.age)  # 30 — обращение как к атрибуту, а не методу
print(person.age())  # TypeError: 'int' object is not callable

Обратите внимание: person.age — без скобок. Python автоматически вызывает метод при обращении к атрибуту.

Атрибут _age с одним подчёркиванием — соглашение об именовании в Python. Он говорит: «это внутренняя деталь реализации, не обращайся к нему напрямую». Двойное подчёркивание __age включает механизм name mangling и используется реже.

Добавление сеттера

Чтобы разрешить запись значения, нужно определить сеттер с декоратором @имя_свойства.setter:

class Person:
    def __init__(self, name: str, age: int):
        self.name = name
        self._age = age

    @property
    def age(self) -> int:
        return self._age

    @age.setter
    def age(self, value: int) -> None:
        if not isinstance(value, int):
            raise TypeError("Возраст должен быть целым числом")
        if value < 0:
            raise ValueError("Возраст не может быть отрицательным")
        if value > 150:
            raise ValueError("Возраст не может превышать 150 лет")
        self._age = value

person = Person("Иван", 30)
print(person.age)   # 30

person.age = 25     # вызывает сеттер
print(person.age)   # 25

person.age = -1     # ValueError: Возраст не может быть отрицательным
person.age = "30"   # TypeError: Возраст должен быть целым числом

Теперь запись через person.age = значение выглядит как обычное присваивание, но внутри работает метод с валидацией.

Добавление делитера

Делитер вызывается при операторе del. Используется редко, но бывает полезен для сброса состояния или освобождения ресурсов:

class DatabaseConnection:
    def __init__(self):
        self._connection = None

    @property
    def connection(self):
        if self._connection is None:
            raise RuntimeError("Соединение не установлено")
        return self._connection

    @connection.setter
    def connection(self, conn):
        self._connection = conn
        print("Соединение установлено")

    @connection.deleter
    def connection(self):
        if self._connection is not None:
            self._connection.close()
            self._connection = None
            print("Соединение закрыто")

db = DatabaseConnection()
db.connection = connect_to_db()  # устанавливаем соединение
del db.connection                 # закрываем соединение

Свойства только для чтения

Если определить только геттер без сеттера, свойство станет доступным только для чтения:

class Circle:
    def __init__(self, radius: float):
        self._radius = radius

    @property
    def radius(self) -> float:
        return self._radius

    @property
    def area(self) -> float:
        import math
        return math.pi * self._radius ** 2

    @property
    def circumference(self) -> float:
        import math
        return 2 * math.pi * self._radius

circle = Circle(5)
print(circle.radius)         # 5
print(circle.area)           # 78.53981633974483
print(circle.circumference)  # 31.41592653589793

circle.area = 100  # AttributeError: can't set attribute

Свойства area и circumference вычисляются динамически и не хранятся в памяти — они пересчитываются каждый раз при обращении.

Вычисляемые свойства

@property удобен для свойств, которые зависят от других атрибутов:

class Rectangle:
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    @property
    def area(self) -> float:
        return self.width * self.height

    @property
    def perimeter(self) -> float:
        return 2 * (self.width + self.height)

    @property
    def is_square(self) -> bool:
        return self.width == self.height

rect = Rectangle(4, 6)
print(rect.area)       # 24
print(rect.perimeter)  # 20
print(rect.is_square)  # False

rect.width = 6
print(rect.is_square)  # True — пересчитывается автоматически

Валидация в конструкторе через сеттер

Часто применяют одну хитрость: вызывать сеттер прямо из __init__, чтобы валидация работала и при создании объекта:

class Product:
    def __init__(self, name: str, price: float, quantity: int):
        self.name = name
        self.price = price      # вызывает сеттер
        self.quantity = quantity  # вызывает сеттер

    @property
    def price(self) -> float:
        return self._price

    @price.setter
    def price(self, value: float) -> None:
        if value < 0:
            raise ValueError("Цена не может быть отрицательной")
        self._price = float(value)

    @property
    def quantity(self) -> int:
        return self._quantity

    @quantity.setter
    def quantity(self, value: int) -> None:
        if not isinstance(value, int) or value < 0:
            raise ValueError("Количество должно быть неотрицательным целым числом")
        self._quantity = value

    @property
    def total_value(self) -> float:
        return self._price * self._quantity

# Валидация срабатывает уже при создании
try:
    product = Product("Ноутбук", -1000, 5)  # ValueError здесь
except ValueError as e:
    print(e)  # Цена не может быть отрицательной

product = Product("Ноутбук", 50000, 3)
print(product.total_value)  # 150000.0

Заметьте: в __init__ используется self.price = price, а не self._price = price. Первый вариант вызывает сеттер, второй — обходит его.

Наследование и переопределение свойств

При наследовании нужно быть внимательным с переопределением свойств. Если переопределяется только сеттер, геттер тоже нужно явно указать:

class Animal:
    def __init__(self, name: str):
        self._name = name

    @property
    def name(self) -> str:
        return self._name

    @name.setter
    def name(self, value: str) -> None:
        self._name = value


class Pet(Animal):
    @property
    def name(self) -> str:
        return f"Питомец: {self._name}"

    @name.setter  # нельзя использовать Animal.name.setter здесь
    def name(self, value: str) -> None:
        if not value.strip():
            raise ValueError("Имя не может быть пустым")
        self._name = value.strip()


pet = Pet("Барсик")
print(pet.name)   # Питомец: Барсик
pet.name = "Мурзик"
print(pet.name)   # Питомец: Мурзик

Если переопределить только геттер в дочернем классе, сеттер из родителя перестанет работать — Python видит новый дескриптор без сеттера.

Практический пример: класс Temperature

Собирём всё вместе в реальном примере — класс для работы с температурой, поддерживающий разные единицы измерения:

class Temperature:
    ABSOLUTE_ZERO_CELSIUS = -273.15

    def __init__(self, celsius: float = 0):
        self.celsius = celsius  # через сеттер

    @property
    def celsius(self) -> float:
        return self._celsius

    @celsius.setter
    def celsius(self, value: float) -> None:
        if value < self.ABSOLUTE_ZERO_CELSIUS:
            raise ValueError(
                f"Температура не может быть ниже абсолютного нуля ({self.ABSOLUTE_ZERO_CELSIUS}°C)"
            )
        self._celsius = float(value)

    @property
    def fahrenheit(self) -> float:
        return self._celsius * 9 / 5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value: float) -> None:
        self.celsius = (value - 32) * 5 / 9  # делегируем валидацию сеттеру celsius

    @property
    def kelvin(self) -> float:
        return self._celsius - self.ABSOLUTE_ZERO_CELSIUS

    @kelvin.setter
    def kelvin(self, value: float) -> None:
        if value < 0:
            raise ValueError("Температура в Кельвинах не может быть отрицательной")
        self.celsius = value + self.ABSOLUTE_ZERO_CELSIUS

    def __repr__(self) -> str:
        return f"Temperature({self._celsius:.2f}°C)"


temp = Temperature(100)
print(temp.celsius)     # 100.0
print(temp.fahrenheit)  # 212.0
print(temp.kelvin)      # 373.15

# Меняем через фаренгейты — остальные пересчитываются автоматически
temp.fahrenheit = 32
print(temp.celsius)  # 0.0
print(temp.kelvin)   # 273.15

# Попытка установить невозможную температуру
try:
    temp.celsius = -300
except ValueError as e:
    print(e)  # Температура не может быть ниже абсолютного нуля (-273.15°C)

Когда использовать @property

@property подходит в следующих случаях:

  • Валидация при записи — проверка типов, диапазонов, бизнес-правил
  • Вычисляемые атрибуты — значения, которые зависят от других атрибутов
  • Ленивая инициализация — дорогостоящие вычисления только при первом обращении
  • Логирование и отладка — запись фактов обращения к атрибуту
  • Обратная совместимость — добавление логики к существующему публичному атрибуту без изменения API

Не стоит использовать @property там, где достаточно обычного атрибута. Если нет валидации, вычисления или побочных эффектов — прямой атрибут проще и понятнее.

Сравнение с slots

При использовании __slots__ важно помнить, что @property остаётся на уровне класса, а __slots__ управляет хранилищем экземпляра. Они совместимы:

class Point:
    __slots__ = ("_x", "_y")

    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    @property
    def x(self) -> float:
        return self._x

    @x.setter
    def x(self, value: float) -> None:
        self._x = float(value)

    @property
    def y(self) -> float:
        return self._y

    @y.setter
    def y(self, value: float) -> None:
        self._y = float(value)


p = Point(1.0, 2.0)
print(p.x, p.y)  # 1.0 2.0

Здесь _x и _y указаны в __slots__ — они хранятся без словаря __dict__, что экономит память.

Итог

Декоратор @property — ключевой инструмент для написания идиоматичного Python-кода. Он позволяет:

  • скрыть детали реализации за чистым интерфейсом
  • добавить валидацию без изменения публичного API
  • создавать вычисляемые свойства, обновляющиеся автоматически
  • контролировать доступ к атрибутам класса

Правило хорошего тона: если атрибут публичный и не требует никакой логики — оставьте его обычным атрибутом. Добавляйте @property только тогда, когда нужна реальная причина: валидация, вычисление или побочный эффект.

Для глубокого погружения в Python и объектно-ориентированное программирование смотрите курс на PurpleSchool: Python-разработчик.

@classmethod и @staticmethod в PythonСтрелочка вправо

Постройте личный план изучения Python до уровня Middle — бесплатно!

Python — часть карты развития Backend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Все гайды по Python

Как отправлять запросы с помощью requests в PythonПочему Python выводит значение без команды printКак работает команда print в PythonВозможности Python для автоматизации задачPython get — методы получения данныхРабота с JSON в Python на примерахКак находить и исправлять ошибки в PythonРабота с данными через API и внешние сервисыСтруктура и оформление кода PythonОсновы Django с PythonПолезные приёмы в Python для повседневной работыИспользование locals в Python для отладкиИнтеграция PHP и PythonКак выполнять HTTPS-запросы в PythonFastAPI Python — быстрый старт: создание REST API с нуляКак работать с API в Python
Ввод целого числа в PythonВедение логов в PythonУдаление данных в Python с помощью removeОбработка исключений с помощью try/except в PythonФункция super() в Python — как вызвать метод родителяСоздание собственных контекстных менеджеров в PythonРабота с символами программирования PythonРабота с переменной X в PythonРабота с классами в PythonКак скачать Python на компьютерПростая программа на Python для начинающихОсновы Python для тех, кто начинаетПоддерживается ли Python 2 и стоит ли его использоватьЧто нового в Python 3Python 1 — с чего начиналась история языкаКоманда python print - полное руководство по выводу данныхПравила именования переменных в PythonПользовательские исключения в PythonОсновы Python coreОписание объектов PythonНаследование классов в Python — основы и примерыМножественное наследование в Python — примеры и MROКонтекстный менеджер with в Python — как работает и зачем нуженКомментарии в Python — однострочные, многострочные и docstringКакой Python выбрать для установкиКак вывести целое число с помощью print в PythonКак установить Python на Windows macOS и LinuxКак пользоваться консолью PythonКак получить последний элемент в PythonКак найти значение в PythonКак настроить PythonКак использовать print для строк в PythonКак работает интерпретатор PythonИнструкция по работе с PythonБлок finally в обработке исключений PythonЦелые числа в PythonАбстрактные классы в Python — ABC и abstractmethod
Pydantic: валидация данных в Python
Управление проектами на GitHub с PythonЗагрузка данных PythonСоздание веб-приложений на Flask PythonСоздание бота на PythonСоздание интерфейсов Python QTСоздание игр с PygameСоздание GUI в PythonКак работать со словарями в PythonРабота с библиотеками через Python PackagingРабота со временем в Python при помощи модуля timePython name — особенности переменнойМатематические операции в Python с модулем mathPython listing — что это и как использоватьPytest — тестирование на Python: полное руководствоОбработка изображений с OpenCV PythonNumPy в Python — основы и применение в задачахМашинное обучение с PythonИспользование Anaconda с PythonМодуль contextlib в Python — утилиты для контекстных менеджеровБиблиотеки Python и их применение в проектах
Возврат значений из функции в PythonВложенные функции в PythonСоздание собственных декораторов в PythonРабота с функцией map в PythonЦикл while в Python и примеры использованияОбработка чисел, введённых через input в PythonОсновные операторы в Python с примерамиУсловные выражения if else в Python для начинающихКак выполняется вызов функций call в PythonПродвинутые генераторы в Python — send, throw, close и корутиныПозиционные и именованные аргументы в PythonОбъявление переменных и управление областью видимости в PythonПередача аргументов по ссылке и по значению в PythonПередача аргументов через args и kwargs в PythonОсновные методы Python и примеры их использованияОператор match/case в Python 3.10+ — основы структурного сопоставленияПаттерны match/case в Python — деструктуризация, guard и вложенные шаблоныПрактические примеры match/case в Python — реальные сценарии примененияЛокальные и глобальные переменные в PythonЧасто используемые команды PythonКлючевые слова global и nonlocal в PythonКак создавать функции в PythonКак работает сборщик мусора в PythonКак работает область видимости переменных в PythonКак работает функция callable в PythonКак работает функция any и all в PythonКак проверить тип переменной в PythonКак передать функцию как аргумент в PythonКак использовать функцию isinstance в PythonКак использовать функцию filter в PythonКак использовать функцию filter в PythonКак использовать функцию eval безопасно в PythonКак использовать декораторы в PythonИзменяемые и неизменяемые типы данных в PythonГенераторы и yield в Python — как создавать и использоватьГенераторные выражения в Python — синтаксис и примерыФункции в Python и способы их вызоваФункции как объекты в PythonЧто такое замыкания в PythonЧто делает функция reduce в PythonЧто делает функция id в PythonАргументы по умолчанию в PythonАнонимные функции и lambda в PythonАлгоритмы на Python — примеры и объяснение
Запись данных в PythonУстановка pip в PythonУправление зависимостями requirement в PythonУправление библиотеками с помощью Python PackagingУдаление пробелов с помощью strip в PythonСтруктурирование кода в PythonСоздание исполняемого файла Python в exeРазбор traceback в модуле PythonРазбор site-packages в PythonРазбор Program Files в PythonРабота с Unicode кодировками в PythonРабота с системными функциями Python sysРабота с папкой AppData в PythonРабота с модулем logging в PythonРабота с каталогами в PythonРабота с CSV в PythonВиртуальная среда venv в Python — создание и настройкаКак создать простое приложение на PythonИспользование pip в Python для установки пакетовМодули в Python и организация кода в проектеИмпорт модулей в Python и правила подключенияРабота с файлами в Python пошаговоЧто делает компилятор Python и как он работаетПолучение строки из модуля PythonПодключение файлов в Python с includeПеременные среды в PythonСборка проекта с помощью packaging в PythonНастройка Python сервераИспользование Python на UbuntuИспользование консоли PythonИспользование кодировок в PythonИнициализация пакетов PythonИмпорт модулей PythonИмпорт имен в PythonСреда IDLE Python и базовые возможностиЧтение и запись TXT в PythonЧтение файлов в Python с помощью open file
Удаление элементов из списка PythonТипы данных в Python — обзор и рекомендацииОсновные операции со строками в PythonМетоды str в Python и обработка текстаСписки в Python и их ключевые методыСоздание списков данных в PythonРабота со столбцами в PythonРабота со строками и символами в PythonРабота со списком значений в PythonРабота с таблицами в Python с помощью DataFrameРабота с RFR в PythonРабота с пробелами в PythonРабота с массивами в PythonРабота с кортежами tuple PythonРабота с координатами X и Y в PythonРабота с ключами в PythonРабота с элементами данных PythonРабота с двоичными числами PythonРабота с данными в PythonРабота с данными NumPy PythonРабота с большими числами в PythonРабота с битами в PythonРабота с байтами в PythonЧто такое значение в Python и как его определитьМножества в Python и операции с нимиИспользование range в Python для цикловПроверка на четность в PythonПроверка числа в PythonПреобразование типов в PythonПреобразование списка в строку PythonПреобразование числа в строку в PythonПостроение графиков в PythonОпределение индекса элемента в PythonОкругление чисел в PythonОбъединение списков в Python с помощью zipМножества в PythonМассивы в Python и отличие от списковМассив чисел в PythonКортежи данных в PythonКак вычислить сумму чисел в PythonКак получить остаток от деления в PythonКак найти следующее число в PythonИспользование Unicode в PythonТип int в Python и его особенностиИндекс списка в PythonФункции для работы со строками в PythonЭлементы Python и способы доступа к нимДоступ к элементам массива в PythonДеление чисел в PythonРабота с данными в Python на практикеКак работать с числами в Python
Открыть базу знаний

Лучшие курсы по теме

Иконка молнииНовый
изображение курса

Основы Python

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Nest.js с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.6
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Docker и Ansible

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий