2011-07-27 10 views
5

tengo el siguiente código:La herencia múltiple y usando un método de una de las clases base

class A(object): 
    def __init__(self): 
     self.name = "A" 
     super(A, self).__init__() 

    def Update(self): 
     print "Update A" 
     self.PickTarget() 

    def PickTarget(self): 
     print "PickTarget A" 

class B(object): 
    def __init__(self): 
     self.name = "B" 
     super(B, self).__init__() 

    def Update(self): 
     print "Update B" 
     self.PickTarget() 

    def PickTarget(self): 
     print "PickTarget B" 

class C(A, B): 
    def __init__(self): 
     super(C, self).__init__() 

    def Update(self, useA): 
     if useA: 
      A.Update(self) 
     else: 
      B.Update(self) 

c = C() 

c.Update(useA = True) 
# prints: 
# Update A 
# PickTarget A 

c.Update(useA = False) 
# prints: 
# Update B 
# PickTarget A 

¿Por qué llamar C.Update con useA=False todavía poner en A.PickTarget? ¿Cómo puedo hacer que esto funcione como quiero que funcione (es decir, B.Update siempre llama a B.PickTarget)? Estoy seguro de que esto se ha preguntado antes, pero mi búsqueda no arrojó nada, probablemente porque no sé qué buscar.

+0

Aunque todavía creo que esto puede requerir un rediseño, señalaré que la creación de nombres ofrece una solución bastante flexible para este problema. Ver [mi edición] (http://stackoverflow.com/questions/6849519/multiple-inheritance-and-using-a-method-of-one-of-the-base-classes/6849613#6849613). – senderle

Respuesta

7

Es porque A es anterior a B en las clases base de C. Para obtener este comportamiento, debe usar B.PickTarget(self) en lugar de self.PickTarget() en B.Update(self). De lo contrario, cambie A y B en la definición de C.

Editar:

Si el comportamiento previsto es para B que siempre llamar a métodos en B y para A a llamar siempre a métodos en A, es correcta utilizar A.method(self) en lugar de self.method(), como la segunda forma doesn' t implica que method está en A.

Debe volver a diseñar sus clases. A debe tener un método de movimiento que mueva al robot al azar y defina que es otro comportamiento básico. B debe ser una subclase de A y debe tener un método de movimiento que llame al super(B, self).move() si no tiene una ruta, y de lo contrario se mueve en la ruta. Esta es la forma correcta de anular un método en una condición.

+0

¿No hay forma de que esto se pueda hacer sin modificar A y B? Este es solo un código de ejemplo: en el código real, A y B también se usan de forma independiente. (El código real es una IA simple para robots: A hace que el robot se mueva aleatoriamente, B hace que el robot siga un camino, C verifica si el robot tiene una ruta; si no, llama a A.Actualizar, si es así, llama a B .Update. Si hay mejores formas de lidiar con esto, soy todo oídos.) – combatdave

+0

Como dije, puedes simplemente cambiar 'A' y' B' en la definición de 'C'. Además, parece que su comportamiento previsto es que 'B' llame siempre a los métodos en' B' y que 'A' siempre llame a los métodos en' A', en cuyo caso __correcta__ para usar 'A.method (self)' de 'self.method()', ya que la segunda forma no implica que 'method' esté en' A'. – agf

+0

¡Ya veo! Ahora esto está empezando a tener sentido. ¿Cómo funciona esto para acceder a los atributos en la clase? Por ejemplo, accediendo a self.name dentro de A.Update - ¿debería ser self.name o A.name? – combatdave

2

Cada vez que se llama a un método de un objeto, python usa el "Orden de resolución de método" (MRO) para determinar a qué versión del método llamar. En este caso, aunque ha llamado explícitamente al A.Update(), A.Update() no llama explícitamente al A.PickTarget. Simplemente llama al self.PickTarget(). Como este es un objeto C, es equivalente a C.PickTarget(self). C.PickTarget() se hereda, y el MRO C dicta en este caso que A.PickTarget es la versión de PickTarget que se debe utilizar.

Usted puede mirar en el MRO de C así:

>>> C.__mro__ 
(<class '__main__.C'>, <class 'foo.A'>, <class 'foo.B'>, <type 'object'>) 

Hay un artículo súper informativo sobre el MRO here.


cuanto a cómo conseguir el comportamiento que desea - Bueno, hay un montón de maneras bastante obvias, y al mismo tiempo, no hay buenos unos (que puede pensar). No creo que este sea realmente un buen diseño. El punto principal de la herencia múltiple es que puede mezclar y combinar métodos ortogonales en C, pero está tratando de meter varias versiones de métodos más o menos similares, dos de A y dos de B, en una sola clase. Si nos dice más sobre lo que está haciendo, tal vez podamos sugerir una mejor solución. (También está cambiando la firma de su método manteniendo el mismo nombre. Eso también me parece poco fiable.)

Si está seguro de que desea hacer esto, sin embargo, otro enfoque que podría considerar es name mangling, que está diseñado exactamente para casos como este. Esto hace exactamente lo que quiere:

class A(object): 
    def __init__(self): 
     self.name = "A" 
     super(A, self).__init__() 

    def Update(self): 
     print "Update A" 
     self.__PickTarget() 

    def PickTarget(self): 
     print "PickTarget A" 

    __PickTarget = PickTarget 

class B(object): 
    def __init__(self): 
     self.name = "B" 
     super(B, self).__init__() 

    def Update(self): 
     print "Update B" 
     self.__PickTarget() 

    def PickTarget(self): 
     print "PickTarget B" 

    __PickTarget = PickTarget 

class C(A, B): 
    def __init__(self): 
     super(C, self).__init__() 

    def Update(self, useA): 
     if useA: 
      A.Update(self) 
     else: 
      B.Update(self) 

Salida:

>>> from mangling import A, B, C 
>>> c = C() 
>>> c.Update(useA = True) 
Update A 
PickTarget A 
>>> c.Update(useA = False) 
Update B 
PickTarget B 
+0

Gracias por la explicación de por qué sucede esto. He intentado establecer self.PickTarget en C.Update() a A.PickTarget o B.PickTarget, pero esto arroja "TypeError: método de unión libre PickTarget() debe llamarse con una instancia como primer argumento (no obtuvo nada en su lugar)" – combatdave

+0

@combatdave, no entiendo muy bien a qué te refieres con "configurar self.PickTarget en C.Update() en A.PickTarget o B.PickTarget". ¿Quiere decir que está haciendo 'self.PickTarget = A.PickTarget'? Porque eso está súper mal. 'A.PickTarget' es un método independiente, por lo que ahora ya no está obligado a' self ', y debes pasar 'self' explícitamente. – senderle

+0

Sí, quise decir eso - me imaginé que estaba mal;) Gracias por la publicación - esto me ayudó a entender lo que está pasando, pero la publicación de @agf solucionó mi problema :) – combatdave

0

Ésta no es la solución, pero si cambia el método actualización de C a:

def update(self, useA): 
    if useA: 
     A().update() 
    else: 
     B().update() 

No funciona como esperabas

Debería leer sobre la Orden de resolución de métodos de Python para poder trabajar con cosas como esta correctamente.

+1

Eso está creando una nueva instancia de la clase base para llamar a Update(), que no es útil. – combatdave

+0

Luego prueba el interruptor A para B en la definición de C. EDITAR: Además, si intenta imprimir C.name, verá lo que está sucediendo. – toqueteos

Cuestiones relacionadas