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

Что будет если обратиться к неопределенному атрибуту классу?

2.0 Middle🔥 81 комментариев
#Python Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Доступ к неопределённому атрибуту класса

Ответ: Будет AttributeError

При попытке обратиться к неопределённому атрибуту класса Python выбросит исключение AttributeError. Это один из фундаментальных механизмов Python, который стоит глубоко понимать.

Простой пример

class Dog:
    name = "Generic Dog"

dog = Dog()
print(dog.name)       # "Generic Dog" — работает
print(dog.age)        # AttributeError: 'Dog' object has no attribute 'age'

При обращении к dog.age Python:

  1. Проверяет __dict__ объекта
  2. Проверяет __dict__ класса
  3. Проверяет родительские классы (MRO)
  4. Вызывает __getattr__ если определён
  5. Выбрасывает AttributeError если ничего не найдено

Порядок поиска атрибутов (MRO)

class Animal:
    species = "Unknown"

class Dog(Animal):
    name = "Dog"

dog = Dog()

# Порядок поиска:
# 1. dog.__dict__        (атрибуты экземпляра)
# 2. Dog.__dict__        (атрибуты класса)
# 3. Animal.__dict__     (атрибуты родителя)
# 4. object.__dict__     (встроенные)

# Проверить MRO:
print(Dog.__mro__)
# (<class 'Dog'>, <class 'Animal'>, <class 'object'>)

Как это работает на самом деле

Процесс поиска в Python очень точный:

class Parent:
    value = "parent"

class Child(Parent):
    def __init__(self):
        self.instance_value = "instance"

obj = Child()

# Шаг 1: проверяем экземпляр
print(obj.__dict__)  # {'instance_value': 'instance'}
print(obj.instance_value)  # "instance" ✓

# Шаг 2: проверяем класс
print(Child.value)  # AttributeError? Нет!
print(Child.__dict__)  # значения класса
print(obj.value)  # "parent" ✓

# Шаг 3: проверяем родителей (MRO)
print(obj.missing_attr)  # AttributeError: 'Child' object has no attribute 'missing_attr'

Контроль этого поведения

1. Метод getattr

Вызывается когда атрибут не найден. Это позволяет создавать "ленивые" атрибуты:

class FlexibleObject:
    def __getattr__(self, name):
        # Вызывается только если атрибут не найден
        print(f"Запрошен атрибут: {name}")
        return f"Значение для {name}"

obj = FlexibleObject()
print(obj.foo)  # Запрошен атрибут: foo
                # Значение для foo
print(obj.bar)  # Запрошен атрибут: bar
                # Значение для bar

Пример с fallback значениями:

class Config:
    def __init__(self, defaults: dict):
        self.defaults = defaults
    
    def __getattr__(self, name):
        """Если атрибута нет, возвращаем из defaults"""
        if name in self.defaults:
            return self.defaults[name]
        raise AttributeError(f"Config has no attribute {name}")

config = Config({"debug": True, "port": 8000})
print(config.debug)  # True
print(config.host)   # AttributeError: Config has no attribute host

2. Метод getattribute

Вызывается ВСЕГДА перед поиском атрибута. Более мощный, но опасный:

class Logging:
    def __init__(self):
        self.value = 42
    
    def __getattribute__(self, name):
        # Вызывается для КАЖДОГО доступа к атрибуту
        print(f"Доступ к атрибуту: {name}")
        return super().__getattribute__(name)

obj = Logging()
print(obj.value)
# Вывод:
# Доступ к атрибуту: value
# 42

Важно: бесконечная рекурсия!

# ПЛОХО — бесконечная рекурсия
class Bad:
    def __getattribute__(self, name):
        return self.some_value  # Вызывает __getattribute__ снова!

# ПРАВИЛЬНО — используй super()
class Good:
    def __init__(self):
        self.value = 42
    
    def __getattribute__(self, name):
        print(f"Читаем: {name}")
        return super().__getattribute__(name)

3. Метод setattr

Контролирует установку атрибутов:

class Validated:
    def __setattr__(self, name, value):
        if name == "age" and not isinstance(value, int):
            raise TypeError("Age must be integer")
        super().__setattr__(name, value)

obj = Validated()
obj.age = 25      # OK
obj.age = "25"    # TypeError: Age must be integer

Практические примеры

Пример 1: Ленивая инициализация

class LazyProperty:
    def __init__(self, func):
        self.func = func
        self.name = func.__name__
    
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        
        # Вычисляем значение в первый раз
        value = self.func(obj)
        # Сохраняем в __dict__ чтобы __getattr__ не вызывался больше
        setattr(obj, self.name, value)
        return value

class User:
    def __init__(self, name: str):
        self.name = name
    
    @LazyProperty
    def expensive_calculation(self):
        print("Вычисляем...")
        import time
        time.sleep(2)
        return "Результат"

user = User("Alice")
print(user.expensive_calculation)  # Вычисляем... Результат
print(user.expensive_calculation)  # Результат (без вычисления)

Пример 2: Proxy/wrapper

class DatabaseProxy:
    def __init__(self, real_db):
        self._db = real_db
    
    def __getattr__(self, name):
        # Логируем все обращения
        print(f"Вызов: {name}")
        return getattr(self._db, name)

class Database:
    def query(self, sql):
        return "результат"

db = DatabaseProxy(Database())
db.query("SELECT *")  # Логирует: Вызов: query

Пример 3: Динамические атрибуты (как в Django models)

class Model:
    def __init__(self, **kwargs):
        self._data = kwargs
    
    def __getattr__(self, name):
        if name.startswith('_'):
            raise AttributeError(f"No attribute {name}")
        
        if name in self._data:
            return self._data[name]
        
        raise AttributeError(f"Model has no field {name}")
    
    def __setattr__(self, name, value):
        if name.startswith('_'):
            super().__setattr__(name, value)
        else:
            self._data[name] = value

user = Model(name="Alice", age=30)
print(user.name)  # "Alice"
print(user.missing)  # AttributeError

Отладка: hasattr и getattr

class User:
    name = "Default"

user = User()

# Проверяем наличие атрибута ДО обращения
if hasattr(user, 'age'):
    print(user.age)
else:
    print("Нет атрибута age")

# Безопасное получение с дефолтом
age = getattr(user, 'age', 0)  # 0 если нет age
print(age)  # 0

# Список всех атрибутов
print(dir(user))
print(user.__dict__)

Частые ошибки

# ОШИБКА 1: Забыли инициализировать
class Point:
    def __init__(self):
        self.x = 0
        # Забыли y!

p = Point()
print(p.y)  # AttributeError

# ОШИБКА 2: Опечатка в имени атрибута
user.name = "Alice"
print(user.name)  # AttributeError (опечатка)

# ОШИБКА 3: Бесконечная рекурсия в __getattribute__
class Bad:
    def __getattribute__(self, name):
        return self.value  # Вызывает себя снова!

Итог

AttributeError — это нормальное исключение. Python использует его как сигнал что:

  • Атрибут не существует
  • Класс не определён
  • Опечатка в имени

Понимание MRO, getattr, getattribute — это ключ к пониманию метапрограммирования в Python.