Можно ли переопределить __getitem__ на уровне экземпляра в Python?

С помощью следующего кода:

import types

class Foo():
    def __getitem__(self, x):
        return x

def new_get(self, x):
    return x + 1

x = Foo()
x.__getitem__ = types.MethodType(new_get, x)

x.__getitem__(42) вернет 43, а x[42] вернет 42.

Есть ли способ переопределить __getitem__ на уровне экземпляра в Python?

Ответ 1

Это, к сожалению, и, что удивительно, недопустимо:

Для пользовательских классов неявные вызовы специальных методов гарантированно работает правильно, если определено для типа объекта, а не в словарь экземпляров объектов.

Источник: https://docs.python.org/3/reference/datamodel.html#special-lookup

Ответ 2

Не делай этого...

Протокол поиска предметов всегда восстанавливает __getitem__ из класса, он даже не просматривает экземпляр __dict__. На самом деле это хорошая вещь, так как в противном случае экземпляры одного и того же класса будут концептуально отличаться друг от друга, что противоречит всей идее классов.

Но...

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

Из-за того, что dunder ищется непосредственно на уровне класса, логика поиска предметов также должна обновляться на уровне класса.

Таким образом, решение состоит в том, чтобы обновить __getitem__, чтобы он сначала искал функцию уровня экземпляра в экземпляре __dict__.

Вот пример, в котором мы создаем подклассы dict для разрешения уровня экземпляра __getitem__.

class Foo(dict):
    def __getitem__(self, item):
        if "instance_getitem" in self.__dict__:
            return self.instance_getitem(self, item)
        else:
            super().__getitem__(item)

foo = Foo()
foo.instance_getitem = lambda self, item: item + 1
print(foo[1]) # 2