Método Resolución de orden en caso de clases base que tienen diferentes parámetros de inicio

Estoy tratando de entender MRO en Python. Aunque hay varias publicaciones aquí, no estoy obteniendo particularmente lo que quiero. Considere dos clases A y B derivadas de BaseClass , cada una con un __init__ toma __init__ diferentes.

 class BaseClass(object): def __init__(self): print "I am the base class" class A(BaseClass): def __init__(self, something, anotherthing): super(A, self).__init__() self.something = something self.anotherthing = anotherthing def methodsA(self): ... class B(BaseClass): def __init__(self, someOtherThing): super(B, self).__init__() self.someOtherThing = someOtherThing def methodsB(self): ... 

La pregunta es, si necesito derivar una Tercera Clase C de A y B , ¿cómo puedo inicializar __init__ , si tengo que hacerlo? Puedo derivar con seguridad C de B o A

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

La implementación anterior me da un error.

Como mencionó jonrsharpe al final de su publicación, la mejor manera que he encontrado para manejar este tipo de situación es aceptar **kwargs y extraer argumentos nombrados explícitamente.

 class BaseClass(object): def __init__(self, **kwargs): print("BaseClass.__init__({},{})".format('', kwargs)) super(BaseClass,self).__init__(**kwargs) class A(BaseClass): def __init__(self, **kwargs): print("A.__init__({},{})".format('', kwargs)) a = kwargs.pop('something') super(A,self).__init__(**kwargs) class B(BaseClass): def __init__(self, **kwargs): print("B.__init__({},{})".format('', kwargs)) b = kwargs.pop('anotherthing') super(B,self).__init__(**kwargs) class C(A, B): def __init__(self, **kwargs): print("C.__init__({},{})".format('', kwargs)) super(C,self).__init__(**kwargs) c = C(something=1,anotherthing='a') 

Los argumentos que deben extraerse deben pasarse en nombrado, para que aparezcan en kwargs.

Incluso puede aceptar explícitamente solo los argumentos con nombre omitiendo los *args argumentos como en el ejemplo, por lo que se encuentra con un TypeError si olvida.

EDITAR:

Después de pensarlo un poco, me doy cuenta de que mi ejemplo es muy específico para su ejemplo, y si introduce otra clase o cambia la herencia, se puede romper. Hay dos cosas que deben abordarse para hacer esto más general:

BaseClass no llama super.

Para el ejemplo, esto no importa, pero si se introduce otra clase, el MRO podría cambiar de tal manera que haya una clase después de BaseClass y, por lo tanto, debería llamarse super. Esto lleva a la segunda cuestión:

object.__init__() no toma parámetros

Si queremos que las clases (BaseClass específicamente) sean seguras para colocarlas en una estructura genérica de herencia múltiple donde su superclamada podría enviarse a otra clase u objeto, debemos quitar los argumentos de Kwargs cuando los consummos.

Sin embargo, esto agrega otra complicación, ya que requiere que no haya dos funciones __init__ que compartan el mismo nombre de parámetro. Creo que la conclusión es que hacer que la herencia múltiple funcione de manera general es difícil.

Aquí hay un artículo interesante (encontrado a través de google) sobre algunos de los detalles: artículo

Para entender esto, intente sin ningún argumento:

 class BaseClass(object): def __init__(self): print("BaseClass.__init__") class A(BaseClass): def __init__(self): print("A.__init__") super(A, self).__init__() class B(BaseClass): def __init__(self): print("B.__init__") super(B, self).__init__() class C(A, B): def __init__(self): print("C.__init__") super(C, self).__init__() 

Cuando ejecutamos esto:

 >>> c = C() C.__init__ A.__init__ B.__init__ BaseClass.__init__ 

Esto es lo que hace super : se asegura de que todo se llame, en el orden correcto, sin duplicación. C hereda de A y B , por lo que ambos de sus métodos __init__ deben ser llamados, y ambos heredan de BaseClass , por lo que también debe llamarse __init__ , pero solo una vez.

Si los métodos __init__ que se llaman toman diferentes argumentos, y no pueden lidiar con argumentos adicionales (por ejemplo, *args, **kwargs ), obtienes el TypeError te refieres. Para solucionar esto, debe asegurarse de que todos los métodos puedan manejar los argumentos apropiados.

Creo que no puedes usar super para esto. Tendrás que usar el “estilo antiguo”:

 class C(A,B): def __init__(self, something, anotherthing, someOtherThing): A.__init__(self, something, anotherthing) B.__init__(self, someOtherThing) 

Si bien la respuesta de bj0 es correcta, la extracción manual de los argumentos de kwargs es más complicada y torpe de lo necesario. También significa que no detectará cuándo se pasan argumentos adicionales a uno de los constructores de clase.

La mejor solución es aceptar **kwargs , pero solo usarlo para transmitir cualquier argumento desconocido. Cuando esto scope el object (base de BaseClass ), generará un error si hubiera argumentos innecesarios:

 class BaseClass(object): def __init__(self, **kwargs): super(BaseClass, self).__init__(**kwargs) # always try to pass on unknown args class A(BaseClass): def __init__(self, something, anotherthing, **kwargs): # take known arguments super(A, self).__init__(**kwargs) # pass on the arguments we don't understand self.something = something self.anotherthing = anotherthing class B(BaseClass): def __init__(self, someOtherThing, **kwargs): # same here super(B, self).__init__(**kwargs) # and here self.someOtherThing = someOtherThing class C(A, B): # this will work, with someOtherThing passed from A.__init__ to B.__init__ pass class D(B, A): # this will also work, with B.__init__ passing on A.__init__'s arguments pass import threading class E(C, threading.Thread): # keyword arguments for Thread.__init__ will work! def run(self): print(self.something, self.anotherthing, self.someOtherThing) 

