Saltar a contenido

4.C Constructores y enumerados

Más de una forma de crear un objeto

A veces un objeto se puede crear de varias maneras: con unos valores por defecto, con datos propios o a partir de una «receta» ya preparada. Cada lenguaje lo resuelve a su manera.

Python solo tiene un __init__, así que se usan parámetros con valor por defecto (nombre="Margarita"), que se pueden pasar por nombre. Para constructores alternativos con otro significado se usan métodos de clase con @classmethod (Pizza.cuatro_quesos(...)), que reciben la clase como cls y devuelven un objeto.

from enum import Enum


class Tamano(Enum):
    PEQUENA = ("Pequeña", 6)
    MEDIANA = ("Mediana", 8)
    GRANDE = ("Grande", 10)

    def __init__(self, etiqueta, precio_base):
        self.etiqueta = etiqueta
        self.precio_base = precio_base


class Pizza:
    def __init__(self, tamano, nombre="Margarita", extras=0):
        self.tamano = tamano
        self.nombre = nombre
        self.extras = extras

    @classmethod
    def cuatro_quesos(cls, tamano):
        return cls(tamano, "Cuatro quesos", 3)

    def anadir_extra(self, cantidad=1):
        self.extras += cantidad

    def precio(self):
        return self.tamano.precio_base + self.extras

    def __str__(self):
        return f"Pizza({self.nombre}, {self.tamano.etiqueta}, {self.extras} extras, {self.precio()} €)"


if __name__ == "__main__":
    a = Pizza(Tamano.MEDIANA)
    b = Pizza.cuatro_quesos(Tamano.GRANDE)
    c = Pizza(Tamano.PEQUENA, "Barbacoa")
    print(a)
    print(b)
    print(c)
    a.anadir_extra()
    a.anadir_extra(2)
    print(a)
    print(f"total del pedido: {a.precio() + b.precio() + c.precio()} €")
    print("tamaños: " + ", ".join(t.etiqueta for t in Tamano))

Salida:

Pizza(Margarita, Mediana, 0 extras, 8 €)
Pizza(Cuatro quesos, Grande, 3 extras, 13 €)
Pizza(Barbacoa, Pequeña, 0 extras, 6 €)
Pizza(Margarita, Mediana, 3 extras, 11 €)
total del pedido: 30 €
tamaños: Pequeña, Mediana, Grande

En este ejemplo hay tres formas de obtener una pizza: solo con el tamaño (el nombre por defecto es «Margarita»), con nombre propio (Barbacoa) y la receta ya preparada con un constructor alternativo (cuatroQuesos, que además trae tres extras). Después, anadirExtra se llama con y sin argumento.

Para un método que se puede llamar con o sin argumento, como anadir_extra() y anadir_extra(2), se usa un solo método con un parámetro con valor por defecto (cantidad=1).

Un solo sitio para las reglas

Procura que todos los constructores acaben pasando por el mismo código (un constructor principal al que los demás llaman): así cualquier regla que añadas más adelante (por ejemplo, un máximo de extras) se escribe una sola vez.

Enumerados

Un enumerado es un tipo con un conjunto fijo y cerrado de valores: los días de la semana, los estados de un pedido, los tamaños de una pizza. Es mejor que usar números o textos sueltos porque el compilador impide valores inventados (Tamano.gigante no existe) y el código se lee solo.

Un Enum en Python puede tener datos asociados: se dan como tupla y se recogen en __init__. Se recorre con for t in Tamano. Por convención los miembros van en MAYÚSCULAS.

En el ejemplo, cada tamaño lleva datos asociados (la etiqueta que se muestra y el precio base), de modo que el resto del programa no necesita un if por cada tamaño: pregunta el dato al propio valor.

Errores frecuentes

Error Cómo evitarlo
Repetir las comprobaciones en cada constructor Un constructor principal y los demás que lo llaman
Dos constructores casi iguales con distinto orden de parámetros Usa valores por defecto o un método de fábrica con nombre claro
Usar números o textos para representar categorías (tipo = 1) Un enumerado
Cadenas de if/else según el valor de un enumerado Pon el dato o el comportamiento dentro del enumerado

Para practicar

Los constructores, los enumerados y la sobrecarga se practican en U4.6 · Prueba. Compara cómo se escribe en otro lenguaje con Pasar de uno a otro.