Supongamos que queremos crear una familia de clases que sean implementaciones o especializaciones diferentes de un concepto global. Supongamos que hay una implementación predeterminada plausible para algunas propiedades derivadas. Nos gustaría poner esto en una clase base
class Math_Set_Base:
@property
def size(self):
return len(self.elements)
Por lo tanto, una subclase podrá contar automáticamente sus elementos en este ejemplo bastante tonto
class Concrete_Math_Set(Math_Set_Base):
def __init__(self,*elements):
self.elements = elements
Concrete_Math_Set(1,2,3).size
# 3
Pero, ¿qué pasa si una subclase no quiere usar este valor predeterminado? Esto no funciona:
import math
class Square_Integers_Below(Math_Set_Base):
def __init__(self,cap):
self.size = int(math.sqrt(cap))
Square_Integers_Below(7)
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 3, in __init__
# AttributeError: can't set attribute
Me doy cuenta de que hay formas de anular una propiedad con una propiedad, pero me gustaría evitar eso. Debido a que el propósito de la clase base es hacer la vida lo más fácil posible para su usuario, no agregar hinchazón imponiendo un método de acceso complicado y superfluo (desde el punto de vista estrecho de la subclase).
Se puede hacer? Si no, ¿cuál es la siguiente mejor solución?
fuente
len(self.elements)
como implementación. Esta implementación muy específica impone un contrato en todas las instancias de la clase, a saber, que proporcionanelements
cuál es un contenedor de tamaño . Sin embargo, suSquare_Integers_Below
clase no parece comportarse de esa manera (tal vez está generando sus miembros dinámicamente), por lo que debe definir su propio comportamiento.len(self.elements)
no es una implementación predeterminada adecuada para conjuntos matemáticos ya que existen "muchos" conjuntos que ni siquiera tienen cardinalidad finita. En general, si una subclase no quiere usar el comportamiento de sus clases base, debe anularlo a nivel de clase. Los atributos de instancia, como su nombre indica, funcionan en el nivel de instancia y, por lo tanto, se rigen por el comportamiento de la clase.Respuestas:
Una propiedad es un descriptor de datos que tiene prioridad sobre un atributo de instancia con el mismo nombre. Puede definir un descriptor sin datos con un
__get__()
método único : un atributo de instancia tiene prioridad sobre el descriptor sin datos con el mismo nombre, consulte los documentos . El problema aquí es que lo que senon_data_property
define a continuación es solo para fines de cálculo (no puede definir un establecedor o un eliminador), pero parece ser el caso en su ejemplo.Sin embargo, esto supone que tiene acceso a la clase base para realizar estos cambios.
fuente
__get__()
,__set__()
y__delete__()
) yAttributeError
generan un si no proporciona ninguna función para ellos. Ver Python equivalente a la implementación de la propiedad .setter
o__set__
la propiedad, esto funcionará. Sin embargo, le advierto que esto también significa que puede sobrescribir fácilmente la propiedad no solo a nivel de clase (self.size = ...
) sino incluso a nivel de instancia (por ejemploConcrete_Math_Set(1, 2, 3).size = 10
, sería igualmente válido). Alimento para los pensamientos :)Square_Integers_Below(9).size = 1
también es válido ya que es un atributo simple. En este caso de uso particular de "tamaño", esto puede parecer incómodo (use una propiedad en su lugar), pero en el caso general "anular fácilmente un accesorio calculado en la subclase", podría ser bueno en algunos casos. También puede controlar el acceso a los atributos con,__setattr__()
pero podría ser abrumador.Esta será una respuesta larga y sin aliento que solo podría ser complementaria ... pero su pregunta me llevó a dar una vuelta por la madriguera del conejo, así que también me gustaría compartir mis hallazgos (y dolor).
En última instancia, puede encontrar esta respuesta no útil para su problema real. De hecho, mi conclusión es que no haría esto en absoluto. Dicho esto, los antecedentes de esta conclusión pueden entretenerlo un poco, ya que está buscando más detalles.
Abordar algunos conceptos erróneos
La primera respuesta, aunque correcta en la mayoría de los casos, no siempre es el caso. Por ejemplo, considere esta clase:
inst_prop
, aunque es unproperty
, es irrevocablemente un atributo de instancia:Todo depende donde sus
property
se define en el primer lugar. Si su@property
se define dentro de la clase "ámbito" (o realmente, elnamespace
), se convierte en un atributo de clase. En mi ejemplo, la clase en sí misma no conoce ningunainst_prop
hasta que se instancia. Por supuesto, no es muy útil como propiedad aquí.Pero primero, abordemos su comentario sobre la resolución de herencia ...
Entonces, ¿cómo influye exactamente la herencia en este problema? El siguiente artículo se sumerge un poco en el tema, y el Orden de resolución de métodos está algo relacionado, aunque analiza principalmente la amplitud de la herencia en lugar de la profundidad.
En combinación con nuestro hallazgo, dada la siguiente configuración:
Imagine lo que sucede cuando se ejecutan estas líneas:
El resultado es así:
Date cuenta cómo:
self.world_view
se pudo aplicar, aunqueself.culture
fallóculture
no existe enChild.__dict__
(elmappingproxy
de la clase, no debe confundirse con la instancia__dict__
)culture
existe enc.__dict__
, no se hace referencia a él.Es posible que pueda adivinar por qué: la clase la
world_view
sobrescribióParent
como no propiedad, porChild
lo que también pude sobrescribirla. Mientras tanto, comoculture
se hereda, solo existe dentromappingproxy
deGrandparent
:De hecho, si intentas eliminar
Parent.culture
:Notarás que ni siquiera existe para
Parent
. Porque el objeto se está refiriendo directamente aGrandparent.culture
.Entonces, ¿qué pasa con la orden de resolución?
Por lo tanto, estamos interesados en observar el orden de resolución real, intentemos eliminarlo
Parent.world_view
:¿Se pregunta cuál es el resultado?
¡Volvió a los abuelos
world_view
property
, a pesar de que habíamos logrado asignar elself.world_view
antes! Pero, ¿qué pasa si cambiamos con fuerzaworld_view
a nivel de clase, como la otra respuesta? ¿Qué pasa si lo eliminamos? ¿Qué sucede si asignamos el atributo de clase actual como una propiedad?El resultado es:
Esto es interesante porque
c.world_view
se restaura a su atributo de instancia, mientras queChild.world_view
es el que le asignamos. Después de eliminar el atributo de instancia, vuelve al atributo de clase. Y después de reasignarChild.world_view
a la propiedad, perdemos instantáneamente el acceso al atributo de instancia.Por lo tanto, podemos suponer el siguiente orden de resolución :
property
, recupere su valor mediantegetter
ofget
(más sobre esto más adelante). La clase actual primero a la clase base última.property
atributo que no es de clase. La clase actual primero a la clase base última.En ese caso, eliminemos la raíz
property
:Lo que da:
Ta-dah!
Child
ahora tiene el suyoculture
basado en la inserción forzada enc.__dict__
.Child.culture
no existe, por supuesto, ya que nunca se definió enParent
oChild
atributo de clase, yGrandparent
se eliminó.¿Es esta la causa raíz de mi problema?
En realidad no . El error que está recibiendo, que todavía estamos observando al asignar
self.culture
, es totalmente diferente . Pero el orden de herencia establece el telón de fondo para la respuesta, que es laproperty
propia.Además del
getter
método mencionado anteriormente ,property
también tiene algunos buenos trucos bajo la manga. Lo más relevante en este caso es el métodosetter
ofset
, que se activa porself.culture = ...
línea. Comoproperty
no implementó ninguna funciónsetter
ofget
función, python no sabe qué hacer, y en suAttributeError
lugar arroja un (es decircan't set attribute
).Sin embargo, si implementó un
setter
método:Al crear una instancia de la
Child
clase, obtendrá:En lugar de recibir un
AttributeError
, ahora estás llamando alsome_prop.setter
método. Lo que le da más control sobre su objeto ... con nuestros hallazgos anteriores, sabemos que necesitamos tener un atributo de clase sobrescrito antes de que llegue a la propiedad. Esto podría implementarse dentro de la clase base como desencadenante. Aquí hay un nuevo ejemplo:Lo que resulta en:
TA-DAH! ¡Ahora puede sobrescribir su propio atributo de instancia sobre una propiedad heredada!
Entonces, ¿problema resuelto?
... Realmente no. El problema con este enfoque es que ahora no puede tener un
setter
método adecuado . Hay casos en los que desea establecer valores en suproperty
. Pero ahora cada vez que se estableceself.culture = ...
que será siempre sobrescribir cualquier función que ha definido en elgetter
(que en este caso, realmente es sólo la@property
parte envuelta. Usted puede añadir en medidas más matizadas, pero de una manera u otra siempre va a implicar más que simplementeself.culture = ...
. p.ej:Es muuuucho más complicado que la otra respuesta,
size = None
a nivel de clase.También podría considerar escribir su propio descriptor en vez de manejar la
__get__
e__set__
, o métodos adicionales. Pero al final del día, cuandoself.culture
se hace referencia,__get__
siempre se activará primero, y cuandoself.culture = ...
se haga referencia,__set__
siempre se activará primero. No hay forma de evitarlo por lo que he intentado.El quid de la cuestión, la OMI
El problema que veo aquí es que no puedes tener tu pastel y comértelo también.
property
se entiende como un descriptor con acceso conveniente desde métodos comogetattr
osetattr
. Si también desea que estos métodos logren un propósito diferente, solo está buscando problemas. Quizás debería repensar el enfoque:property
para esto?property
, ¿hay alguna razón por la que deba sobrescribirlo?property
no se aplica?property
s, ¿un método separado me serviría mejor que simplemente reasignar, ya que la reasignación puede anular accidentalmente elproperty
s?Para el punto 5, mi enfoque sería tener un
overwrite_prop()
método en la clase base que sobrescriba el atributo de clase actual para queproperty
ya no se active:Como puede ver, aunque todavía es un poco artificial, es al menos más explícito que un críptico
size = None
. Dicho esto, en última instancia, no sobrescribiría la propiedad en absoluto, y reconsideraría mi diseño desde la raíz.Si has llegado hasta aquí, gracias por caminar este viaje conmigo. Fue un pequeño ejercicio divertido.
fuente
A
@property
se define a nivel de clase. La documentación entra en detalles exhaustivos sobre cómo funciona, pero es suficiente para decir que establecer u obtener la propiedad resuelve llamar a un método en particular. Sin embargo, elproperty
objeto que gestiona este proceso se define con la propia definición de la clase. Es decir, se define como una variable de clase pero se comporta como una variable de instancia.Una consecuencia de esto es que puedes reasignarlo libremente a nivel de clase :
Y al igual que cualquier otro nombre de nivel de clase (por ejemplo, métodos), puede anularlo en una subclase definiéndolo de manera explícita de manera diferente:
Cuando creamos una instancia real, la variable de instancia simplemente sombrea la variable de clase del mismo nombre. El
property
objeto normalmente usa algunas travesuras para manipular este proceso (es decir, aplicar getters y setters), pero cuando el nombre de nivel de clase no se define como una propiedad, no sucede nada especial, por lo que actúa como cabría esperar de cualquier otra variable.fuente
__dict__
? Además, ¿hay alguna forma de automatizar esto? Sí, es solo una línea, pero es el tipo de cosa que es muy críptico de leer si no está familiarizado con los detalles sangrientos de las propiedades, etc.# declare size to not be a @property
No necesita asignación (a
size
) en absoluto.size
es una propiedad en la clase base, por lo que puede anular esa propiedad en la clase secundaria:Puede (micro) optimizar esto precalculando la raíz cuadrada:
fuente
size
es una propiedad y no un atributo de instancia, no tiene que hacer nada elegante.Parece que quieres definir
size
en tu clase:Otra opción es almacenar
cap
en su clase y calcularlo consize
definido como una propiedad (que anula la propiedad de la clase basesize
).fuente
Sugiero agregar un setter así:
De esta manera, puede anular la
.size
propiedad predeterminada de la siguiente manera:fuente
También puedes hacer lo siguiente
fuente
_size
ni estar_size_call
allí. Podría haber incorporado la llamada a la funciónsize
como una condición, y usartry... except...
para probar en_size
lugar de tomar una referencia de clase adicional que no se utilizará. Sin embargo, creo que esto es aún más críptico que la simplesize = None
sobrescritura en primer lugar.Creo que es posible establecer una propiedad de una clase base en otra propiedad de una clase derivada, dentro de la clase derivada y luego usar la propiedad de la clase base con un nuevo valor.
En el código inicial hay una especie de conflicto entre el nombre
size
de la clase base y el atributoself.size
de la clase derivada. Esto puede ser visible si reemplazamos el nombreself.size
de la clase derivada conself.length
. Esto generará:Entonces, si sustituimos el nombre del método
size
conlength
en todas las ocurrencias de todo el programa, esto dará lugar a la misma excepción:El código fijo, o de todos modos, una versión que de alguna manera funciona, es mantener el código exactamente igual, con la excepción de la clase
Square_Integers_Below
, que establecerá el métodosize
de la clase base en otro valor.Y luego, cuando ejecutamos todo el programa, la salida es:
Espero que esto haya sido útil de una forma u otra.
fuente