2009-07-04 14 views
8

En Python, me gustaría construir una instancia de la clase de Child directamente desde una instancia de la clase Parent. Por ejemplo:Heredar de instancia en Python

A = Parent(x, y, z) 
B = Child(A) 

Este es un truco que pensé que podría funcionar:

class Parent(object): 

    def __init__(self, x, y, z): 
     print "INITILIZING PARENT" 
     self.x = x 
     self.y = y 
     self.z = z 

class Child(Parent): 

    def __new__(cls, *args, **kwds): 
     print "NEW'ING CHILD" 
     if len(args) == 1 and str(type(args[0])) == "<class '__main__.Parent'>": 
      new_args = [] 
      new_args.extend([args[0].x, args[0].y, args[0].z]) 
      print "HIJACKING" 
      return Child(*new_args) 
     print "RETURNING FROM NEW IN CHILD" 
     return object.__new__(cls, *args, **kwds) 

Pero cuando corro

B = Child(A) 

me sale:

NEW'ING CHILD 
HIJACKING 
NEW'ING CHILD 
RETURNING FROM NEW IN CHILD 
INITILIZING PARENT 
Traceback (most recent call last): 
    File "classes.py", line 52, in <module> 
    B = Child(A) 
TypeError: __init__() takes exactly 4 arguments (2 given) 

Parece el truco funciona tal como esperaba pero el compilador arroja un T ypeError al final. Me preguntaba si podría sobrecargar TypeError para hacer que ignore el modismo B = Child (A), pero no estaba seguro de cómo hacerlo. En cualquier caso, ¿podría darme sus soluciones para heredar de instancias?

Gracias!

+1

Solo un FYI, "inicialización" se escribe con una 'a'. Intenté editar esto pero StackOverflow no permitirá ediciones de menos de 6 letras. – cfwschmidt

Respuesta

7

Una vez en clase __new__Child devuelve una instancia de Child, Child.__init__ se llamará (con los mismos argumentos __new__ fue dada) en esa instancia - y al parecer sólo se hereda Parent.__init__, que no toma bien al ser llamado con sólo una arg (la otra Parent, A).

Si no hay otra forma en que un Child se puede hacer, se puede definir un Child.__init__ que acepta cualquiera arg (que no tiene en cuenta) o tres (en cuyo caso se llama Parent.__init__). ¡Pero es más simple renunciar a __new__ y tener toda la lógica en Child.__init__, simplemente llamando al Parent.__init__ de manera apropiada!

para hacer de este hormigón con un ejemplo de código:

class Parent(object): 

    def __init__(self, x, y, z): 
     print "INITIALIZING PARENT" 
     self.x = x 
     self.y = y 
     self.z = z 

    def __str__(self): 
     return "%s(%r, %r, %r)" % (self.__class__.__name__, 
      self.x, self.y, self.z) 


class Child(Parent): 

    _sentinel = object() 

    def __init__(self, x, y=_sentinel, z=_sentinel): 
     print "INITIALIZING CHILD" 
     if y is self._sentinel and z is self._sentinel: 
      print "HIJACKING" 
      z = x.z; y = x.y; x = x.x 
     Parent.__init__(self, x, y, z) 
     print "CHILD IS DONE!" 

p0 = Parent(1, 2, 3) 
print p0 
c1 = Child(p0) 
print c1 
c2 = Child(4, 5, 6) 
print c2 
3

Aún no se define un constructor (init) para el niño, por lo que el constructor se llama Padres, esperando 4 argumentos, mientras que sólo 2 se pasan en (de nuevo). Aquí hay una forma de lograr lo que desea:

class Child(Parent): 
    def __init__(self, *args, **kwargs): 
     if len(args) == 1 and isinstance(args[0], Parent): 
      Parent.__init__(self, args[0].x, args[0].y, args[0].z) 

     else: 
      # do something else 
+0

+1: Simplemente crea un elemento secundario copiando los atributos del elemento principal. –

0

Gracias, muchachos, ¡eso fue rápido! La primera vez que leí el comentario de Alex y Reescribí del Niño __init__ como

def __init__(self, *args, **kwds): 
    if len(args) == 1 and str(type(args[0])) == "<class '__main__.Parent'>": 
     new_args = [args[0].x, args[0].y, args[0].z] 
     super(Child, self).__init__(*new_args, **kwds) 
    else: 
     super(Child, self).__init__(*args, **kwds) 

