Как __getitem__, __setitem__, работать со срезами?

Я запускаю Python 2.7.10.

Мне нужно перехватить изменения в списке. Под "изменением" я подразумеваю все, что изменяет список в неглубоком смысле (список не изменяется, если он состоит из одних и тех же объектов в том же порядке, независимо от состояния этих объектов, в противном случае это так). Мне не нужно определять , как список изменился, только тот, который у него есть. Поэтому я просто проверяю, могу ли я это обнаружить, и пусть базовый метод выполняет свою работу. Это моя тестовая программа:

class List(list):
    def __init__(self, data):
        list.__init__(self, data)
        print '__init__(', data, '):', self

    def __getitem__(self, key):
        print 'calling __getitem__(', self, ',', key, ')',
        r = list.__getitem__(self, key)
        print '-->', r
        return r

    def __setitem__(self, key, data):
        print 'before __setitem__:', self
        list.__setitem__(self, key, data)
        print 'after  __setitem__(', key, ',', data, '):', self

    def __delitem__(self, key):
        print 'before __delitem__:', self
        list.__delitem__(self, key)
        print 'after  __delitem__(', key, '):', self

l = List([0,1,2,3,4,5,6,7]) #1
x = l[5]                    #2
l[3] = 33                   #3
x = l[3:7]                  #4
del l[3]                    #5
l[0:4]=[55,66,77,88]        #6
l.append(8)                 #7

Дела № 1, № 2, № 3 и № 5 работают так, как я ожидал; # 4, # 6 и # 7 нет. Программа печатает:

__init__( [0, 1, 2, 3, 4, 5, 6, 7] ): [0, 1, 2, 3, 4, 5, 6, 7]
calling __getitem__( [0, 1, 2, 3, 4, 5, 6, 7] , 5 ) --> 5
before __setitem__: [0, 1, 2, 3, 4, 5, 6, 7]
after  __setitem__( 3 , 33 ): [0, 1, 2, 33, 4, 5, 6, 7]
before __delitem__: [0, 1, 2, 33, 4, 5, 6, 7]
after  __delitem__( 3 ): [0, 1, 2, 4, 5, 6, 7]

Я не очень удивлен # 7: append, вероятно, реализуется специальным образом. Но для №4 и №6 я смущен. Документация __getitem__ гласит: "Вызывается для реализации оценки self [key]. Для типов последовательностей принятые ключи должны быть целыми и срезами. (мои мотивы). И для __setitem__:" То же самое, что и для __getitem__()", что я подразумеваю, что key также может быть срезом.

Что не так с моими рассуждениями? Я готов, если необходимо, переопределить каждый метод изменения списка (добавление, расширение, вставка, поп и т.д.), Но что нужно переопределить, чтобы поймать что-то вроде # 6?

Я знаю о существовании __setslice__ и т.д. Но эти методы устарели с 2.0...

Хммм. Я снова прочитал документы для __getslice__, __setslice__ и т.д., И я нахожу это утверждение о том, что:

"(Тем не менее, встроенные типы в CPython по-прежнему реализуют __getslice__(). Поэтому вы должны переопределить его в производных классах при реализации slicing.)"

Это объяснение? Является ли это высказыванием "Ну, методы устарели, но для достижения такой же функциональности в 2.7.10, как и в версии 2.0, вам все равно придется их переопределять"? Увы, тогда почему вы их осуждали? Как все будет работать в будущем? Есть ли класс "списка", о котором я не знаю, - что я мог бы продлить и не представлял бы это неудобство? Что мне действительно нужно переопределить, чтобы удостовериться, что я поймаю каждую операцию изменения списка?

Ответ 1

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

Как все будет работать в будущем? Есть ли класс "списка", о котором я не знаю, - что я мог бы продлить и не представлял бы это неудобство?

Да, современный способ сделать это - использовать python Абстрактные базовые классы. Вы можете избежать этих уродливых осложнений, которые вы видите, когда подклассификация встроена list, вместо этого используется ABC. Для чего-то вроде списка попробуйте подклассом Sequence:

from collections import Sequence

class MyList(Sequence):
    ...

Теперь вам нужно только иметь дело с __getitem__ и друзьями для поведения нарезки.


Если вы хотите выполнить подклассификацию встроенного list, читайте дальше...

Ваша догадка правильная, вам нужно переопределить __getslice__ и __setslice__. Справочник языка объясняет, почему, и вы уже видели это:

Однако встроенные типы в CPython в настоящее время реализуют __getslice__(). Следовательно, вы должны переопределить его в производных классах при реализации среза.

Обратите внимание, что l[3:7] будет подключаться к __getslice__, тогда как в противном случае l[3:7:] будет подключаться к __getitem__. Итак, вы должны обрабатывать кусочки в обоих... стон!