Pregunta

Empecé a usar el protocolo descriptor de Python más ampliamente en el código que he estado escribiendo. Por lo general, la magia de búsqueda de python predeterminada es lo que quiero que suceda, pero a veces encuentro que quiero obtener el objeto descriptor en sí mismo en lugar de los resultados de su método __get__ . Querer saber el tipo de descriptor, o el estado de acceso almacenado en el descriptor, o algo así.

Escribí el siguiente código para recorrer los espacios de nombres en lo que creo que es el orden correcto, y devolver el atributo en bruto, independientemente de si es un descriptor o no. Sin embargo, estoy sorprendido de que no pueda encontrar una función incorporada o algo en la biblioteca estándar para hacer esto: creo que tiene que estar allí y simplemente no me he dado cuenta o no busqué en Google el término de búsqueda correcto.

¿Hay alguna funcionalidad en algún lugar de la distribución de Python que ya lo haga (o algo similar)?

¡Gracias!

from inspect import isdatadescriptor

def namespaces(obj):
    obj_dict = None
    if hasattr(obj, '__dict__'):
        obj_dict = object.__getattribute__(obj, '__dict__')

    obj_class = type(obj)
    return obj_dict, [t.__dict__ for t in obj_class.__mro__]

def getattr_raw(obj, name):
    # get an attribute in the same resolution order one would normally,
    # but do not call __get__ on the attribute even if it has one
    obj_dict, class_dicts = namespaces(obj)

    # look for a data descriptor in class hierarchy; it takes priority over
    # the obj's dict if it exists
    for d in class_dicts:
        if name in d and isdatadescriptor(d[name]):
            return d[name]

    # look for the attribute in the object's dictionary
    if obj_dict and name in obj_dict:
        return obj_dict[name]

    # look for the attribute anywhere in the class hierarchy
    for d in class_dicts:
        if name in d:
            return d[name]

    raise AttributeError

Editar mié, 28 de octubre de 2009.

La respuesta de Denis me dio una convención para usar en mis clases de descriptor para obtener los objetos de descriptor. Pero, tenía toda una jerarquía de clases de clases de descriptores, y no quería comenzar a cada __get__ una función repetitiva

def __get__(self, instance, instance_type):
    if instance is None: 
        return self
    ...

Para evitar esto, hice que la raíz del árbol de clases de descriptor herede de lo siguiente:

def decorate_get(original_get):
    def decorated_get(self, instance, instance_type):
        if instance is None:
            return self
        return original_get(self, instance, instance_type)
    return decorated_get

class InstanceOnlyDescriptor(object):
    """All __get__ functions are automatically wrapped with a decorator which
    causes them to only be applied to instances. If __get__ is called on a 
    class, the decorator returns the descriptor itself, and the decorated
    __get__ is not called.
    """
    class __metaclass__(type):
        def __new__(cls, name, bases, attrs):
            if '__get__' in attrs:
                attrs['__get__'] = decorate_get(attrs['__get__'])
            return type.__new__(cls, name, bases, attrs)
¿Fue útil?

Solución

La mayoría de los descriptores hacen su trabajo cuando se accede solo como atributo de instancia. Por lo tanto, es conveniente regresar cuando se accede a la clase:

class FixedValueProperty(object):
    def __init__(self, value):
        self.value = value
    def __get__(self, inst, cls):
        if inst is None:
            return self
        return self.value

Esto le permite obtener el descriptor mismo:

>>> class C(object):
...     prop = FixedValueProperty('abc')
... 
>>> o = C()
>>> o.prop
'abc'
>>> C.prop
<__main__.FixedValueProperty object at 0xb7eb290c>
>>> C.prop.value
'abc'
>>> type(o).prop.value
'abc'

Tenga en cuenta que esto también funciona para los descriptores integrados (¿la mayoría?):

>>> class C(object):
...     @property
...     def prop(self):
...         return 'abc'
... 
>>> C.prop
<property object at 0xb7eb0b6c>
>>> C.prop.fget
<function prop at 0xb7ea36f4>

Acceder al descriptor podría ser útil cuando necesite ampliarlo en la subclase, pero hay un mejor manera de hacer esto.

Otros consejos

La biblioteca inspect proporciona una función para recuperar un atributo sin ningún descriptor mágico: inspect.getattr_static .

Documentación: https://docs.python.org /3/library/inspect.html#fetching-attributes-statical

(Esta es una pregunta antigua, pero sigo encontrándola cuando intento recordar cómo hacer esto, ¡así que publico esta respuesta para poder encontrarla nuevamente!)

El método anterior

class FixedValueProperty(object):
    def __init__(self, value):
        self.value = value
    def __get__(self, inst, cls):
        if inst is None:
            return self
        return self.value

Es un método excelente cada vez que controla el código de la propiedad, pero hay algunos casos, como cuando la propiedad es parte de una biblioteca controlada por otra persona, donde otro enfoque es útil. Este enfoque alternativo también puede ser útil en otras situaciones, como implementar la asignación de objetos, recorrer un espacio de nombres como se describe en la pregunta u otras bibliotecas especializadas.

Considere una clase con una propiedad simple:

class ClassWithProp:

    @property
    def value(self):
        return 3
>>>test=ClassWithProp()
>>>test.value
3
>>>test.__class__.__dict__.['value']
<property object at 0x00000216A39D0778>

Cuando se accede desde la clase de objetos contenedor dict , se omite la 'magia del descriptor'. Tenga en cuenta también que si asignamos la propiedad a una nueva variable de clase, se comporta como el original con 'descriptor magic', pero si se asigna a una variable de instancia, la propiedad se comporta como cualquier objeto normal y también omite 'descriptor magic'.

>>> test.__class__.classvar =  test.__class__.__dict__['value']
>>> test.classvar
3
>>> test.instvar = test.__class__.__dict__['value']
>>> test.instvar
<property object at 0x00000216A39D0778>

Digamos que queremos obtener el descriptor para obj.prop donde type (obj) es C .

C.prop generalmente funciona porque el descriptor generalmente se devuelve cuando se accede a través de C (es decir, vinculado a C ). Pero C.prop puede activar un descriptor en su metaclase. Si prop no estuviera presente en obj , obj.prop aumentaría AttributeError mientras que C.prop puede que no. Por lo tanto, es mejor usar inspect.getattr_static (obj, 'prop') .

Si no está satisfecho con eso, aquí hay un método específico de CPython (de _PyObject_GenericGetAttrWithDict en Objects / object.c ):

import ctypes, _ctypes

_PyType_Lookup = ctypes.pythonapi._PyType_Lookup
_PyType_Lookup.argtypes = (ctypes.py_object, ctypes.py_object)
_PyType_Lookup.restype = ctypes.c_void_p

def type_lookup(ty, name):
    """look for a name through the MRO of a type."""
    if not isinstance(ty, type):
        raise TypeError('ty must be a type')

    result = _PyType_Lookup(ty, name)
    if result is None:
        raise AttributeError(name)

    return _ctypes.PyObj_FromPtr(result)

type_lookup (type (obj), 'prop') devuelve el descriptor de la misma manera cuando CPython lo usa en obj.prop if obj es un objeto habitual (no clase, por ejemplo).

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top