Почему __getitem__ не может быть classmethod?

Предположим, что следующий класс:

class Class(object):
    @classmethod
    def getitem(*args):
        print 'getitem %s' % (args,)
    @classmethod
    def __getitem__(*args):
        print '__getitem__ %s' % (args,)

Метод getitem ведет себя как и ожидалось: он получает Class как первый arg, но __getitem__ получает type как первый аргумент arg:

calling Class.getitem(test)
getitem (<class '__main__.Class'>, 'test')

calling obj.getitem(test)
getitem (<class '__main__.Class'>, 'test')

calling Class[test]
'type' object has no attribute '__getitem__'

calling obj[test]
__getitem__ (<class '__main__.Class'>, 'test')

Какая там магия позади __getitem__?

Ответы

Ответ 1

Специальные методы рассматриваются на классе, а не на экземпляре - в отличие от обычных методов, которые сначала проверяются на экземпляр. См. Специальный поиск методов в документах модели данных Python.

Думая о Class как пример type, это означает, что когда вы делаете

Class.getitem(test)

Вначале выглядит именно то, что вы говорите: метод в Class собственные атрибуты, называемые getitem. Но, когда вы используете

Class[test]

он пропускает это значение и переходит прямо к type (являясь классом Class или его метаклассом), и поэтому вызывает type.__getitem__(Class, test). Итак, что происходит не в том, что __getitem__ получает type в качестве своего первого аргумента (он все равно получит Class, как это делает, если вы явно Class.__getitem__(test)), то его __getitem__, который ищет Python в этого случая не существует. Чтобы он существовал, вам нужно определить свой собственный метакласс для Class, который определяет его как метод экземпляра, а не определяет его на Class как метод класса.

Ответ 2

Когда вы вызываете x[test], интерпретатор проверяет type(x) для атрибута __getitem__. В случае Class[test] это метаклас Class, т.е. type. Если вы хотите иметь класс __getitem__ класса, определите его внутри нового метакласса. (Излишне говорить, что это своего рода волшебство, как и все, что вы делаете с метаклассами)

class Meta(type):
    def __getitem__(self, arg):
        print "__getitem__:", arg


class X(object):
    __metaclass__ = Meta

X['hello'] # output: __getitem__ hello