← Назад к вопросам

Как __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)  # Безопасно

Сравнительная таблица

Характеристикаgetattributegetattr
Когда вызываетсяВСЕГДАТолько если атрибут не найден
ВызовДо поиска атрибутаПосле неудачного поиска
Для встроенных методовДаНет
ПроизводительностьМожет быть медленнееБыстрее (редко вызывается)
СложностьВышеНиже
Случаи использованияПерехват всех обращенийЗначения по умолчанию

Лучшие практики

# Используй __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__