programacion - Python, anulando un método de clase heredado
poo python 3 (5)
Tengo dos clases, Field
y Background
. Se ven un poco así:
class Field( object ):
def __init__( self, a, b ):
self.a = a
self.b = b
self.field = self.buildField()
def buildField( self ):
field = [0,0,0]
return field
class Background( Field ):
def __init__( self, a, b, c ):
super(Background, self).__init__( a, b )
self.field = self.buildField( c )
def buildField( self, c ):
field = [c]
return field
a, b, c = 0, 1, 2
background = Background( a, b, c )
Este error está apuntando a buildField()
Field:
"TypeError: buildField() takes exactly 2 arguments (1 given)."
Esperaba que init init () se llamara primero. Para pasar "a, b" a Fields init (), Field para asignar a y b para asignar una lista con tres 0 en el campo. Luego, para que init () continúe, llame a su propia buildField () y anule self.field con una lista que contenga c.
Parece que no entiendo totalmente super (), sin embargo, no pude encontrar una solución a mi problema después de ver problemas de herencia similares en la web y por aquí.
Esperaba un comportamiento como c ++ en el que una clase puede anular un método heredado. ¿Cómo puedo lograr esto o algo similar?
La mayoría de los problemas que encontré relacionados con esto fueron personas que usan doble guión bajo. Mi experiencia con la herencia con super está usando la clase heredada init () para simplemente pasar diferentes variables a la superclase. Nada que implique sobrescribir nada.
Esperaba que se llamara Background init ()
En realidad, se está llamando a Background init()
.
Pero eche un vistazo a su clase de fondo ...
class Background( Field ):
def __init__( self, a, b, c ):
super(Background, self).__init__( a, b )
self.field = self.buildField( c )
Entonces, la primera afirmación de __init__
es invocando el método de inicio de __init__
super class(Field)
... y pasando al self
como argumento. Ahora este self
es en realidad una referencia de la Background class
de Background class
...
Ahora en tu clase de campo: -
class Field( object ):
def __init__( self, a, b ):
print self.__class__ // Prints `<class ''__main__.Background''>`
self.a = a
self.b = b
self.field = self.buildField()
Tu método buildField()
está invocando el de la clase Background. Esto es porque, el self
aquí es una instancia de la clase Background
(prueba a imprimir self.__class__
en tu método __init__
de la Field class
). Al pasarlo mientras invocaste el método __init__
, de la clase de Background
...
Es por eso que estás recibiendo un error ...
El error "TypeError: buildField () toma exactamente 2 argumentos (1 dado).
Como no está pasando ningún valor ... Entonces, solo el valor pasado es el self
implícito.
Esperaba que se llamara Background init (). Para pasar "a, b" a Campos init (), Campo para asignar a y b
Hasta aquí todo bien.
luego asignar una lista con tres 0 en el campo.
Ah. Aquí es donde obtenemos el error.
self.field = self.buildField()
Aunque esta línea ocurre dentro de Field.__init__
, self
es una instancia de Background
. así que self.buildField
encuentra el método buildField
, no Field
''s.
Dado que Background.buildField
espera 2 argumentos en lugar de 1,
self.field = self.buildField()
provoca un error
Entonces, ¿cómo le decimos a Python que llame Field
método buildField
Field
lugar de Background
?
El propósito del mangle de nombres (nombrar un atributo con dobles guiones bajos) es resolver este problema exacto.
class Field(object):
def __init__(self, a, b):
self.a = a
self.b = b
self.field = self.__buildField()
def __buildField(self):
field = [0,0,0]
return field
class Background(Field):
def __init__(self, a, b, c):
super(Background, self).__init__(a, b)
self.field = self.__buildField(c)
def __buildField(self, c):
field = [c]
return field
a, b, c = 0, 1, 2
background = Background(a, b, c)
El nombre del método __buildField
está "destrozado" en _Field__buildField
dentro del Field
así que dentro del Field.__init__
,
self.field = self.__buildField()
llama a self._Field__buildField()
, que es Field
método __buildField
. Mientras que de manera similar,
self.field = self.__buildField(c)
dentro de Background.__init__
se __buildField
método __buildField
.
El super(Background, self).__init__( a, b )
invocará:
def __init__( self, a, b ):
self.a = a
self.b = b
self.field = self.buildField()
en el Field
. Sin embargo, self
aquí se refiere a la instancia de background
, y self.buildField()
está de hecho llamando a buildField()
de Background
, por lo que obtienes ese error.
Parece ser que su código debería escribirse mejor como:
class Field( object ):
def __init__( self, a, b ):
self.a = a
self.b = b
self.field = Field.buildField()
@classmethod
def buildField(cls):
field = [0,0,0]
return field
class Background( Field ):
def __init__( self, a, b, c ):
super(Background, self).__init__(a, b)
self.field = Background.buildField(c)
@classmethod
def buildField(cls,c):
field = [c]
return field
a, b, c = 0, 1, 2
background = Background( a, b, c )
Si no puede permitir que el constructor base finalice, indica que el diseño es defectuoso.
Por lo tanto, es mucho mejor separar buildField()
para pertenecer a la clase utilizando classmethod
decorator o staticmethod
, si tiene que llamar a estos métodos en su constructor.
Sin embargo, si el constructor de su clase base no invoca ningún método de instancia desde adentro, puede sobrescribir con seguridad cualquier método de esta clase base.
Se habla de Overriding
pero me parece que chaining constructors or (methods)
Y también suena como sobreescribir las propiedades:
Dejame explicar:
Una propiedad llamada campo se inicializará como
[0,0,0]
.@property
decoradores@property
ven mejor.Luego, la clase de
Background
sobrescribe esta propiedad.
Solución rápida y sucia
No conozco la lógica de su negocio, pero a veces __init__
método __init__
la __init__
me dio más control:
#!/usr/bin/env python
class Field( object ):
def __init__( self, a, b ):
self.a = a
self.b = b
self.field = self.buildField()
def buildField( self ):
field = [0,0,0]
return field
class Background( Field ):
def __init__( self, a, b, c ):
# super(Background, self).__init__( a, b )
# Unfortunately you should repeat or move initializing a and b
# properties here
self.a = a
self.b = b
self.field = self.buildField( c )
def buildField( self, c ):
# You can access super class methods
assert super(Background, self).buildField() == [0,0,0]
field = [c]
return field
a, b, c = 0, 1, 2
bg = Background(a,b,c)
assert bg.field == [2]
Usando propiedades
Tiene una sintaxis más limpia.
#!/usr/bin/env python
class Field( object ):
@property
def field(self):
return [0,0,0]
def __init__( self, a, b ):
self.a = a
self.b = b
class Background( Field ):
def __init__( self, a, b, c ):
super(Background, self).__init__( a, b )
self.c = c
assert (self.a, self.b, self.c) == (0,1,2) # We assigned a and b in
# super class''s __init__ method
assert super(Background, self).field == [0,0,0]
assert self.field == [2]
@property
def field(self):
return [self.c]
a, b, c = 0, 1, 2
background = Background( a, b, c )
print background.field
Viniendo desde una perspectiva de C ++, puede haber dos conceptos erróneos aquí.
En primer lugar, anular un método con una firma diferente no lo sobrecarga como en C ++. Si uno de tus objetos de fondo intenta llamar a buildField sin argumentos, no se invocará la versión original de Field, que se ha ocultado por completo.
El segundo problema es que si un método definido en la superclase llama a buildField, se llamará a la versión de la subclase. En python, todos los métodos están vinculados dinámicamente, como un método virtual
C ++.
La __init__
de __init__
esperaba estar tratando con un objeto que tenía un método buildField sin argumentos. Usaste el método con un objeto que tiene un método buildField tomando un argumento.
Lo que ocurre con el super
es que no cambia el tipo de objeto, por lo que no debe cambiar la firma de ningún método que puedan llamar los métodos de la superclase.