Давайте посмотрим на несколько примеров использования __getattr__ и __getattribute__.
__getattr__
Магический метод "__getattr__" будет вызываться каждый раз, когда вы обратитесь к атрибуту, который еще не был определен. В следующем примере класс "Count" не реализует метод "__getattr__". Теперь, когда я пытаюсь получить доступ к атрибутам "mymin" и "mymax" все работает нормально. Но когда я пытаюсь получить доступ к атрибуту "mycurrent" - Python выдаст мне ошибку: "AttributeError: 'Count' object has no attribute 'mycurrent'"
Код:
Теперь у моего класса Count есть метод "__getattr__". Теперь, когда я пытаюсь получить доступ к "mycurrent" python возвращает мне все, что я реализовал в своем "__getattr__". В моем примере каждый раз, когда я пытаюсь вызвать атрибут, которого не существует, python создает этот атрибут и устанавливает ему целочисленное значение 0.
__getattribute__
Теперь давайте посмотрим на метод "__getattribute__". Если в вашем классе есть метод "__getattribute__", python вызывает этот метод для каждого атрибута независимо от того, существует он или нет. Так зачем же нам нужен метод "__getattribute__"? Одна из веских причин заключается в том, что вы можете запретить доступ к атрибутам и сделать их более безопасными, как будет показано в следующем примере.
Всякий раз, когда кто-то пытается получить доступ к атрибутам, начинающимся с подстроки 'cur' python вызывает исключение "AttributeError". В противном случае он возвращает этот атрибут.
Важно: чтобы избежать бесконечной рекурсии в "__getattribute__", его реализация всегда должна вызывать метод базового класса с тем же именем для доступа к любым необходимым ему атрибутам. Например:
"object.__getattribute__(self, name)"
или
"super().__getattribute__(item)"
Если ваш класс реализует магические методы "__getattr__" и "__getattribute__", то сначала вызывается "__getattribute__". Но если "__getattribute__" вызывает исключение "AttributeError", то исключение будет проигнорировано и будет вызван метод "__getattr__".