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

Каким будет правильный способ для прокси-класса реализовать методы __getitem__, __iter__ и __len__ динамически в зависимости от их доступности во входной последовательности. Вот моя попытка сделать это (я называю свои последовательности «поставщиками», а функция - «преобразованием»):

class TransformProviderProxy:

    def __init__(self, input_provider, transform):
        self.input_provider = input_provider
        self.transform = transform

    def __iter__(self):
        # Use generator expressions to produce a new iterator when requested
        return (self.transform(data_sample) for data_sample in self.input_provider)

    def __getattr__(self, name):
        if name == "__getitem__" and hasattr(self.input_provider, "__getitem__"):
            return self._getitem_impl
        elif name == "__len__" and hasattr(self.input_provider, "__len__"):
            return self._len_impl
        else:
            return None

    def _getitem_impl(self, index):
        return self.transform(self.input_provider[index])

    def _len_impl(self):
        return len(self.input_provider)

Однако это не удается, потому что кажется, что python обходит __getattr__ при поиске __getitem__. Как правильно это сделать?

В качестве бонуса, есть ли способ сделать это, когда мой прокси-класс динамически наследует от collections.abc.Sequence или collections.abc.Iterable в зависимости от входной последовательности?

PS: Я также думал о том, чтобы просто реализовать __getitem__ в любом случае и полагаться на перехват TypeError при попытке вызвать его в неиндексируемой входной последовательности, но я хотел бы иметь возможность проверить, индексируется ли мой прокси-класс, ища наличие __getitem__ без явного вызова его с индексом.

2
Julien Hirel 28 Июл 2017 в 19:16

1 ответ

Лучший ответ

Просто внедрите __getitem__ и __len__. Он завершится ошибкой с соответствующим сообщением об ошибке, если input_provider не реализует следующие операции:

class TransformProviderProxy:

    def __init__(self, input_provider, transform):
        self.input_provider = input_provider
        self.transform = transform

    def __iter__(self):
        # Use generator expressions to produce a new iterator when requested
        return (self.transform(data_sample) for data_sample in self.input_provider)

    def __getitem__(self, index):
        return self.transform(self.input_provider[index])

    def __len__(self):
        return len(self.input_provider)

Как пример:

>>> t = TransformProviderProxy(2, lambda x: x+2)
>>> t[1]
TypeError: 'int' object is not subscriptable

>>> t = TransformProviderProxy([1,2,3,4], lambda x: x+2)
>>> t[1]
4
0
MSeifert 28 Июл 2017 в 19:24
Я отредактировал свой вопрос незадолго до того, как вы опубликовали свой ответ, извините (вы были слишком быстры для меня!). Я думал об этом, но хотел иметь возможность проверить, является ли мой класс индексируемым (в идеале, проверив, является ли он экземпляром collections.abc.Sequence), не пытаясь явно его проиндексировать. Может, мне просто следует признать, что твой путь более питонический?
 – 
Julien Hirel
28 Июл 2017 в 19:34
Да, EAFP (проще просить прощения, чем разрешения) считается более "pythonic", чем LBYL (посмотрите, прежде чем прыгать). Однако вы можете проверить сторонний модуль wrapt а это ObjectProxy. Это может содержать какое-то (уже построенное) решение вашей непосредственной проблемы. Извините, я не видел ваш последний абзац раньше ... :(
 – 
MSeifert
28 Июл 2017 в 19:40