
Магические методы Python и __getattr__
На второй неделе нашего курса Наука о данных с Python мы говорим о важности хороших, чистых и интуитивно понятных интерфейсов и о том, насколько прекрасна объектно-ориентированная модель Python (тема для другого поста). Пока мы объясняем это, мы вводим несколько полезных магических методов, таких как __len__, __str__, __repr__ и т. Д. Среди них мы также показываем и __getattr__, и __getattribute__, и кажется, что это всегда интересно нашим ученикам, поэтому мы подумали, что должны поделиться им. с миром.
Учебник по магическим методам
Магические методы - это широкий и общий термин, который относится к «специальным» методам в классе Python. Нет единого определения для всех из них, поскольку их использование разнообразно. Например, несколько распространенных и хорошо известных магических методов включают:
__init__, который служит инициализатором объекта (иногда его неправильно называют конструктором)__str__, который обеспечивает «строковое представление» вашего объекта.__add__, что позволяет «перегружать» оператор +.
Что общего у всех этих методов? Ну, очевидно, все они начинаются и заканчиваются двойным подчеркиванием (__). Но помимо этого, их делает «волшебными методами» то, что они вызываются каким-то «особым образом». Мы не вызываем эти методы вручную; Это делает Python. Например, мы не делаем obj.__str__(), мы делаем str(obj).
Существует МНОГО волшебных методов, но, как я сказал в начале, я хочу сосредоточиться на __getattr__ и __getattribute__.
__getattr__
Начнем с __getattr__. Этот метод позволит вам «перехватывать» ссылки на атрибуты, которых нет в вашем объекте. Давайте посмотрим на простой пример, чтобы понять, как это работает:
class Dummy(object):
pass
d = Dummy()
d.does_not_exist # Fails with AttributeError

В этом примере не удается получить доступ к атрибуту (с AttributeError), потому что атрибут does_not_exist не существует.
Но с помощью __getattr__ волшебного метода мы можем перехватить этот поиск несуществующего атрибута и сделать что-нибудь, чтобы он не потерпел неудачу:
class Dummy(object):
def __getattr__(self, attr):
return attr.upper()
d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one # 'WHAT_ABOUT_THIS_ONE'

Но если атрибут существует, __getattr__ не будет вызываться:
class Dummy(object):
def __getattr__(self, attr):
return attr.upper()
d = Dummy()
d.value = "Python"
print(d.value) # "Python"

__getattribute__
__getattribute__ похож на __getattr__ с той важной разницей, что __getattribute__ будет перехватывать КАЖДЫЙ поиск атрибутов, не имеет значения, существует атрибут или нет. Позвольте мне показать вам простой пример:
class Dummy(object):
def __getattribute__(self, attr):
return 'YOU SEE ME?'
d = Dummy()
d.value = "Python"
print(d.value) # "YOU SEE ME?"

В этом примере объект d уже имеет атрибут value. Но когда мы пытаемся получить к нему доступ, мы не получаем исходное ожидаемое значение («Python»); мы просто получаем то, что __getattribute__ вернули. Это означает, что мы практически потеряли атрибут value; он стал «недоступным».
Если вам когда-нибудь понадобится __getattribute__ для моделирования чего-то похожего на __getattr__, вам придется проделать более сложную обработку Python:
class Dummy(object):
def __getattribute__(self, attr):
__dict__ = super(Dummy, self).__getattribute__('__dict__')
if attr in __dict__:
return super(Dummy, self).__getattribute__(attr)
return attr.upper()
d = Dummy()
d.value = "Python"
print(d.value) # "Python"
print(d.does_not_exist) # "DOES_NOT_EXIST"
Более реалистичный пример
Нередко случайный выбор каждого атрибута не является обычным явлением. Обычно мы используем __getattr__ с четкой целью: всякий раз, когда мы хотим предоставить некоторый динамический доступ к нашим объектам. Хорошим примером может быть расширение базового кортежа Python, чтобы добавить в него немного Scala (мне очень нравится Scala 🙌). В Scala кортежи создаются очень похоже на Python:
val a_tuple = ("z", 3, “Python”, -1)
Но доступ к ним осуществляется по-другому:
println(a_tuple._1) // “z” println(a_tuple._3) // “Python”
К каждому элементу в кортеже обращаются как к атрибуту, причем первый элемент является атрибутом _1, вторым _2 и так далее.
Мы можем легко расширить наш общий кортеж Python, чтобы он соответствовал этому поведению, код действительно прост:
Вы не знали, что можете расширять встроенные коллекции Python 😱? Ознакомьтесь с нашим Курсом Data Science with Python!
В этом примере вы можете увидеть, как мы отлавливаем отсутствующие атрибуты с помощью __getattr__, но если этот атрибут не в форме _n (где n - целое число), мы просто поднимаем AttributeError вручную.
Заключение
Магические методы - отличный механизм для расширения основных функций классов и объектов Python и обеспечения более интуитивно понятных интерфейсов. Вы можете обеспечить динамический поиск атрибутов с __getattr__ для тех недостающих атрибутов, которые вы хотите перехватить. Но будьте осторожны с __getattribute__, потому что может быть сложно правильно реализовать без потери атрибутов в пустоте Python.
Особая благодарность Fabrício Calado (@fabricalado) за вычитку и предложения 🙇🏻.