que es muy similar a lo sugerido abhinavg (como me acabo de enterar). Y funciona. Sólo su línea y la de ars '

if len(args) == 1 and isinstance(args[0], Parent): 

es más limpio que el mío.

Gracias otra vez !!

5

OK, así que no me di cuenta de que estaba contento con una copia estática de los argumentos hasta que ya estaba medio hecho con mi solución. Pero decidí no desperdiciarlo, así que aquí está de todos modos. La diferencia con las otras soluciones es que realmente obtendrá los atributos del padre incluso si se actualizaron.

_marker = object() 

class Parent(object): 

    def __init__(self, x, y, z): 
     self.x = x 
     self.y = y 
     self.z = z 

class Child(Parent): 

    _inherited = ['x', 'y', 'z'] 

    def __init__(self, parent): 
     self._parent = parent 
     self.a = "not got from dad" 

    def __getattr__(self, name, default=_marker): 
     if name in self._inherited: 
      # Get it from papa: 
      try: 
       return getattr(self._parent, name) 
      except AttributeError: 
       if default is _marker: 
        raise 
       return default 

     if name not in self.__dict__: 
      raise AttributeError(name) 
     return self.__dict__[name] 

Ahora bien, si hacemos esto:

>>> A = Parent('gotten', 'from', 'dad') 
>>> B = Child(A) 
>>> print "a, b and c is", B.x, B.y, B.z 
a, b and c is gotten from dad 

>>> print "But x is", B.a 
But x is not got from dad 

>>> A.x = "updated!" 
>>> print "And the child also gets", B.x 
And the child also gets updated! 

>>> print B.doesnotexist 
Traceback (most recent call last): 
    File "acq.py", line 44, in <module> 
    print B.doesnotexist 
    File "acq.py", line 32, in __getattr__ 
    raise AttributeError(name) 
AttributeError: doesnotexist 

Para una versión más genérica de esto, observamos el paquete http://pypi.python.org/pypi/Acquisition. De hecho, en algunos casos es una sangrienta solución de necesidad.

2

Sé que este es un hilo muy antiguo, pero recientemente me encontré con el mismo desafío que Alexandra, y este fue el tema más relavent que pude encontrar. Tuve una clase para padres con muchos, muchos atributos, y quise esencialmente 'parchear' una instancia de la misma, manteniendo todos sus métodos y atributos, añadiendo algunos y modificando/sobreescribiendo otros. Una subclase simple no funcionaría porque el usuario completaría los atributos durante el tiempo de ejecución y no podría heredar los valores predeterminados de la clase principal. Después de muchos retoques, encontré una forma muy limpia (aunque bastante hacky) de hacerlo usando __new__. He aquí un ejemplo:

class Parent(object): 
    def __init__(self): 
     # whatever you want here 
     self.x = 42 
     self.y = 5 
    def f(self): 
     print "Parent class, x,y =", self.x, self.y 

class Child(Parent): 
    def __new__(cls, parentInst): 
     parentInst.__class__ = Child 
     return parentInst 
    def __init__(self, parentInst): 
     # You don't call the Parent's init method here 
     self.y = 10 
    def f(self): 
     print "Child class, x,y =", self.x, self.y 

c = Parent() 
c.f() # Parent class, x,y = 42 5 
c.x = 13 
c.f() # Parent class, x,y = 13 5 
c = Child(c) 
c.f() # Child class, x,y = 13 10 

La única parte especial está cambiando el atributo __class__ de la clase padre en el constructor del niño. Debido a esto, se llamará al método __init__ del niño como de costumbre, y que yo sepa, la clase Child debería funcionar exactamente como cualquier otra clase heredada.

+1

También encontré algo similar: http://stackoverflow.com/questions/14177788/init-child-with-parent-instance – elyase

2

Me parece (encapsulación) para ser la manera más limpia:

class Child(object): 
    def __init__(self): 
     self.obj = Parent() 

    def __getattr__(self, attr): 
     return getattr(self.obj, attr) 

esta manera se puede utilizar el método de todos los padres y su propio sin caer en los problemas de la herencia.

Cuestiones relacionadas