Property: примеры (PYTHON)

Использование property в Python на практике
Раздел: Декораторы, Доступ к атрибутам
property(fget: callable=None, fset: callable=None, fdel: callable=None, doc: str=None): property

Основы функции property

Функция property() в Python создает управляемый атрибут объекта, который обеспечивает доступ к данным через методы-геттеры, сеттеры и делитеры. Эта функция используется для инкапсуляции, валидации данных, вычисления атрибутов на лету и контроля доступа.

Аргументы функции:

  • fget (опциональный): метод для получения значения атрибута.
  • fset (опциональный): метод для установки значения атрибута.
  • fdel (опциональный): метод для удаления атрибута.
  • doc (опциональный): строка документации для свойства.

Функция возвращает объект свойства, который обычно присваивается имени атрибута в классе.

Простые примеры

Базовый пример с использованием property как функции:

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

    def get_width(self):
        return self._width

    def set_width(self, value):
        if value <= 0:
            raise ValueError("Ширина должна быть положительной")
        self._width = value

    width = property(get_width, set_width)

r = Rectangle(10, 20)
print(r.width)
r.width = 30
print(r.width)
10
30

Использование property как декоратора:

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

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("Радиус должен быть положительным")
        self._radius = value

    @radius.deleter
    def radius(self):
        print("Удаление радиуса")
        del self._radius

c = Circle(5)
print(c.radius)
c.radius = 7
del c.radius
5
Удаление радиуса

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

class Square:
    def __init__(self, side):
        self._side = side

    @property
    def area(self):
        return self._side ** 2

s = Square(4)
print(s.area)
# s.area = 100  # Вызовет AttributeError
16

Альтернативные подходы в Python

Декораторы @property, @setter, @deleter: самый распространенный синтаксический сахар для создания свойств, делает код чище и читабельнее.

Дескрипторы: более мощный и гибкий механизм для создания переиспользуемых свойств. Класс с методами __get__, __set__, __delete__.

class PositiveNumber:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, obj, objtype=None):
        return obj.__dict__.get(self.name)

    def __set__(self, obj, value):
        if value <= 0:
            raise ValueError("Значение должно быть положительным")
        obj.__dict__[self.name] = value

class Box:
    weight = PositiveNumber()

b = Box()
b.weight = 10
print(b.weight)
10

Библиотека attrs/dataclasses: предоставляет декларативный способ создания классов с автоматически генерируемыми методами. Поддерживает валидацию через декораторы.

Свойства используют, когда нужен контроль доступа к конкретному атрибуту. Дескрипторы подходят для создания переиспользуемых валидаторов. Библиотеки вроде attrs применяют для сокращения шаблонного кода в классах данных.

Распространенные ошибки

Попытка определить сеттер для свойства без геттера:

class Example:
    @property
    def x(self):
        return self._x

    # Неверный порядок: сеттер определен до геттера
    @x.setter
    def x(self, value):
        self._x = value

# Правильно: сначала @property, потом @x.setter

Использование property для скрытия тяжелых операций, что приводит к неочевидной производительности:

class Data:
    @property
    def items(self):
        # Каждый доступ выполняет запрос к БД
        return query_database()  # Медленно!

Конфликт имен между свойством и приватным атрибутом:

class Confusing:
    def __init__(self, x):
        self.x = x  # Вызывает сеттер, а не устанавливает атрибут

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value * 2

c = Confusing(5)
print(c.x)  # Ожидается 5, но будет 10
10

Изменения в новых версиях Python

В Python 3.8 добавлен декоратор functools.cached_property, который кэширует результат вычисления свойства после первого доступа. В Python 3.9 улучшена производительность и стабильность работы property. В Python 3.10 и выше property продолжает работать как дескриптор без фундаментальных изменений в API.

from functools import cached_property

class Data:
    def __init__(self, n):
        self.n = n

    @cached_property
    def expensive_calc(self):
        print("Выполняется вычисление...")
        return self.n * 2

d = Data(5)
print(d.expensive_calc)
print(d.expensive_calc)  # Второй раз вычисление не выполняется
Выполняется вычисление...
10
10

Расширенные примеры использования

Вычисляемое свойство на основе других атрибутов:

Пример python
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def fahrenheit(self):
        return self.celsius * 9/5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5/9

t = Temperature(100)
print(t.fahrenheit)
t.fahrenheit = 212
print(t.celsius)
212.0
100.0

Свойство с кэшированием результата:

Пример python
class Factorial:
    def __init__(self, n):
        self.n = n
        self._cache = None

    @property
    def result(self):
        if self._cache is None:
            print("Вычисляем факториал...")
            import math
            self._cache = math.factorial(self.n)
        return self._cache

f = Factorial(5)
print(f.result)
print(f.result)  # Используется кэш
Вычисляем факториал...
120
120

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

Пример python
class UserProfile:
    def __init__(self, user_id):
        self.user_id = user_id
        self._profile_data = None

    @property
    def profile(self):
        if self._profile_data is None:
            print("Загрузка данных из БД...")
            # Имитация загрузки
            self._profile_data = {"name": "Иван", "age": 30}
        return self._profile_data

u = UserProfile(1)
print(u.profile["name"])
print(u.profile["age"])  # Данные уже загружены
Загрузка данных из БД...
Иван
30

Динамическое свойство, зависящее от состояния объекта:

Пример python
class Account:
    def __init__(self, balance, currency_rate):
        self.balance = balance
        self.currency_rate = currency_rate

    @property
    def balance_in_usd(self):
        return self.balance * self.currency_rate

acc = Account(1000, 0.013)
print(f"{acc.balance_in_usd:.2f} USD")
acc.currency_rate = 0.015
print(f"{acc.balance_in_usd:.2f} USD")
13.00 USD
15.00 USD

Свойство для проверки и преобразования типа:

Пример python
class Product:
    def __init__(self, price):
        self._price = float(price)

    @property
    def price(self):
        return round(self._price, 2)

    @price.setter
    def price(self, value):
        try:
            self._price = float(value)
        except ValueError:
            raise ValueError("Цена должна быть числом")

p = Product(19.9999)
print(p.price)
p.price = "25.50"
print(p.price)
20.0
25.5

Property в других языках программирования

C#: свойства с синтаксисом get/set.

// C#
class Person {
    private string _name;
    public string Name {
        get { return _name; }
        set { _name = value; }
    }
}

JavaScript: геттеры и сеттеры через defineProperty или синтаксис get/set в классах.

// JavaScript
const obj = {
    _value: 0,
    get value() { return this._value; },
    set value(val) { this._value = val; }
};
console.log(obj.value);
0

Java: используют методы getXxx() и setXxx(), иногда с аннотациями Lombok.

// Java с Lombok
// @Getter @Setter
// private String name;

PHP: магические методы __get, __set или ключевые слова get/set в интерфейсах.

// PHP
class User {
    private $data = [];
    public function __get($name) {
        return $this->data[$name];
    }
    public function __set($name, $value) {
        $this->data[$name] = $value;
    }
}

Kotlin: свойства с автоматическими геттерами и сеттерами.

// Kotlin
class Person(var name: String) {
    var age: Int = 0
        set(value) {
            if (value < 0) throw IllegalArgumentException()
            field = value
        }
}

Отличие Python в том, что property является полноценным объектом-дескриптором, что позволяет использовать его на уровне класса.

питон property function comments

En
Property Return a property attribute