← Назад к вопросам
Как __getattr__ и __getattribute__ влияют на получение атрибута?
2.7 Senior🔥 111 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как getattr и getattribute влияют на получение атрибута
Это два магических метода в Python, которые контролируют доступ к атрибутам объекта. Они работают на разных уровнях и часто вызывают путаницу. Ключевое отличие — в моменте вызова и порядке выполнения.
getattribute — первая линия защиты
__getattribute__ вызывается ВСЕГДА при обращении к любому атрибуту:
class MyClass:
def __init__(self):
self.value = 42
def __getattribute__(self, name):
print(f"__getattribute__ вызван для: {name}")
return super().__getattribute__(name)
obj = MyClass()
print(obj.value)
# Вывод:
# __getattribute__ вызван для: value
# 42
Важно: __getattribute__ вызывается даже для встроенных атрибутов и методов:
class TrackedClass:
def __init__(self):
self.x = 10
def __getattribute__(self, name):
print(f"Попытка доступа: {name}")
return super().__getattribute__(name)
obj = TrackedClass()
obj.__class__ # Вызовет __getattribute__
obj.__dict__ # Вызовет __getattribute__
obj.x # Вызовет __getattribute__
getattr — запасной вариант
__getattr__ вызывается ТОЛЬКО когда атрибут НЕ найден обычным способом:
class MyClass:
def __init__(self):
self.value = 42
def __getattr__(self, name):
print(f"__getattr__ вызван для: {name}")
return f"Атрибут {name} не найден"
obj = MyClass()
print(obj.value) # Найдётся в __dict__, __getattr__ НЕ вызовется
print(obj.missing) # НЕ найдётся, __getattr__ ВЫЗОВЕТСЯ
# Вывод:
# 42
# __getattr__ вызван для: missing
# Атрибут missing не найден
Порядок выполнения
class OrderExample:
def __init__(self):
self.existing = "есть"
def __getattribute__(self, name):
print(f"1. __getattribute__ для {name}")
try:
return super().__getattribute__(name)
except AttributeError:
print(f"2. Атрибут {name} не найден в __getattribute__")
raise
def __getattr__(self, name):
print(f"3. __getattr__ для {name}")
return f"Значение по умолчанию для {name}"
obj = OrderExample()
print("\n--- Существующий атрибут ---")
print(obj.existing)
print("\n--- Несуществующий атрибут ---")
print(obj.missing)
# Вывод:
# --- Существующий атрибут ---
# 1. __getattribute__ для existing
# есть
#
# --- Несуществующий атрибут ---
# 1. __getattribute__ для missing
# 2. Атрибут missing не найден в __getattribute__
# 3. __getattr__ для missing
# Значение по умолчанию для missing
Практические примеры
Пример 1: Кэширование атрибутов
class CachedProperty:
def __init__(self):
self.cache = {}
def __getattribute__(self, name):
cache = super().__getattribute__('cache')
if name in cache:
print(f"Возвращаю {name} из кэша")
return cache[name]
value = super().__getattribute__(name)
if not name.startswith('_'):
cache[name] = value
print(f"Закэшировал {name}")
return value
obj = CachedProperty()
obj.data = [1, 2, 3]
print(obj.data) # Закэшировал data
print(obj.data) # Возвращаю data из кэша
Пример 2: Прокси к словарю
class DictProxy:
"""Обращение к атрибутам как к словарю"""
def __init__(self, data):
super().__setattr__('_data', data)
def __getattr__(self, name):
data = super().__getattribute__('_data')
if name in data:
return data[name]
raise AttributeError(f"Ключ {name} не найден")
def __setattr__(self, name, value):
if name == '_data':
super().__setattr__(name, value)
else:
data = super().__getattribute__('_data')
data[name] = value
proxy = DictProxy({'name': 'Alice', 'age': 30})
print(proxy.name) # Alice
print(proxy.age) # 30
proxy.city = 'Moscow'
print(proxy._data) # {'name': 'Alice', 'age': 30, 'city': 'Moscow'}
Пример 3: Ленивая загрузка
class LazyProperty:
"""Атрибут вычисляется при первом обращении"""
def __init__(self):
self._expensive = None
def __getattr__(self, name):
if name == 'expensive_data':
if self._expensive is None:
print("Вычисляю дорогостоящее значение...")
import time
time.sleep(1) # Имитация долгой операции
self._expensive = "Результат вычисления"
return self._expensive
raise AttributeError(f"{name} не найден")
obj = LazyProperty()
print(obj.expensive_data) # Вычисляю...
print(obj.expensive_data) # Быстро вернёт кэшировано значение
Пример 4: Отслеживание изменений
class TrackedObject:
"""Отслеживает доступ к атрибутам"""
def __init__(self):
self.access_log = []
def __getattribute__(self, name):
if name not in ['access_log']:
log = super().__getattribute__('access_log')
log.append(f"Доступ к {name}")
return super().__getattribute__(name)
def __getattr__(self, name):
return f"Атрибут {name} не существует"
obj = TrackedObject()
obj.x = 10
obj.y
obj.x
print(obj.access_log)
# ['Доступ к x', 'Доступ к y', 'Доступ к x']
Опасность: бесконечная рекурсия
# ОПАСНО: бесконечная рекурсия
class BadClass:
def __getattribute__(self, name):
return self.__dict__[name] # Вызовет __getattribute__ снова!
# ПРАВИЛЬНО: используй super()
class GoodClass:
def __getattribute__(self, name):
return super().__getattribute__(name) # Безопасно
Сравнительная таблица
| Характеристика | getattribute | getattr |
|---|---|---|
| Когда вызывается | ВСЕГДА | Только если атрибут не найден |
| Вызов | До поиска атрибута | После неудачного поиска |
| Для встроенных методов | Да | Нет |
| Производительность | Может быть медленнее | Быстрее (редко вызывается) |
| Сложность | Выше | Ниже |
| Случаи использования | Перехват всех обращений | Значения по умолчанию |
Лучшие практики
# Используй __getattr__ для простых случаев
class Config:
def __init__(self):
self.debug = False
def __getattr__(self, name):
# Значения по умолчанию
return os.getenv(name.upper(), None)
config = Config()
print(config.debug) # False (существует)
print(config.db_host) # Из переменной окружения
# Используй __getattribute__ только когда НЕОБХОДИМО перехватить все обращения
class Proxy:
def __init__(self, obj):
super().__setattr__('_obj', obj)
def __getattribute__(self, name):
obj = super().__getattribute__('_obj')
print(f"Доступ к {obj.__class__.__name__}.{name}")
return getattr(obj, name)
Вывод
- getattribute — всегда вызывается, используй для полного контроля
- getattr — используй для обработки отсутствующих атрибутов
- Предпочитай getattr для простоты и производительности
- Будь осторожен с getattribute — легко создать бесконечную рекурсию
- Всегда используй
super().__getattribute__()вместо прямого доступа к__dict__