Python: el __getattribute__ método y descriptores

de acuerdo con esta guía sobre descriptores de python https://docs.python.org/2/howto/descriptor.html

los objetos de método en las nuevas clases de estilo se implementan utilizando descriptores para evitar encuadrarlos en la búsqueda de atributos.

la forma en que entiendo esto es que hay un tipo de objeto de método que implementa __get__ y devuelve un objeto de método enlazado cuando se llama con una instancia y un objeto de método no vinculado cuando se llama sin instancia y solo una clase. el artículo también establece que esta lógica se implementa en el método .__ getattribute__ de objeto. al igual que:

def __getattribute__(self, key): "Emulate type_getattro() in Objects/typeobject.c" v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(None, self) return v 

Sin embargo, el objeto .__ getattribute__ es en sí mismo un método. Entonces, ¿cómo está vinculado a un objeto (sin recursión infinita)? si está encuadrado en el atributo de búsqueda, ¿eso no anula el propósito de eliminar la carcasa especial de estilo antiguo?

En realidad, en CPython, la implementación predeterminada de __getattribute__ no es un método de Python , sino que se implementa en C. Puede acceder a las ranuras de objetos (entradas en la estructura C que representan objetos de Python) directamente, sin molestarse en pasar por la molesta rutina de acceso a atributos.

El hecho de que su código Python tenga que hacer esto no significa que el código C tenga que hacerlo. 🙂

Si implementa un método Python __getattribute__ , simplemente use el object.__getattribute__(self, attrname) , o mejor aún, super(YourClassName, self).__getattribute__(attrname) para acceder a los atributos en self . De esa manera tampoco golpearás la recursión.

En la implementación de CPython, el acceso al atributo es manejado por la ranura tp_getattro en el objeto de tipo C, con un retroceso a la ranura tp_getattr . De esa forma puedes evitar las recursivas.

Para ser exhaustivo y para exponer completamente lo que hace el código C, cuando usa el acceso de atributo en una instancia, aquí está el conjunto completo de funciones llamadas:

  • Python traduce el acceso de atributos a una llamada a la función PyObject_GetAttr() C. La implementación para esa función busca la ranura tp_getattro o tp_getattr para su clase.

  • El tipo de object tiene tp_getattr lleno con la función _PyObject_GenericGetAttrWithDict . Esta función es su object.__getattribute__ método object.__getattribute__ (una tabla especial se asigna entre el nombre y la ranura).

  • Esta función puede acceder al objeto __dict__ la instancia a través de la ranura tp_dict , pero para los descriptores (incluidos los métodos), se _PyType_Lookup función _PyType_Lookup .

  • _PyType_Lookup busca atributos en la clase (y superclases). El código utiliza punteros directos a cl_dict (clases personalizadas de Python) y tp_dict para hacer referencia a los diccionarios de clases y tipos.

  • Si _PyType_Lookup encuentra un descriptor, se devuelve a _PyObject_GenericGetAttrWithDict y llama a la función tp_descr_get en ese objeto (el gancho __get__ ).

Cuando accede a un atributo en la clase en sí, en lugar de _PyObject_GenericGetAttrWithDict , la función type_getattro() type->tp_getattro ranura type->tp_getattro , que también toma en cuenta las type_getattro() . Esta versión también llama a __get__ , pero deja el parámetro de instancia establecido en None .

En ningún lugar este código tiene que llamar de forma recursiva a __getattribute__ para acceder al atributo __dict__ , ya que simplemente puede acceder directamente a las estructuras en C.