Сообщить об ошибке.

Алгоритм поиска специальных методов в классах Python

Для пользовательских классов неявные вызовы специальных методов гарантированно работают правильно только в том случае, если они определены для типа объекта (в теле класса как фактический метод), а не в словаре экземпляра объекта. Такое поведение является причиной того, что следующий код вызывает исключение:

>>> class C:
...     pass
...
>>> c = C()
>>> c.__len__ = lambda: 5
>>> len(c)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: object of type 'C' has no len()

Обоснование такого поведения заключается в ряде специальных методов, таких как __hash__() и __repr__(), которые реализуются всеми объектами, включая объекты типов. Если при неявном поиске этих методов использовался обычный процесс поиска, они завершились бы ошибкой при вызове самого объекта типа:

>>> 1 .__hash__() == hash(1)
# True
>>> int.__hash__() == hash(int)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: descriptor '__hash__' of 'int' object needs an argument

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

>>> type(1).__hash__(1) == hash(1)
# True
>>> type(int).__hash__(int) == hash(int)
# True

Обход специального метода __getattribute__() при неявном поиске.

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

>>> class Meta(type):
...     def __getattribute__(*args):
...         print("Metaclass getattribute invoked")
...         return type.__getattribute__(*args)
...
>>> class C(object, metaclass=Meta):
...     def __len__(self):
...         return 10
...     def __getattribute__(*args):
...         print("Class getattribute invoked")
...         return object.__getattribute__(*args)
...
>>> c = C()
# Явный поиск через экземпляр
>>> c.__len__()
# Class getattribute invoked
# 10

# Явный поиск по типу
>>> type(c).__len__(c)
# Metaclass getattribute invoked
# 10

# Неявный поиск
>>> len(c)
# 10

Обход механизма __getattribute__() таким образом предоставляет значительные возможности для оптимизации скорости в интерпретаторе за счет некоторой гибкости в обработке специальных методов. Специальный метод должен быть установлен на самом объекте класса, чтобы его можно было последовательно вызывать из интерпретатора.