Python: возможно ли иметь метод класса, который действует на срез класса?

Я создаю контейнерный класс на Python, который либо наследуется от list, либо просто реализует все стандартные методы списка (мне все равно, какие именно).

Как мне создать метод, который будет воздействовать только на элементы, возвращаемые из среза? Мне удалось создать метод, который будет воздействовать на весь контейнер (см. ниже), но я не могу понять, как воздействовать только на срез.

Я использую python 2.7.6 и использую from __future__ import print_function, division во всем своем коде.

Пример кода:

from __future__ import print_function, division
import itertools

class MyContainerClass(list):
    """ For now, I'm inheriting from list. Is this my problem? """
    def __init__(self, data_array):
        list.__init__(self, data_array)

    def __getitem__(self, *args):

        arg = args[0]

        # specific indices   MyContainerClass[0, 3, 5] => indexes 0, 3, and 5
        if isinstance(arg, (list, tuple)) and not isinstance(arg[0], bool):
            return [list.__getitem__(self, _i) for _i in arg]

        # standard slice notation
        elif isinstance(arg, slice):
            return list.__getitem__(self, arg)

        # Using an array mask   MyContainerClass[[True, False, False,...,True]] => index 0 and -1]
        elif isinstance(arg[0], bool):  # or isinstance(arg, np.ndarray):
            return list(itertools.compress(self, arg))

        else:
            # I'll eventually replace this with and exception raise.
            return 'error'

    def my_method(self):
        """
        Will act on entire list, but I want it to act on only what's
        returned by the slice (which may be the entire list in some cases).
        """
        return "a, ".join([str(_i) for _i in self])

Вот пример использования, которое я хотел бы:

>>> data = MyContainerClass([1, 2, 3, 4, 5, 6, 7])
>>> data[5:]
[6, 7]
>>> data.my_method()           # This works as expected
"1a, 2a, 3a, 4a, 5a, 6a, 7"
>>> data[0:3].my_method()      # Doesn't work
"1a, 2a, 3"                    # but should return this


Похоже, теперь все работает. Большое спасибо, ребята! Вот что у меня получилось:

from __future__ import print_function, division
import itertools

class MyContainerClass(list):
    """
    """
    def __init__(self, array):
        if isinstance(array, int):
            list.__init__(self, [array])
        else:
            list.__init__(self, array)

    def __getitem__(self, arg):
        # Standard Slice notation
        if isinstance(arg, slice):
            retval = super(MyContainerClass, self).__getitem__(arg)

        # specific indices
        elif isinstance(arg, (list, tuple)) and not isinstance(arg[0], bool):
            retval = [list.__getitem__(self, _i) for _i in arg]

        # a single specific index
        elif isinstance(arg, int):
            retval = list.__getitem__(self, arg)

        # an array mask of T/F values
        elif isinstance(arg[0], bool):      # or isinstance(arg, np.ndarray):
            retval = list(itertools.compress(self, arg))

        # raise an error on unknown
        else:
            raise SyntaxError("Unknown notation for list slice or index")

        retval = type(self)(retval)
        return retval

    def __getslice__(self, i, j):
        # Python 2 built-in types only
        return self.__getitem__(slice(i, j))

    def my_method(self):
        return "a, ".join([str(_i) for _i in self])

И действует как:

>>> data = MyContainerClass([1, 2, 3, 4, 5, 6, 7])
>>> mask = [True, True, False, True, False, False, False]
>>> print(data)
[1, 2, 3, 4, 5, 6, 7]
>>> print(type(data[5:]))
<class '__main__.MyContainerClass'>
>>> print(data.my_method())
1a, 2a, 3a, 4a, 5a, 6a, 7
>>> print(data[0:5].my_method())
1a, 2a, 3a, 4a, 5
>>> print(data[1, 5, 2].my_method())
2a, 6a, 3
>>> print(data[mask].my_method())
1a, 2a, 4
>>> print(data[2].my_method())
3

