т. (383) 381-86-26

Блог о создании вебсайтов

 

О переопределении "магических" методов в Python

 

В Python у объектов есть особый набор методов, которые принято называть "магическими". Нет, "авада кедавра" с их помощью выполнить нельзя, но за то можно сотворить много других интересных трюков.

Рассмотрим на примере Django один интересный случай. К примеру, есть модель Category, тогда получить все записи в БД, соответствующие этой модели можно так: quryset = Category.objects.all(). Отлично, но зачем всё это? А вот для чего -- queryset является экземпляром django.db.models.query.QuerySet и, допустим, что мы хотим вместо того, чтобы перебирать все объекты Category в queryset обращаться к ним по slug (в нашем случае slug является некоторым полем модели Category вот так: queryset.avtomoto. ох! И зачем? Не забивай себе голову. Просто читай дальше — тебе понравится.

Тогда что требуется для реализации желаемого поведения? Во-первых, что такое ".avtomoto"? Это просто атрибут. Но атрибут, который появится у объекта уже после его создания (а фактически он вообще не появится в __dict__ экземпляра объекта, но поведение будет именно как при доступе к атрибуту). Вспоминаем, что отвечает за контроль доступа к атрибутам в Python? Самый общим механизм -- дескрипторы. Но есть еще такие методы как __getattr__, __getattribute__.

Разница между ними скромная, но важная. __getattribute__ вызывается всякий раз, когда мы обращаемся к атрибуту и плохо выполненное переопределение может привести к серьезным проблемам, а __getattr__ только тогда, когда при обращении к атрибуту, он не был найден в __dict__ объекта или класса.

Теперь ясно, что раз ".avtomoto" просто атрибут, то следует воспользоваться __getattr__ и переопределить его. Хорошая идея. Но я хочу помочь вам сэкономить ваше время и скажу сразу, что ничего не выйдет. Просто потому "магические" методы невозможно переопределить на уровне экземпляра.

Написать a.__getattr__ = мой-новый-крутой-метод можно, но работать это не будет. Что делать? Да решение то в общем-то простое. А давайте сделаем небольшой прокси-класс?

class QuerysetProxy(object):
    """
      Хитрый класс, чтобы можно было внутри
      проксирующий "доступ" к атрибутам queryset
    """
    def __init__(self, queryset):
        self.queryset = queryset

    def __getattr__(self, name):
        try:
            # для того, чтобы не произошло перекрытий атрибутов
            # сначала проверяем есть ли такой атрибут уже
            # и если есть отдаем его.
            return self.queryset.__getattribute__(self, name)
        except:
            # В противном случае считаем, что
            # атрибут, к которому мы обращаемся,
            # является slug'ом категории
            try:
                cat = self.queryset.get(slug=name)
                return cat
            except ObjectDoesNotExist:
                raise AttributeError(name)

>>> q = QuerysetProxy(queryset)
>>> q.avadakedavra  # теперь работает!

Таким образом, можно не переопределяя магические методы у класса или экземпляра добиться их выполнения требующимся образом. Примеры достаточно фиктивны, но сама идея выросла из практической задачи.

Следите за нашими публикациями и вы узнаете ещё больше о мистических возможностях Python, его особенностях и интересных трюках, которые сделают из вас могущественного профессионала программирования на языке Python! =)

Подпишитесь на рассылку, будет интересно!