Как объект .__ getattribute__ перенаправляет на метод __get__ в моем дескрипторе и __getattr__? - PullRequest
1 голос
/ 20 марта 2020

У меня есть вопрос из двух частей, касающийся реализации object.__getattribute(self, key), но они оба связаны с моей путаницей в том, как это работает.

Я определил дескриптор данных под названием NonNullStringDescriptor, который Я намеревался присоединиться к атрибутам.

class NonNullStringDescriptor:

    def __init__(self, value: str = "Default Name"):
        self.value = value

    def __get__(self, instance, owner):
        return self.value

    def __set__(self, instance, value):
        if isinstance(value, str) and len(value.strip()) > 0:
            self.value = value
        else:
            raise TypeError("The value provided is not a non-null string.")

Затем я объявляю класс Person с атрибутом name.

class Person:
    name = NonNullStringDescriptor()

    def __init__(self):
        self.age = 22

    def __getattribute__(self, key):
        print(f"__getattribute__({key})")
        v = super(Person, self).__getattribute__(key)
        print(f"Returned {v}")

        if hasattr(v, '__get__'):
            print("Invoking __get__")
            return v.__get__(None, self)
        return v

    def __getattr__(self, item):
        print(f"__getattr__ invoked.")
        return "Unknown"

Теперь я пытаюсь получить доступ к переменным атрибутам, некоторые которые являются дескрипторами, некоторыми обычными атрибутами экземпляра и другими несуществующими:

    person = Person()
    print("Printing", person.age) # "normal" attribute
    print("Printing", person.hobby) # non-existent attribute
    print("Printing", person.name) # descriptor attribute

Вывод, который мы видим:

__getattribute__(age)
Returned 22
Printing 22
__getattribute__(hobby)
__getattr__ invoked.
Printing Unknown
__getattribute__(name)
Returned Default Name
Printing Default Name

У меня есть два основных вопроса, оба из которых вокруг super(Person, self).__getattribute__(key):

  1. Когда я пытаюсь получить доступ к несуществующему атрибуту, например hobby, я вижу, что он перенаправляет на __getattr__, который, как я знаю, часто «резервный» метод в поиске атрибутов. Тем не менее, я вижу, что __getattribute__ вызывает этот метод. Однако консольный вывод Returned ... никогда не печатается, что означает, что остальная часть __getattribute__ не завершена - так как именно __getattribute__ вызывает __getattr__ напрямую, возвращая это значение по умолчанию "Неизвестно", не выполняя остальную часть его собственный вызов функции?

  2. Я ожидаю, что то, что возвращается из super(Person, self).__getattribute__(key) (v), является экземпляром дескриптора данных NonNullStringDescriptor. Тем не менее, я вижу, что v на самом деле сама строка "Имя по умолчанию"! Так как же object.__getattribute__(self, key) просто знает, как использовать метод __get__ моего дескриптора вместо возврата экземпляра дескриптора?

В протоколе дескриптора есть ссылки на поведение :

Если искомое значение является объектом, определяющим один из методов дескриптора, то Python может переопределить поведение по умолчанию и вместо этого вызвать метод дескриптора .

Но мне никогда не было ясно определено, что на самом деле происходит в object.__getattribute(self, key), который выполняет переопределение. Я знаю, что в конечном итоге person.name преобразуется в низкоуровневый вызов type(person).__dict__["name"].__get__(person, type(person)) - все это происходит в object.__getattribute__?

Я нашел это ТАК сообщение , которое описывает правильное реализация __getattribute__, но мне более любопытно, что на самом деле происходит в object.__getattribute__. Тем не менее, моя IDE (PyCharm) предоставляет только заглушку для его реализации:

def __getattribute__(self, *args, **kwargs): # real signature unknown
        """ Return getattr(self, name). """
        pass

1 Ответ

4 голосов
/ 20 марта 2020

__getattribute__ не звонит __getattr__. Откат __getattr__ происходит в механизме доступа к атрибуту, после того, как __getattribute__ поднимает AttributeError. Если вы хотите увидеть реализацию, она находится в slot_tp_getattr_hook в Objects/typeobject.c.

object.__getattribute__ знает, как вызвать __get__, потому что в object.__getattribute__ есть код, который вызывает __get__. Это довольно просто. Если вы хотите увидеть реализацию, object.__getattribute__ - это PyObject_GenericGetAttr в реализации (да, хотя в нем говорится GetAttr - сторона C немного отличается от стороны Python ), и есть два __get__ сайта вызовов (один для дескрипторов данных и один для дескрипторов, не связанных с данными), здесь и здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...