info@severcart.org
Русский English

Магические методы Python и __getattr__

10 января 2018 г.    Python

Магические методы - это общий термин, относящийся к «специальным» методам классов Python. Для них нет единого определения, поскольку их применение разнообразно. Пример нескольких распространенных и известных магических методов:

  • __init__ - выполняет функцию инициализатора объекта (иногда неправильно называемого конструктором)
  • __str__ - строковое представление объекта
  • __add__- перегрузка оператора +.

Что общего у всех этих методов? Очевидно, что все они начинаются и заканчиваются двойными подчеркиваниями (__). Но помимо этого, что делает их «магическими методами», это то, что они вызываются как-то «специфически». Эти методы не вызываются вручную, Python это делает за нас. Например, при выполнении str(obj), выполняется вызов obj.__ str __ ().

Есть множество магических методов, но в этой статье будет рассмотрены __getattr__ и __getattribute__.

__getattr__

Этот метод позволит вам «улавливать» ссылки на атрибуты, которые не существуют в объекте. Рассмотрим простой пример, чтобы понять, как это работает:

class Dummy(object):
    pass
d = Dummy()
d.does_not_exist  # Сбой с AttributeError

В этом примере доступ к атрибуту завершается с ошибкой (вызов исключения AttributeError), поскольку атрибут does_not_exist не существует.

Используя магический метод __getattr__можно перехватить поиск несуществующего атрибута и выполнить некоторое действие:

class Dummy(object):
    def __getattr__(self, attr):
        return attr.upper()
d = Dummy()
d.does_not_exist 
d.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 '1111111111'
d = Dummy()
d.value = "Python"
print(d.value)  # "1111111111"

В этом примере объект 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, чтобы добавить к нему немого синтаксического сахара:

a_tuple = ("z", 3, "Python", -1)

К элементам обращаемся по-другому:

print(a_tuple._1) // "z"
print(a_tuple._3) // "Python"

Каждый элемент в кортеже получает доступ как атрибут, причем первым элементом является атрибут _1, второй _2 и т. д.

Можно легко расширить общий кортеж Python, чтобы соответствовать этому поведению, для этого напишем очень простой код:

class Tuple(tuple):
    def __getattr__(self, name):
        def _int(val):
            try:
                return int(val)
            except ValueError:
                return False

        if not name.startswith('_') or not _int(name[1:]):
            raise AttributeError("'tuple' object has no attribute '%s'" % name)
        index = _int(name[1:]) - 1
        return self[index]


t = Tuple(['z', 3, 'Python', -1])
print(t._1)  # 'z'
print(t._2)  # 3
print(t._3)  # 'Python'

Результаты выполнения скрипта:

t = Tuple(['z', 3, 'Python', -1])

assert t._1 == 'z'
assert t._2 == 3
assert t._3 == 'Python'
assert t._4 == -1

В этом примере можно увидеть, как улавливаются отсутствующие атрибуты с помощью __getattr__, но если этот атрибут не найден в формате _n (где n - целое число), производится вызов исключения AttributeError вручную.

Вывод

Магические методы - отличный механизм расширения базовых функций классов и объектов Python и обеспечения более интуитивно понятных интерфейсов. __getattr__ предоставляет возможности динамической генерации отсутствующих атрибутов. Но будьте осторожны с __ getattribute __, т. к для правильной реализации понадобится хитрость, при этом не теряя Python атрибуты в пустую.