person dthor    schedule 26.08.2014    source источник
comment
Будет ли срез снова экземпляром этого класса? Если вы просто наследуете этот метод от list, то, вероятно, нет. И в этом проблема: срез — это list, у которого нет этого метода. В качестве альтернативы, почему бы просто не сделать это функцией, а не методом?   -  person tobias_k    schedule 26.08.2014
comment
Может быть, это может представлять интерес: stackoverflow.com/a/16033058/2363712?   -  person Sylvain Leroux    schedule 26.08.2014
comment
Это Python 2 или Python 3?   -  person Martijn Pieters    schedule 26.08.2014
comment
@tobias_k: я бы хотел, чтобы срез был экземпляром класса, да. И это должно решить мою проблему. Что касается метода функции v: метод более полезен при разработке грандиозного проекта. То, что вы видите здесь, является лишь упрощенным примером :-)   -  person dthor    schedule 26.08.2014
comment
@SylvainLeroux: Фактические классы, которые я пишу, используют getitem, чтобы я мог принимать нотацию среза, маску массива или список индексов для возврата элементов. Как я упоминал в другом комментарии, похоже, мне просто нужно выяснить, как заставить getitem возвращать экземпляр MyContainerClass, а не список.   -  person dthor    schedule 26.08.2014
comment
@MartijnPieters Python 2.7 с __future__ import print_function, division. Я отредактировал сообщение, чтобы отразить это.   -  person dthor    schedule 26.08.2014
comment
Не могли бы вы показать нам, как ваш __getitem__ выглядит сейчас?   -  person tobias_k    schedule 26.08.2014
comment
@tobias_k: Поза обновлена.   -  person dthor    schedule 26.08.2014


Ответы (1)


Вам нужно будет убедиться, что ваш __getitem__ снова возвращает ваш тип:

class MyContainerClass(list):
    """ For now, I'm inheriting from list. Is this my problem? """
    def __init__(self, data_array):
        list.__init__(self, data_array)

    def my_method(self):
        """
        Will act on entire list, but I want it to act on only what's
        returned by the slice (which may be the entire list in some cases).
        """
        return "a, ".join([str(_i) for _i in self])

    def __getitem__(self, index):
        retval = super(MyContainerClass, self).__getitem__(index)
        if isinstance(index, slice):
            retval = type(self)(retval)
        return retval

    def __getslice__(self, i, j):
        # Python 2 built-in types only
        return self.__getitem__(slice(i, j))

Дополнительный метод __getslice__ нужен только в Python 2 и только в том случае, если вы наследуете из типа, который уже реализует __getslice__. list является таким типом.

Демо:

>>> data = MyContainerClass([1, 2, 3, 4, 5, 6, 7])
>>> data[:5]
[1, 2, 3, 4, 5]
>>> type(data[:5])
<class '__main__.MyContainerClass'>
>>> data.my_method()
'1a, 2a, 3a, 4a, 5a, 6a, 7'
>>> data[:3].my_method()
'1a, 2a, 3'
person Martijn Pieters    schedule 26.08.2014
comment
Я не знаком с этой строкой: retval = type(self)(retval). Не могли бы вы объяснить это немного? Я использовал тип (элемент) раньше, но не со вторым набором () после него. - person dthor; 26.08.2014
comment
@dthor: я сохраняю ваш класс подклассом; type(self) возвращает MyContainerClass (или любой подкласс), поэтому вы можете просто снова вызвать этот объект, чтобы создать новый экземпляр. - person Martijn Pieters; 26.08.2014
comment
@dthor: вы можете жестко закодировать класс с помощью retval = MyContainerClass(retval), но тогда подклассу придется переопределить __getitem__ только для того, чтобы изменить возвращаемый тип. - person Martijn Pieters; 26.08.2014
comment
@dthor, если это вас смущает, вы можете заменить его на self.__class__(retval). Они синонимы. Получите класс self и используйте его конструктор, чтобы вернуть новый объект. - person Adam Smith; 26.08.2014
comment
@AdamSmith Спасибо. Я понял это из комментария MartijnPieters, но ваш комментарий заставил меня понять, что это похоже на настройку __class__ экземпляра через self.__class__ = NewClass (что я делаю в другом месте в кодовой базе). Я знаю, что это не то же самое, поскольку здесь мы создаем новый объект. - person dthor; 26.08.2014
comment
Вы также можете получить класс View из класса list, возвращаемого операциями среза. Экземпляр View будет реализовывать методы, которые вы хотите применить к срезам. - person Carl Smith; 10.05.2018