2009-10-27 16 views
6

Empecé a usar el protocolo de descripción de python de forma más extensa en el código que he estado escribiendo. Normalmente, la magia de búsqueda de python predeterminada es lo que quiero que suceda, pero a veces me doy cuenta de que quiero obtener el objeto de descripción 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í.búsqueda de atributos Python sin ningún descriptor mágico?

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 puedo encontrar una función incorporada o algo así en la biblioteca estándar para hacer esto, me imagino que tiene que estar allí y simplemente no lo he notado o busqué en Google el término de búsqueda correcto.

¿Hay alguna funcionalidad en 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ér 28 de octubre de 2009.

respuesta de Denis me dio una convención para su uso en mis clases de descriptores para obtener objetos mismos del descriptor. Sin embargo, tenía toda una jerarquía de clases de clases de descriptores, y yo no quiero empezar cada función __get__ con un texto modelo

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

Para evitar esto, he hecho la raíz del árbol de la clase hereda de descriptor el 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) 
+0

¿A veces quieres el objeto descriptor? Eso viola las expectativas básicas para los descriptores: se supone que deben verse como atributos. ¿Por qué romper esa expectativa fundamental? ¿Por qué hacer esto? ¿Por qué crear algo tan complejo? –

+0

Lo que estoy haciendo no me parece * eso * complejo, pero supongo que podría decir que estoy experimentando con el diseño. En mi caso particular actual, tengo un descriptor que devuelve la fuerza de un arma en un juego. Ese valor es una función del estado del descriptor (fuerza del arma) y la instancia (salud del barco). Hay diferentes tipos de armas; generalmente solo quiero el resultado del valor, pero en algunos casos, necesito saber qué tipo de arma es, el tipo de descriptor. ¿Y qué ocurre si un descriptor tiene métodos que no forman parte del protocolo descriptor y desea llamarlos? –

Respuesta

11

La mayoría de los descriptores hacen su trabajo cuando se accede solo como atributo de instancia. Así que es conveniente volver a sí mismo 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 sí:

>>> 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 funciona para (la mayoría?) Incorporadas en los descriptores también:

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

descriptor de Acceso puede ser útil cuando se necesita a medida que en la subclase, pero hay una better way para hacer esto.

+0

No me di cuenta de eso; bueno saber. Las funciones mismas serían una excepción, una excepción a ese patrón, pero tal vez el único. Tendrá que hurgar en los descriptores incorporados. –

+0

Aunque no respondió mi pregunta exactamente como me lo preguntaron, tu respuesta me ayudó a resolver mi problema subyacente. Lo acepto –

+0

son funciones de excepción a este patrón (supongo que hablas de métodos)? No, 'c.method' devuelve un método dependiente de una descripción, mientras que' C.method' devuelve un método independiente. Es el mismo patrón. – u0b34a0f6ae

0

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 gran método cada vez que se 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 mapeo de objetos, recorrer un espacio de nombres como se describe en la pregunta u otras bibliotecas especializadas.

Considérese 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 los objetos contenedores de clase dict, se omite la 'magia descriptor'.Tenga en cuenta también que si asignamos la propiedad a una nueva variable de clase, se comporta igual que 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 pasa por alto 'descriptor magic'.

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

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

C.prop generalmente funciona porque el descriptor generalmente se devuelve al acceder a través de C (es decir, vinculado a C). Pero C.prop puede desencadenar un descriptor en su metaclase. Si prop no estaban presentes en obj, obj.prop aumentaría AttributeError, mientras que C.prop podría no ser así. Entonces es mejor usar inspect.getattr_static(obj, 'prop').

Si no está satisfecho con eso, aquí hay un método de CPython específica (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 utiliza en obj.prop si obj es una objeto habitual (no clase, por ejemplo).

Cuestiones relacionadas