búsqueda de atributos de Python sin ningún descriptor mágico?
-
06-07-2019 - |
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)
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).