Herencia y anulación de __init__ en python

129

Estaba leyendo 'Sumérgete en Python' y en el capítulo sobre clases da este ejemplo:

class FileInfo(UserDict):
    "store file metadata"
    def __init__(self, filename=None):
        UserDict.__init__(self)
        self["name"] = filename

Luego, el autor dice que si desea anular el __init__método, debe llamar explícitamente al padre __init__con los parámetros correctos.

  1. ¿Qué pasa si esa FileInfoclase tenía más de una clase de antepasados?
    • ¿Tengo que llamar explícitamente a todos los __init__métodos de las clases ancestrales ?
  2. Además, ¿tengo que hacer esto con algún otro método que quiera anular?
liewl
fuente
3
Tenga en cuenta que la sobrecarga es un concepto separado de la anulación.
Dana the Sane

Respuestas:

158

El libro está un poco anticuado con respecto a las llamadas de subclase-superclase. También está un poco anticuado con respecto a la subclase de clases incorporadas.

Se ve así hoy en día:

class FileInfo(dict):
    """store file metadata"""
    def __init__(self, filename=None):
        super(FileInfo, self).__init__()
        self["name"] = filename

Tenga en cuenta lo siguiente:

  1. Podemos subclase directamente incorporado en las clases, como dict, list, tuple, etc.

  2. La superfunción maneja el rastreo de las superclases de esta clase y llama a las funciones de manera apropiada.

S.Lott
fuente
55
¿Debo buscar un mejor libro / tutorial?
liewl
2
Entonces, en el caso de herencia múltiple, ¿super () los rastrea a todos en su nombre?
Dana el
dict .__ init __ (self), en realidad, pero no tiene nada de malo: la llamada super (...) solo proporciona una sintaxis más coherente. (No estoy seguro de cómo funciona para la herencia múltiple, creo que solo puede encontrar una superclase init )
David Z
44
La intención de super () es que maneja la herencia múltiple. La desventaja es que, en la práctica, la herencia múltiple aún se rompe muy fácilmente (ver < fuhm.net/super-harmful ).
algo
2
Sí, en caso de herencia múltiple y clases base que toman argumentos de constructor, generalmente se encuentra llamando a los constructores manualmente.
Torsten Marek
18

En cada clase de la que necesita heredar, puede ejecutar un bucle de cada clase que necesita iniciarse al iniciar la clase secundaria ... un ejemplo que puede copiarse podría entenderse mejor ...

class Female_Grandparent:
    def __init__(self):
        self.grandma_name = 'Grandma'

class Male_Grandparent:
    def __init__(self):
        self.grandpa_name = 'Grandpa'

class Parent(Female_Grandparent, Male_Grandparent):
    def __init__(self):
        Female_Grandparent.__init__(self)
        Male_Grandparent.__init__(self)

        self.parent_name = 'Parent Class'

class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
#---------------------------------------------------------------------------------------#
        for cls in Parent.__bases__: # This block grabs the classes of the child
             cls.__init__(self)      # class (which is named 'Parent' in this case), 
                                     # and iterates through them, initiating each one.
                                     # The result is that each parent, of each child,
                                     # is automatically handled upon initiation of the 
                                     # dependent class. WOOT WOOT! :D
#---------------------------------------------------------------------------------------#



g = Female_Grandparent()
print g.grandma_name

p = Parent()
print p.grandma_name

child = Child()

print child.grandma_name
Código de error
fuente
2
No parece que Child.__init__sea ​​necesario el bucle for . Cuando lo elimino del ejemplo, mi hijo todavía imprime "Abuela". ¿El abuelo init no es manejado por la Parentclase?
Adam
44
Creo que los init de los abuelos ya son manejados por el init de Parent, ¿no?
johk95
15

Realmente no tiene que llamar a los __init__métodos de la (s) clase (s) base (s), pero generalmente desea hacerlo porque las clases base harán algunas inicializaciones importantes allí que son necesarias para que el resto de los métodos de clases funcionen.

Para otros métodos, depende de tus intenciones. Si solo desea agregar algo al comportamiento de las clases base, deberá llamar al método de las clases base adicionalmente a su propio código. Si desea cambiar fundamentalmente el comportamiento, es posible que no llame al método de la clase base e implemente toda la funcionalidad directamente en la clase derivada.

algo
fuente
44
Para completar la técnica, algunas clases, como threading.Thread, arrojarán errores gigantescos si alguna vez intentas evitar llamar al init de los padres .
David Berger el
55
Encuentro todo esto "no tienes que llamar al constructor de la base" hablar extremadamente irritante. No tiene que llamarlo en ningún idioma que yo sepa. Todos ellos se arruinarán (o no) de la misma manera, al no inicializar miembros. Sugerir no inicializar las clases base es simplemente incorrecto de muchas maneras. Si una clase no necesita inicialización ahora, la necesitará en el futuro. El constructor es parte de la interfaz de la estructura de clase / construcción del lenguaje, y debe usarse correctamente. Su uso correcto es llamarlo alguna vez en el constructor de su derivado. Así que hazlo.
AndreasT
2
"Sugerir que no se inicialicen las clases base es simplemente incorrecto de muchas maneras". Nadie ha sugerido no inicializar la clase base. Lee la respuesta cuidadosamente. Se trata de intención. 1) Si desea dejar la lógica de inicio de la clase base tal como está, no anule el método init en su clase derivada. 2) Si desea extender la lógica de inicio desde la clase base, defina su propio método de inicio y luego llame al método de inicio de la clase base desde él. 3) Si desea reemplazar la lógica de inicio de la clase base, defina su propio método init sin llamar al de la clase base.
wombatonfire
4

Si la clase FileInfo tiene más de una clase ancestral, definitivamente debe llamar a todas sus funciones __init __ (). También debe hacer lo mismo para la función __del __ (), que es un destructor.

moinudin
fuente
2

Sí, debe llamar __init__para cada clase de padres. Lo mismo ocurre con las funciones, si está anulando una función que existe en ambos padres.

vezult
fuente