2.22. Herencia¶
La herencia permite que una clase extienda otra clase: parte de todos sus métodos y atributos, y luego añade o redefine lo que es diferente. La original se llama la base (o padre); la extensión se llama la subclase.
Square reutiliza todos los métodos de Shape y redefine los que necesita especializar.¶
2.22.1. Definir una subclase¶
La clase base va entre los paréntesis de la línea class:
class Shape:
def __init__(self, name):
self.name = name
def describe(self):
return self.name + " with area " + str(self.area())
def area(self):
return 0
class Square(Shape):
def __init__(self, side):
super().__init__("square")
self.side = side
def area(self):
return self.side * self.side
s = Square(4)
print(s.describe())
Salida:
square with area 16
Square hereda describe de Shape sin cambios y redefine area para que devuelva un valor real. El describe de la base sigue funcionando porque llama a self.area(), que se resuelve a la redefinición en tiempo de ejecución.
2.22.2. super()¶
super() devuelve un proxy que permite a un método llamar a la versión de la clase base de un método. El uso más común es llamar al __init__ de la base desde el __init__ de una subclase para que la base pueda inicializar sus propios atributos:
class Square(Shape):
def __init__(self, side):
super().__init__("square") # run Shape.__init__
self.side = side
Olvidar super().__init__ es una fuente habitual de errores: los atributos que la base habría establecido nunca se establecen, y los métodos heredados que dependen de ellos fallan más adelante.
2.22.2.1. La clase base implícita¶
Toda clase hereda implícitamente de object, la raíz de la jerarquía de tipos de Python. Métodos como __repr__, __eq__ y toda la maquinaria que hay detrás del acceso a atributos provienen de ahí, incluso cuando una clase no declara ninguna base. Escribir class Foo(object): explícitamente y escribir class Foo: son equivalentes en el Python moderno; lo segundo es la forma convencional.
2.22.3. Cuándo ayuda la herencia, y cuándo no¶
Usa la herencia cuando una clase sea genuinamente un tipo más específico de otra, compartiendo la mayor parte del comportamiento. La prueba clásica es la comprobación «es-un»: un Square es una Shape.
Cuando dos clases simplemente comparten unos cuantos auxiliares, la herencia es excesiva. Recurre en su lugar a la composición: haz que una clase contenga una instancia de la otra como atributo y la use a través de ese atributo. La forma es «tiene-un», no «es-un»:
class Logger:
def log(self, msg):
print("[log]", msg)
# inheritance: Worker IS-A Logger
class WorkerInherits(Logger):
def run(self):
self.log("starting")
# composition: Worker HAS-A Logger
class WorkerComposes:
def __init__(self):
self.logger = Logger()
def run(self):
self.logger.log("starting")
Ambas funcionan. La versión que compone suele ser mejor porque:
Mantiene las interfaces pequeñas.
WorkerComposesexpone únicamenterunylogger.WorkerInheritstambién exponelog: quien llama puede escribirworker.log(...)directamente, fuera intencionado o no.Desacopla los tiempos de vida. El logger puede intercambiarse, compartirse entre workers o construirse de forma distinta para cada worker, todo ello sin tocar la clase
Worker. Una subclase está atornillada a su base.Evita las colisiones de nombres de método. Dos bases que ambas definen
runchocan cuando una clase intenta heredar de ambas; dos atributos nunca chocan.
Una regla práctica:
Usa la herencia cuando la subclase realmente sea un tipo de la base y todos los métodos de la base tengan sentido también para ella:
Squarees unaShape;MemoryErrores unaException.Usa la composición para todo lo demás, cuando solo quieras el comportamiento de otra clase, no su identidad. La mayoría del código real usa la composición mucho más que la herencia.