2012-09-26 18 views
19

A la pregunta:descriptores como atributos ejemplo en Python

Por qué no puede ser descriptores atribuye ejemplo?

ha sido answered que:

objetos descriptor tiene que vivir en la clase, no en la instancia

porque esa es la forma en que el __getattribute__ se implementa.

Un ejemplo simple. Considere un descriptor:

class Prop(object): 

    def __get__(self, obj, objtype=None): 
     if obj is None: 
      return self 
     return obj._value * obj._multiplier 

    def __set__(self, obj, value): 
     if obj is None: 
      return self 
     obj._value = value 

class Obj(object): 

    val = Prop() 

    def __init__(self): 
     self._value = 1 
     self._multiplier = 0 

considerar el caso en el que cada obj tiene múltiples Prop: yo tendría que usar nombres únicos para identificar los valores y los multiplicadores (como here Tener un objeto descriptor por cada instancia permitiría almacenar el. . _multiplier (y el _value) en el descriptor de sí mismo, lo que simplifica algunas cosas

para implementar por ejemplo descriptor de atributos que necesita para cualquiera:

  1. crear una instancia por clase See here
  2. anulación __getattribute__See here

Soy consciente de que preguntas similares se han planteado antes, pero no he encontrado una explicación real:

  1. Por qué Python está diseñado de esta manera?
  2. ¿Cuál es la forma sugerida de almacenar la información que el descriptor necesita pero es por instancia?

Respuesta

9

Mucha funcionalidad avanzada solo funciona cuando se define en una clase en lugar de una instancia; todos los métodos especiales, por ejemplo. Además de hacer que la evaluación del código sea más eficiente, esto deja en claro la separación entre las instancias y los tipos que de otro modo tenderían a colapsar (porque, por supuesto, todos los tipos son objetos).

no estoy seguro de cómo es Recomendaciones Este, pero se puede almacenar en el caso de un mapeo de ejemplo descriptor de atribuir valor:

class Prop(object): 
    def __get__(self, obj, objtype=None): 
     if obj is None: 
      return self 
     return obj._value * obj._multiplier[self] 

    def __set__(self, obj, value): 
     if obj is None: 
      return self 
     obj._value = value 

class Obj(object): 
    val = Prop() 

    def __init__(self): 
     self._value = 1 
     self._multiplier = {Obj.val: 0} 

Esto tiene ventajas evidentes sobre las otras dos opciones sugeridas:

  1. clases por instancia rompen la orientación del objeto y aumentan el uso de la memoria;
  2. omitiendo __getattribute__ es ineficaz (ya que todos los accesos a los atributos deben pasar por el método especial anulado) y es frágil.

Como alternativa, se puede utilizar una propiedad de proxy:

class PerInstancePropertyProxy(object): 
    def __init__(self, prop): 
     self.prop = prop 
    def __get__(self, instance, owner): 
     if instance is None: 
      return self 
     return instance.__dict__[self.prop].__get__(instance, owner) 
    def __set__(self, instance, value): 
     instance.__dict__[self.prop].__set__(instance, value) 
class Prop(object): 
    def __init__(self, value, multiplier): 
     self.value = value 
     self.multiplier = multiplier 
    def __get__(self, instance, owner): 
     if instance is None: 
      return self 
     return self.value * self.multiplier 
    def __set__(self, instance, value): 
     self.value = value 
class Obj(object): 
    val = PerInstancePropertyProxy('val') 
    def __init__(self): 
     self.__dict__['val'] = Prop(1.0, 10.0) 
    def prop(self, attr_name): 
     return self.__dict__[attr_name] 
+0

Puedes expandir lo que quieres decir con ** romper orientación de objeto **. En segundo lugar, el problema que veo con este enfoque es cómo proporcionar una API simple para cambiar el multiplicador. Los usuarios tendrán que hacer algo como 'obj._multiplier [Obj.val] = 10'. Esto se puede envolver en una función 'def change_multiplier (self, attr_name, new_value)' pero no escala bien si 'Prop' tiene múltiples atributos. Algo como 'def prop (self, attr_name): return self.__dict __ [attr_name] 'podría usarse para hacer algo como' obj.prop ('val'). multiplicador = 10'. – Hernan

+0

@Hernan hay una suposición habitual de que las instancias tienen el mismo tipo; viola eso y varias cosas se romperán. En lo que se refiere a cambiar el multiplicador, ¿quizás una propiedad proxy? - ver editar arriba. – ecatmur

+0

Es cierto que todas las instancias no serán del mismo tipo, pero puede crear subclases, por lo que la instancia seguirá funcionando. En cuanto al proxy, escribí algo como esto, pero no estoy seguro de que sea una buena idea. Básicamente, 'obj.prop ('val')' devuelve un objeto proxy que conoce acerca de 'obj' y' val'. Cuando haces 'obj.prop ('val'). Multiplicador = 10' escribe a' obj._multiplier [val] = 10'. No estoy seguro de lo fácil que será. – Hernan

15

Esta pregunta exacta fue raised on Python-list a principios de este año. Voy a citar Ian G. Kelly's response:

El comportamiento es por diseño. Primero, mantener el comportamiento del objeto en la definición de clase simplifica la implementación y también hace que las comprobaciones de la instancia sean más significativas. Para tomar prestado su ejemplo de Registro, si el descriptor "M" está definido por algunas instancias en lugar de por la clase, entonces sabiendo que el objeto "reg" es una instancia de Registro no le dice nada al si "reg.M "es un atributo válido o un error. Como resultado de , tendré que proteger prácticamente todos los accesos de "reg.M" con un try -excepto por si acaso "reg" es el tipo incorrecto de registro.

En segundo lugar, la separación de la clase de la instancia también le ayuda a mantener el comportamiento de los objetos separado de los datos del objeto. Considere la clase siguiente:

class ObjectHolder(object): 
    def __init__(self, obj): 
     self.obj = obj 

No se preocupe por lo que esta clase podría ser útil para.Sólo sé que que está destinado a mantener y proporcionar un acceso sin restricciones a arbitrarias de Python objetos:

>>> holder = ObjectHolder(42) 
>>> print(holder.obj) 42 
>>> holder.obj = range(5) 
>>> print(holder.obj) [0, 1, 2, 3, 4] 

Puesto que la clase está destinado a contener objetos arbitrarios, es aún válida que alguien podría querer almacenar un objeto descriptor de allí:

>>> holder.obj = property(lambda x: x.foo) 
>>> print(holder.obj) <property object at 0x02415AE0> 

Ahora supongamos que Python invoca el protocolo descriptor para descriptores almacenados en la instancia atributos:

>>> holder = ObjectHolder(None) 
>>> holder.obj = property(lambda x: x.foo) 
>>> print(holder.obj) 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
AttributeError: 'ObjectHolder' object has no attribute 'foo' 

En este caso, el ObjectHolder no podría contener simplemente la propiedad objeto como datos. El mero acto de asignar el objeto de propiedad, un descriptor , a un atributo de instancia sería cambiar el comportamiento de el ObjectHolder. En lugar de tratar "holder.obj" como un simple atributo de datos , comenzaría a invocar el protocolo descriptor en accesos a "holder.obj" y finalmente los redireccionaría al atributo "holder.foo" sin sentido y sin sentido, que ciertamente no es lo que el autor pretendía.

Si desea poder admitir varias instancias de un descriptor, simplemente haga que el constructor de ese descriptor tome un argumento de nombre (prefijo) y prefija los atributos agregados con ese nombre. Incluso podría crear un objeto de espacio de nombres (diccionario) dentro de la instancia de clase para albergar todas las nuevas instancias de propiedad.

+0

Los enlaces a pitón lista están muertos. ¿El nuevo enlace es https://mail.python.org/pipermail/python-list/2012- Jan/631340.html? –

+0

Sí. Lo editaré en. – nneonneo

Cuestiones relacionadas