Si una de sus clases modifica (o proporciona un valor predeterminado para) un argumento que también es usado por una de sus clases básicas, puede tomar un parámetro específico y pasarlo por palabra clave:

 class F(C): def __init__(self, something, **kwargs): super(F, self).__init__(something="foo"+something, **kwargs) 

Debe llamar a todos sus constructores con solo argumentos de palabras clave, no posicionales. Por ejemplo:

 f = F(something="something", anotherthing="bar", someOtherThing="baz") 

Es posible admitir algo similar para los argumentos posicionales, pero generalmente es una mala idea porque no se puede contar con el orden de los argumentos. Si solo tenía una clase que tomó argumentos posicionales (quizás un número desconocido de ellos en *args ), probablemente podría hacer que ese trabajo pase *args dentro y fuera de cada método __init__ , pero varias clases que toman diferentes argumentos posicionales están pidiendo problemas debido al orden en el que aparecen, posiblemente cambiando a medida que se realiza la herencia múltiple.

Gracias a todos por ayudarme a entender MRO. A continuación se muestra mi código completo junto con la salida. Espero que esto también ayude a otros.

clase BaseClass (objeto):

  def __init__(self, *args, **kwargs): self.name = kwargs.get('name') def printName(self): print "I am called from BaseClass" print self.name def setName(self, givenName): print "I am called from BaseClass" self.name=givenName def CalledFromThirdGen(self): print "I am called from BaseClass and invoked from Third Generation Derived Class" 

clase FirstGenDerived (BaseClass):

  def __init__(self, *args, **kwargs): super(FirstGenDerived, self).__init__(*args, **kwargs) self.name = kwargs.get('name') self.FamilyName = kwargs.get('FamilyName') def printFullName(self): print "I am called from FirstDerivedClass" print self.name + ' ' + self.FamilyName def printName(self): print "I am called from FirstDerivedClass, although I was present in BaseClass" print "His Highness " + self.name + ' ' + self.FamilyName 

clase SecondGenDerived (BaseClass):

  def __init__(self, *args, **kwargs): super(SecondGenDerived, self).__init__(*args, **kwargs) self.name = kwargs.get('name') self.middleName = kwargs.get('middleName') self.FamilyName = kwargs.get('FamilyName') def printWholeName(self): print "I am called from SecondDerivedClass" print self.name + ' ' + self.middleName + ' ' + self.FamilyName def printName(self): print "I am called from SecondDerivedClass, although I was present in BaseClass" print "Sir " + self.name + ' ' + self.middleName + ' ' + self.FamilyName 

clase ThirdGenDerived (FirstGenDerived, SecondGenDerived):

  def __init__(self, *args, **kwargs): super(ThirdGenDerived, self).__init__(*args, **kwargs) 

si nombre == ” principal “:

  print "Executing BaseClass" BaseClass(name='Robin').printName() print "Executing Instance of BaseClass with SetName \n" Instance = BaseClass() Instance.setName("Little John") Instance.printName() print "################################################\n" print "Executing FirstGenDerived with printName and printFullName\n" FirstGenDerived(name='Robin', FamilyName='Hood').printFullName() FirstGenDerived(name='Robin', FamilyName='Hood').printName() print "################################################\n" print "Executing FirstGenderived with instance\n" Instance2 = FirstGenDerived(name=None, FamilyName="Hood") Instance2.setName("Edwards") Instance2.printFullName() print "################################################\n" print "Executing SecondGenDerived with printName and printWholeName\n" SecondGenDerived(name='Robin', FamilyName='Hood', middleName='Williams').printWholeName() SecondGenDerived(name='Robin', FamilyName='Hood', middleName='Williams').printName() print "################################################\n" print "Executing ThirdGenDerived\n" ThirdGenDerived(name='Robin', FamilyName='Hood', middleName='Williams').CalledFromThirdGen() ThirdGenDerived(name='Robin', FamilyName='Hood', middleName='Williams').printName() print "################################################\n" 

Salida: Ejecutando BaseClass Me llaman desde BaseClass Robin Ejecutando la instancia de BaseClass con SetName

Me llamaron de BaseClass. Me llamaron de BaseClass Little John.

Ejecutando FirstGenDerived con printName y printFullName

Me llamaron de FirstDerivedClass Robin Hood Me llamaron de FirstDerivedClass, aunque estuve presente en BaseClass His Highness Robin Hood

Ejecutando FirstGenderived con instancia

Me llamaron de BaseClass. Me llamaron de FirstDerivedClass Edwards Hood.

Ejecutando SecondGenDerived con printName y printWholeName

Me llamaron de SecondDerivedClass Robin Williams Hood Me llamaron de SecondDerivedClass, aunque estuve presente en BaseClass Sir Robin Williams Hood

Ejecutando ThirdGenDerived

Me llamaron de BaseClass y me invocaron de Tercera Generación Clase Derivada Me llamaron de FirstDerivedClass, aunque estuve presente en BaseClass Su Alteza Robin Hood