Перехват __getitem__ вызовов атрибута объекта - PullRequest
0 голосов
/ 03 июля 2018

Вопрос: Как перехватить __getitem__ вызовы атрибута объекта?

Пояснение:

Итак, сценарий следующий. У меня есть объект, который хранит как диктованный объект в качестве атрибута. Каждый раз, когда вызывается метод __getitem__ этого атрибута, я хочу перехватить этот вызов и выполнить некоторую специальную обработку для выбранного элемента в зависимости от ключа. То, что я хочу, выглядело бы примерно так:

class Test:

    def __init__(self):
        self._d = {'a': 1, 'b': 2}

    @property
    def d(self, key):
        val = self._d[key]
        if key == 'a':
            val += 2
        return val
t = Test()
assert(t.d['a'] == 3) # Should not throw AssertionError

Проблема в том, что метод @property на самом деле не имеет доступа к ключу в вызове __getitem__, поэтому я вообще не могу проверить его для выполнения моего специального шага постобработки.

Важное замечание: я не могу просто создать подкласс MutableMapping, переопределить метод __getitem__ моего подкласса для выполнения этой специальной обработки и сохранить экземпляр подкласса в self._d. В моем действительном коде self._d уже является подклассом MutableMapping, и другим клиентам этого подкласса необходим доступ к неизмененным данным.

Спасибо за любую помощь!

Ответы [ 3 ]

0 голосов
/ 03 июля 2018

Вот довольно похожий подход к ShadowRanger'у. Он немного короче, так как он наследуется от dict напрямую, поэтому существует менее явное делегирование для определения.

class DictProxy(dict):
    def __getitem__(self, item):
        val = super().__getitem__(item)
        if item == 'a':
            val += 2
        return val

class Test:

    def __init__(self):
        self._d = {'a': 1, 'b': 2}

    @property
    def d(self):
        return DictProxy(self._d)

t = Test()
assert(t.d['a'] == 3) # Does not throw AssertionError anymore :)

С точки зрения поведения, это действительно зависит от вкуса. Нет ничего плохого в любом подходе.

РЕДАКТИРОВАТЬ: Спасибо ShadowRanger за указание, что это решение на самом деле копирует словарь каждый раз. Поэтому, вероятно, лучше использовать его явное решение делегирования, которое использует то же самое внутреннее словарное представление. Таким образом, это будет более эффективным, и если вы когда-нибудь захотите изменить свой прокси-сервер в будущем, чтобы он фактически влиял на исходную структуру данных, его подход значительно упростит внесение этих будущих изменений.

0 голосов
/ 03 июля 2018

Нет мелкого копирования, самое короткое и с возможностью модификации:

from collections import UserDict

class DictProxy(UserDict):
    def __init__(self, d):
        self.data = d

    def __getitem__(self, item):
        val = super().__getitem__(item)
        if item == 'a':
            val += 2
        return val
0 голосов
/ 03 июля 2018

Одним из решений будет Mapping, который проксирует основное отображение. Свойство d обернет базовое отображение self._d в прокси-оболочку и вернет его, и использование этого прокси-сервера будет демонстрировать необходимое поведение. Пример:

from collections.abc import Mapping

class DProxy(Mapping):
    __slots__ = ('proxymap',)
    def __init__(self, proxymap):
        self.proxymap = proxymap
    def __getitem__(self, key):
        val = self.proxymap[key]
        if key == 'a':
            val += 2
        return val
    def __iter__(self):
        return iter(self.proxymap)
    def __len__(self):
        return len(self.proxymap)

Как только вы это сделаете, ваш первоначальный класс может быть:

class Test:
    def __init__(self):
        self._d = {'a': 1, 'b': 2}

    @property
    def d(self):
        return DProxy(self._d)

Пользователи будут затем обращаться к экземплярам Test с test.d[somekey]; test.d вернул бы прокси, который затем изменил бы результат __getitem__, необходимый для somekey. Они могут даже хранить ссылки с помощью locald = test.d, а затем использовать locald, сохраняя при этом необходимые параметры прокси. Вы можете сделать его MutableMapping, если необходимо, но простой прокси на основе Mapping позволяет избежать сложности, когда целью является чтение значений, никогда не изменяя их через прокси.

Да, это создает новый экземпляр DProxy при каждом доступе к d; Вы можете кэшировать его, если хотите, но, учитывая, насколько прост __init__ класс DProxy, стоимость имеет смысл, только если квалифицированный доступ через атрибут d часто выполняется на самых горячих путях кода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...