Зачем нужна проверка типа индекса при перегрузке __getitem__ для своих классов

Рейтинг: 3Ответов: 2Опубликовано: 12.01.2023

Много видел примеров перегрузки __getitem__ с помощью проверок isinstance на slice или int, а зачем это делают? Если arr[key] будет работать для slice и int или бросит исключение TypeError.

Ответы

▲ 3Принят

Вы правы в этом нет необходимости, если не нужно поддержать в качестве индекса другие типы

Пример:

class GetItem:
    def __init__(self, items):
        self.items = items

    def __getitem__(self, item):
        if isinstance(item, str):
            item = int(item)

        if isinstance(item, dict):
            item = item['value']

        if isinstance(item, tuple):
            item = slice(*item)

        if not isinstance(item, (int, slice)):
            raise TypeError(f'string indices сan be int, slice, tuple, str, dict, but not {type(item)}')

        return self.items[item]


print(GetItem('123')[0])
print(GetItem('123')[0:2])
print(GetItem('123')['1'])
print(GetItem('123')[0, 2])
print(GetItem('123')[dict(value=(1, 3))])
print(GetItem('123')[1.2345])

Результат:

1
12
2
12
23
TypeError: string indices сan be int, slice, tuple, str, dict, but not <class 'float'>

Стандартная ошибка говорит про целые числа:

TypeError: string indices must be integers
▲ 1

Когда у вас в методе __getitem__ идет обращение к списку или другому sequence-подобному объекту - без проблем, можно так и делать, не добавлять проверку типа индекса, а просто передавать его как индекс для внутреннего списка.

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

Как пример - представьте обертку над файловым объектом, которая позволит выбирать строки через индексы или слайсы, не загружая все строки файла в память. В этом случае не получится просто куда-то передать индекс, и оно само как-то нужные строки извлечет.

Пример реализации:

from itertools import islice
from pprint import pprint
from typing import Iterable, TextIO


class FileSlicer:
    def __init__(self, file: TextIO):
        self.file = file

    def get_line_by_index(self, index: int) -> str:
        self.file.seek(0)
        for i, line in enumerate(self.file):
            if i == index:
                return line
    
    def get_lines_by_slice(self, s: slice) -> Iterable[str]:
        self.file.seek(0)
        return islice(self.file, s.start, s.stop, s.step)

    def __getitem__(self, index):
        if isinstance(index, int):
            return self.get_line_by_index(index)
        elif isinstance(index, slice):
            return self.get_lines_by_slice(index)
        else:
            raise TypeError(f"Unknown index type: {type(index)}")


with open(__file__, "r") as file:
    slicer = FileSlicer(file)
    print(slicer[0])  # from itertools import islice
    print(slicer[5])  # class FileSlicer:

    pprint(list(slicer[::2]))  # Вывести список строк файла с четными индексами

Вывод:

from itertools import islice

class FileSlicer:

['from itertools import islice\n',
 'from typing import Iterable, TextIO\n',
 '\n',
 '    def __init__(self, file: TextIO):\n',
 '\n',
 '        self.file.seek(0)\n',
 '            if i == index:\n',
 '    \n',
 '        self.file.seek(0)\n',
 '\n',
 '        if isinstance(index, int):\n',
 '        elif isinstance(index, slice):\n',
 '        else:\n',
 '\n',
 'with open(__file__, "r") as file:\n',
 '    print(slicer[0])  # from itertools import islice\n',
 '\n']