Магические методы 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) за вычитку и предложения 🙇🏻.