Для пользовательских классов неявные вызовы специальных методов гарантированно работают правильно только в том случае, если они определены для типа объекта (в теле класса как фактический метод), а не в словаре экземпляра объекта. Такое поведение является причиной того, что следующий код вызывает исключение:
>>> 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__()
таким образом предоставляет значительные возможности для оптимизации скорости в интерпретаторе за счет некоторой гибкости в обработке специальных методов. Специальный метод должен быть установлен на самом объекте класса, чтобы его можно было последовательно вызывать из интерпретатора.