В примере выше мы создаём класс Users, который, по сути, скрывает внутри обычный список пользователей.
Для получения первого и последнего пользователя потребуется писать
self.users[0] и self.users[-1]. Такие конструкции раскрывают внутреннее устройство класса, загрязняют код и делают его менее читабельным. Мы заставляем внешнего потребителя класса разбираться в его внутреннем устройстве.Один из вариантов решения — декоратор
@property и создание нужных атрибутов. Но для большого количества атрибутов будет спагетти из однотипных property.Другой приём — использование динамических атрибутов. Создаём словарь, где ключом будет имя, а значением индекс в списке пользователей. Метод
getattr проверяет наличие искомого атрибута среди ключей словаря, если такой есть — то возвращает пользователя по индексу из словаря.Детали решения
Почему первый
print выведет U1?Вся суть в том, как python ищет атрибуты. Атрибут ищется в классе. В нашем случае атрибута
first нет. Далее производится поиск по иерархии наследования, и тоже безуспешно. Только в этом случае вызывается магический метод __getattr__, и параметром ему передаётся имя аргумента в виде строки "first". По словарю first превратится в нулевой индекс из self.users, что соответствует выводу U1.Почему второй
print выведет new_user?В 17 строке явно создаем атрибут
first и присваиваем ему значение new_user. В 18 строке в классе будет найден атрибут first и метод getattr не будет вызываться.Обратите внимание ещё на одну интересность. Вывод
self.users.users[0] всё ещё даст U1. Получается некая несогласованность. Мы переопределили first, и это уже не первый пользователь.Поставьте 🔥, если интересно возможное решение такой проблемы.