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.radius5 Удаление радиуса
Свойство только для чтения:
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 # Вызовет AttributeError16
Альтернативные подходы в 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, но будет 1010
Изменения в новых версиях 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
Расширенные примеры использования
Вычисляемое свойство на основе других атрибутов:
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
Свойство с кэшированием результата:
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
Свойство для ленивой загрузки данных:
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
Динамическое свойство, зависящее от состояния объекта:
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
Свойство для проверки и преобразования типа:
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 является полноценным объектом-дескриптором, что позволяет использовать его на уровне класса.