¿Una clase derivada tiene automáticamente todos los atributos de la clase base?

Parece que no hay buena documentación en línea sobre esto: si hago una clase derivada, ¿tendrá automáticamente todos los atributos de la clase base? Pero, ¿para qué BaseClass.__init() ? ¿También debe hacerlo con otros métodos de clase base? ¿ BaseClass.__init__() necesita argumentos? Si tiene argumentos para su clase base __init__() , ¿también los utiliza la clase derivada, necesita establecer explícitamente los argumentos en el __init__() la __init__() derivada, o establecerlos en BaseClass.__init__() lugar?

Si implementa __init__ en una clase derivada de BaseClass, se sobrescribirá el método __init__ heredado y, por BaseClass.__init__ , nunca se llamará BaseClass.__init__ . Si necesita llamar al método __init__ para BaseClass (como suele ser el caso), entonces depende de usted hacerlo y se hace explícitamente llamando a BaseClass.__init__ , normalmente desde el método __init__ recién implementado.

 class Foo(object): def __init__(self): self.a = 10 def do_something(self): print self.a class Bar(Foo): def __init__(self): self.b = 20 bar = Bar() bar.do_something() 

Esto causará el siguiente error:

 AttributeError: 'Bar' object has no attribute 'a' 

Por lo tanto, el método do_something se ha heredado como se esperaba, pero ese método requiere que se haya establecido el atributo a, que nunca se debe a que __init__ también se sobrescribió. Foo.__init__ esto llamando explícitamente a Foo.__init__ desde dentro de Bar.__init__ .

 class Foo(object): def __init__(self): self.a = 10 def do_something(self): print self.a class Bar(Foo): def __init__(self): Foo.__init__(self) self.b = 20 bar = Bar() bar.do_something() 

que imprime 10 como se esperaba. Foo.__init__ en este caso espera un solo argumento que es una instancia de Foo (que por convención se llama self ).

Normalmente, cuando llama a un método en una instancia de una clase, la instancia de la clase se pasa automáticamente como el primer argumento. Los métodos en una instancia de una clase se llaman métodos enlazados . bar.do_something es un ejemplo de un método enlazado (y bar.do_something que se llama sin ningún argumento). Foo.__init__ es un método Foo.__init__ porque no está vinculado a una instancia particular de Foo , por lo que el primer argumento, una instancia de Foo , debe pasarse explícitamente.

En nuestro caso, nos pasamos a Foo.__init__ , que es la instancia de Bar que se pasó al método __init__ en Bar . Como Bar hereda de Foo , las instancias de Bar también son instancias de Foo , por lo que self permite pasar self to Foo.__init__ .

Es probable que la clase de la que está heredando requiera o acepte más argumentos que solo una instancia de la clase. Estos se tratan como lo haría con cualquier método que esté llamando desde __init__ :

 class Foo(object): def __init__(self, a=10): self.a = a def do_something(self): print self.a class Bar(Foo): def __init__(self): Foo.__init__(self, 20) bar = Bar() bar.do_something() 

que imprimiría 20 .

Si está intentando implementar una interfaz que expone completamente todos los argumentos de inicialización de la clase base a través de su clase heredada, deberá hacerlo explícitamente. Esto normalmente se hace con los argumentos * args y ** kwargs (los nombres son por convención), que son marcadores de posición para el rest de los argumentos que no tienen un nombre explícito. El siguiente ejemplo hace uso de todo lo que he discutido:

 class Foo(object): def __init__(self, a, b=10): self.num = a * b def do_something(self): print self.num class Bar(Foo): def __init__(self, c=20, *args, **kwargs): Foo.__init__(self, *args, **kwargs) self.c = c def do_something(self): Foo.do_something(self) print self.c bar = Bar(40, a=15) bar.do_something() 

En este caso, el argumento c se establece en 40, ya que es el primer argumento de Bar.__init__ . El segundo argumento se incorpora a las variables args y kwargs (el * y ** es una syntax específica que dice expandir la lista / tupla o diccionario en argumentos separados cuando se pasa a una función / método), y se pasa a Foo.__init__ .

Este ejemplo también señala que cualquier método sobrescrito debe llamarse explícitamente si eso es lo que se requiere (como do_something es do_something en este caso).

Un último punto, a menudo verá que se super(ChildClass, self).method() (donde ChildClass es una clase secundaria arbitraria) que se usa en lugar de una llamada al método BaseClass explícitamente. La discusión de super es otra pregunta, pero basta con decir que, en estos casos, se suele utilizar para hacer exactamente lo que se hace llamando a BaseClass.method(self) . Brevemente, los super delegates de la llamada al método a la siguiente clase en el orden de resolución del método: el MRO (que en herencia simple es la clase principal). Consulte la documentación en super para más información.

Si hago una clase derivada, ¿tendrá automáticamente todos los atributos de la clase base?

Atributos de clase, sí. Atributos de instancia, no (simplemente porque no existen cuando se crea la clase), a menos que no haya __init__ en la clase derivada, en cuyo caso se llamará a la base y se establecerán los atributos de la instancia.

Hace BaseClass. init () necesitas argumentos?

Depende de la clase y su firma __init__ . Si está llamando explícitamente a Base.__init__ en la clase derivada, al menos necesitará pasar self como primer argumento. Si usted tiene

 class Base(object): def __init__(self): # something 

entonces es bastante obvio que ningún otro argumento es aceptado por el __init__ . Si tuvieras

 class Base(object): def __init__(self, argument): # something 

entonces tienes que pasar un argument al llamar a la base __init__ . No hay ciencia espacial aquí.

Si tiene argumentos para su clase base init (), si la clase derivada también los utiliza, ¿necesita establecer explícitamente los argumentos en el init () de la clasificación derivada o establecerlos en BaseClass? init () en su lugar?

Nuevamente, si la clase derivada no tiene __init__ , se usará una base en su lugar.

 class Base(object): def __init__(self, foo): print 'Base' class Derived(Base): pass Derived() # TypeError Derived(42) # prints Base 

En otro caso, necesitas cuidarlo de alguna manera. Si utiliza *args, **kwargs y simplemente pasa argumentos no modificados a la clase base, o copia la firma de la clase base, o suministra argumentos de otros lugares, depende de lo que esté intentando lograr.