Управление атрибутами объектов: от основ до продвинутых техник
Атрибуты объекта в 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__.
Цель: реализация динамических атрибутов, прокси-объектов, отладка доступа.
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
Пояснение: Прокси перехватывает обращения к атрибутам, логируя вызовы методов и блокируя защищенные атрибуты.