Управление атрибутами объектов: от основ до продвинутых техник

Раздел: Объектно-ориентированное программирование -> Объектно-ориентированное программирование

Атрибуты объекта в Python

Прямое обращение к атрибутам

Наиболее эффективный способ доступа к атрибуту объекта – использование оператора точки: obj.attribute. Этот метод не требует дополнительных вычислений и применяется в большинстве ситуаций.

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

p = Person('Анна')
print(p.name)  # Анна

атрибуты класса python (атрибуты классов и объектов в python)

Возможная проблема: если атрибут не существует, возникает исключение AttributeError. Решение – предварительная проверка через hasattr(obj, 'name') или оборачивание в try/except.

Цель использования: когда имя атрибута известно на этапе написания кода и нет необходимости в динамике.

Как получить или изменить атрибут, если его имя хранится в переменной?

Функции getattr, setattr, hasattr, delattr позволяют работать с атрибутами по строковому имени.

class Book:
    def __init__(self, title):
        self.title = title

b = Book('Война и мир')
attr_name = 'title'
print(getattr(b, attr_name))  # Война и мир
setattr(b, attr_name, 'Тихий Дон')
print(b.title)  # Тихий Дон
print(hasattr(b, 'author'))  # False

библиотека классов python (библиотека классов в python)

Исключение: если атрибут отсутствует и не задано значение по умолчанию, getattr вызывает AttributeError. Следует передавать третий аргумент – значение по умолчанию.

Цель: динамическое управление атрибутами, например, при сериализации или конфигурации.

Как добавить геттер и сеттер с проверкой данных?

Декоратор @property превращает метод в атрибут, позволяя контролировать чтение и запись.

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

t = Temperature(25)
print(t.celsius)  # 25
t.celsius = 30
print(t.celsius)  # 30

метод объекта python (методы объектов в python)

Ошибка: внутри геттера или сеттера нельзя обращаться к свойству по тому же имени (например, self.celsius), иначе возникает бесконечная рекурсия. Используется атрибут с подчёркиванием self._celsius.

Цель: инкапсуляция и валидация данных при установке значения.

Как ограничить набор атрибутов объекта и уменьшить память?

Определение __slots__ в классе фиксирует список допустимых атрибутов. Экземпляры не имеют __dict__ и занимают меньше памяти.

class Point:
    __slots__ = ('x', 'y')

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

p = Point(1, 2)
p.x = 10
# p.z = 5  # AttributeError

метод call python (метод __call__ в python)

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

Цель: экономия памяти (полезно для большого числа объектов) и предотвращение случайного создания лишних атрибутов.

Как создать атрибут с кастомным поведением, разделяемым между несколькими классами?

Дескрипторы – классы, определяющие методы протокола дескриптора. Они позволяют перехватывать операции доступа к атрибуту.

class Positive:
    def __get__(self, obj, objtype=None):
        return obj._value

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

class Order:
    quantity = Positive()

    def __init__(self, quantity):
        self.quantity = quantity

o = Order(5)
print(o.quantity)  # 5
# o.quantity = -3  # ValueError

Python структура объекта (структура объекта в python)

Ошибка: если дескриптор неправильно использует имя атрибута (в примере obj._value), может возникнуть конфликт при совместном использовании с другими дескрипторами. Рекомендуется хранить значение в отдельном словаре с уникальным ключом.

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

Как полностью контролировать доступ ко всем атрибутам объекта?

Методы __getattr__ и __setattr__ вызываются при обращении к отсутствующему атрибуту или при установке любого атрибута соответственно.

class Dynamic:
    def __getattr__(self, name):
        return f'Атрибут {name} не найден'

    def __setattr__(self, name, value):
        if not isinstance(value, (int, float)):
            raise TypeError('Разрешены только числа')
        super().__setattr__(name, value)

d = Dynamic()
print(d.foo)  # Атрибут foo не найден
d.bar = 42
print(d.bar)  # 42
# d.baz = 'строка'  # TypeError

Рекурсия: в __setattr__ нельзя напрямую писать self.name = value, так как снова вызывается __setattr__. Необходимо использовать super().__setattr__ или словарь self.__dict__.

Цель: реализация динамических атрибутов, прокси-объектов, отладка доступа.

- Object attribute python (атрибуты объекта в python)
- Python call method (вызов метода в python)
- Python класс данных (класс данных в python)
Пример
class CachedProperty:
    def __init__(self, func):
        self.func = func
        self.name = func.__name__

    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        if not hasattr(obj, '_cache'):
            obj._cache = {}
        if self.name not in obj._cache:
            obj._cache[self.name] = self.func(obj)
        return obj._cache[self.name]

    def __delete__(self, obj):
        if hasattr(obj, '_cache') and self.name in obj._cache:
            del obj._cache[self.name]

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

    @CachedProperty
    def area(self):
        print('Вычисление площади...')
        return 3.14159 * self.radius ** 2

c = Circle(10)
print(c.area)
print(c.area)
del c.area
print(c.area)
Вычисление площади...
314.159
314.159
Вычисление площади...
314.159

Пояснение: CachedProperty кэширует результат вычисления в словаре _cache. Удаление атрибута сбрасывает кэш. Это полезно для дорогих вычислений, которые не должны повторяться.

Пример
class Base:
    __slots__ = ('a',)

class Derived(Base):
    __slots__ = ('b',)

obj = Derived()
obj.a = 1
obj.b = 2
print(obj.a, obj.b)
# obj.c = 3  # AttributeError
1 2

Пояснение: Атрибуты a и b разрешены, c – нет. Если в дочернем классе не указать __slots__, будет создан __dict__.

Пример
class Proxy:
    def __init__(self, target):
        self._target = target

    def __getattr__(self, name):
        if name.startswith('_'):
            raise AttributeError('Доступ к защищенным элементам запрещен')
        attr = getattr(self._target, name)
        if callable(attr):
            def wrapper(*args, **kwargs):
                print(f'Вызов {name} с {args}, {kwargs}')
                return attr(*args, **kwargs)
            return wrapper
        return attr

class Computer:
    def __init__(self, cpu):
        self.cpu = cpu

    def compute(self, x):
        return x * 2

c = Computer('Intel')
p = Proxy(c)
print(p.compute(5))
print(p.cpu)
# p._internal  # AttributeError
Вызов compute с (5,), {}
10
Intel

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

Атрибуты объекта в Python - comments

En
Object attribute python (python)