================
== Pythonisas ==
================
Brujas del teclado. Alquimistas de los bits.

POO — Naves espaciales, coches y Python

prácticas

Tabla de Contenido

  1. Trabajando con Clases e Instancias — Naves Espaciales
  2. La Clase Coche
  3. Establecer un Valor por Defecto
  4. Modificando los Valores de Atributos

Trabajando con Clases e Instancias — Naves Espaciales

¿Por que naves? Porque mas adelante (Capitulo 12) crearemos nuestro propio videojuego Alien Invasion con Pygame, donde la clase Ship sera el corazon del juego. Esta practica es el calentamiento.

Adelanto: En alien_invasion la nave tendra atributos como posicion_x, velocidad, moviendo_derecha y metodos como actualiza() y blitme(). Hoy construimos la version “sin graficos” para dominar la mecanica de clases antes de llegar a Pygame.

Pero no corramos tanto! … como diria el poeta, ‘hagamos camino al andar’. Paso a paso. Veamos un ejemplo de todo esto basado en unos vehiculos mas terrenales. Veamos un ejemplo basado en coches y como manejar parametros como fabricante, año de fabricacion, kilometraje etc…

La Clase Coche

Vamos a escribir una clase que represente un coche. Nuestra clase almacenara informacion sobre el tipo de coche y tendra un metodo que resuma esa informacion.

Definicion basica de la clase

En la clase Coche definimos el metodo __init__() con el parametro self en primer lugar, igual que hicimos con la clase Pyrro en la practica anterior. Le damos tambien tres parametros mas: fabricante, modelo y año.

El metodo __init__() recibe estos parametros y los asigna a los atributos que se asociaran con las instancias generadas a partir de esta clase.

class Coche:
    """Intento sencillo de representar un coche."""

    def __init__(self, fabricante, modelo, año):
        """Inicializa los atributos para describir un coche."""
        self.fabricante = fabricante
        self.modelo = modelo
        self.año = año

    def nombra_descriptivamente(self):
        """Devuelve un nombre descriptivo con formato legible."""
        nombre_descriptivo = f"{self.año} {self.fabricante} {self.modelo}"
        return nombre_descriptivo.title()

mi_nuevo_coche = Coche('audi', 'a4', 2024)
print(mi_nuevo_coche.nombra_descriptivamente())
2024 Audi A4

Que ocurre aqui

  • nombra_descriptivamente() combina el año, la marca y el modelo en un solo string que describe el coche de forma legible.
  • Para acceder a los valores de los atributos dentro del metodo usamos self.fabricante, self.modelo y self.año.
  • Fuera de la clase, creamos una instancia de Coche y la asignamos a la variable mi_nuevo_coche.
  • Llamamos a nombra_descriptivamente() para mostrar que coche tenemos.

Establecer un Valor por Defecto para un Atributo de ’nuestro coche’

Cuando se crea una instancia, los atributos pueden definirse sin necesidad de pasarlos como parametros. Estos atributos se definen dentro del metodo __init__(), donde se les asigna un valor por defecto.

Vamos a añadir un atributo llamado lectura_cuentakilometros que siempre empiece con valor 0. Tambien añadiremos un metodo lee_cuentakilometros() que nos ayude a leer el cuentakilometros de cada coche.

class Coche:
    """Intento sencillo de representar un coche."""

    def __init__(self, fabricante, modelo, año):
        """Inicializa los atributos para describir un coche."""
        self.fabricante = fabricante
        self.modelo = modelo
        self.año = año
        self.cuentakilomentros_lectura = 0           # valor por defecto

    def nombra_descriptivamente(self):
        """Devuelve un nombre descriptivo con formato legible."""
        nombre_descriptivo = f"{self.año} {self.fabricante} {self.modelo}"
        return nombre_descriptivo.title()

    def lee_cuentakilometros(self):
        """Imprime el kilometraje del coche."""
        print(f"Este coche ha recorrido {self.cuentakilomentros_lectura} kilometros.")

mi_nuevo_coche = Coche('audi', 'a4', 2024)
print(mi_nuevo_coche.nombra_descriptivamente())
mi_nuevo_coche.lee_cuentakilometros()
2024 Audi A4
Este coche ha recorrido 0 kilometros.

Que ocurre aqui

  • Python crea un nuevo atributo cuentakilomentros_lectura y le asigna el valor inicial 0 — no hace falta pasarlo como argumento al crear la instancia.
  • El metodo lee_cuentakilometros() simplemente imprime el valor actual del cuentakilometros.
  • Nuestro coche empieza con 0 millas. Pocos coches se venden con exactamente 0 millas, asi que necesitamos una forma de cambiar este valor…

Modificando los Valores de Atributos de ’nuestro coche'

Se puede cambiar el valor de un atributo de tres formas:

  1. Directamente a traves de la instancia
  2. A traves de un metodo (setter)
  3. Incrementandolo a traves de un metodo

Veamos cada enfoque.

Forma 1 — Modificar un atributo directamente

La forma mas sencilla es acceder al atributo directamente a traves de la instancia usando la notacion de punto:

class Coche:
    """Intento sencillo de representar un coche."""

    def __init__(self, fabricante, modelo, año):
        """Inicializa los atributos para describir un coche."""
        self.fabricante = fabricante
        self.modelo = modelo
        self.año = año
        self.cuentakilomentros_lectura = 0

    def nombra_descriptivamente(self):
        """Devuelve un nombre descriptivo con formato legible."""
        nombre_descriptivo = f"{self.año} {self.fabricante} {self.modelo}"
        return nombre_descriptivo.title()

    def lee_cuentakilometros(self):
        """Imprime el kilometraje del coche."""
        print(f"Este coche ha recorrido {self.cuentakilomentros_lectura} kilometros.")

mi_nuevo_coche = Coche('audi', 'a4', 2024)
print(mi_nuevo_coche.nombra_descriptivamente())

mi_nuevo_coche.cuentakilomentros_lectura = 23            # acceso directo
mi_nuevo_coche.lee_cuentakilometros()
2024 Audi A4
Este coche ha recorrido 23 kilometros.

Esta linea le dice a Python: toma la instancia mi_nuevo_coche, busca el atributo cuentakilometros_lectura y asignale el valor 23.

A veces querras acceder a los atributos directamente asi, pero otras veces preferiras escribir un metodo que actualice el valor por ti.

Forma 2 — Modificar un atributo mediante un metodo

Es util tener metodos que actualicen ciertos atributos. En lugar de acceder directamente, pasas el nuevo valor a un metodo que se encarga de la actualizacion internamente.

class Coche:
    """Intento sencillo de representar un coche."""

    def __init__(self, fabricante, modelo, año):
        """Inicializa los atributos para describir un coche."""
        self.fabricante = fabricante
        self.modelo = modelo
        self.año = año
        self.cuentakilomentros_lectura = 0

    def nombra_descriptivamente(self):
        """Devuelve un nombre descriptivo con formato legible."""
        nombre_descriptivo = f"{self.año} {self.fabricante} {self.modelo}"
        return nombre_descriptivo.title()

    def lee_cuentakilometros(self):
        """Imprime el kilometraje del coche."""
        print(f"Este coche ha recorrido {self.cuentakilomentros_lectura} kilometros.")

    def actualiza_cuentakilomentros(self, kilometraje):
        """Establece la lectura del cuentakilometros al valor dado."""
        self.cuentakilomentros_lectura = kilometraje

mi_nuevo_coche = Coche('audi', 'a4', 2024)
print(mi_nuevo_coche.nombra_descriptivamente())

mi_nuevo_coche.actualiza_cuentakilomentros(23)              # a traves de metodo
mi_nuevo_coche.lee_cuentakilometros()
2024 Audi A4
Este coche ha recorrido 23 kilometros.

Añadir logica de proteccion

Podemos extender actualiza_cuentakilomentros() para que nadie intente retroceder el cuentakilometros:

class Coche:
    """Intento sencillo de representar un coche."""

    def __init__(self, fabricante, modelo, año):
        """Inicializa los atributos para describir un coche."""
        self.fabricante = fabricante
        self.modelo = modelo
        self.año = año
        self.cuentakilomentros_lectura = 0

    def nombra_descriptivamente(self):
        """Devuelve un nombre descriptivo con formato legible."""
        nombre_descriptivo = f"{self.año} {self.fabricante} {self.modelo}"
        return nombre_descriptivo.title()

    def lee_cuentakilometros(self):
        """Imprime el kilometraje del coche."""
        print(f"Este coche ha recorrido {self.cuentakilomentros_lectura} kilometros.")

    def actualiza_cuentakilomentros(self, kilometraje):
        """
        Establece la lectura del cuentakilometros al valor dado.
        Rechaza el cambio si intenta retroceder el cuentakilometros.
        """
        if kilometraje >= self.cuentakilomentros_lectura:
            self.cuentakilomentros_lectura = kilometraje
        else:
            print("¡No puedes retroceder el cuentakilometros!")

mi_nuevo_coche = Coche('audi', 'a4', 2024)
mi_nuevo_coche.actualiza_cuentakilomentros(23)
mi_nuevo_coche.lee_cuentakilometros()

# Intentamos retroceder el odometro:
mi_nuevo_coche.actualiza_cuentakilomentros(10)
Este coche ha recorrido 23 kilometros.
¡No puedes retroceder el cuentakilometros!

Ahora actualiza_cuentakilomentros() comprueba que la nueva lectura tiene sentido antes de modificar el atributo. Si el valor proporcionado es mayor o igual que el kilometraje existente, se actualiza. Si es menor, se muestra una advertencia.

Forma 3 — Incrementar un atributo mediante un metodo

A veces querras sumar una cantidad al valor de un atributo, en lugar de asignarle un valor completamente nuevo.

Ejemplo: compramos un coche usado y le hacemos 100 millas entre la compra y el registro.

class Coche:
    """Intento sencillo de representar un coche."""

    def __init__(self, fabricante, modelo, año):
        """Inicializa los atributos para describir un coche."""
        self.fabricante = fabricante
        self.modelo = modelo
        self.año = año
        self.cuentakilomentros_lectura = 0

    def nombra_descriptivamente(self):
        """Devuelve un nombre descriptivo con formato legible."""
        nombre_descriptivo = f"{self.año} {self.fabricante} {self.modelo}"
        return nombre_descriptivo.title()

    def lee_cuentakilometros(self):
        """Imprime el kilometraje del coche."""
        print(f"Este coche ha recorrido {self.cuentakilomentros_lectura} kilometros.")

    def actualiza_cuentakilomentros(self, kilometraje):
        """
        Establece la lectura del cuentakilometros al valor dado.
        Rechaza el cambio si intenta retroceder el cuentakilometros.
        """
        if kilometraje >= self.cuentakilomentros_lectura:
            self.cuentakilomentros_lectura = kilometraje
        else:
            print("¡No puedes hacer retroceder el cuentakilometros!")

    def incrementa_cuentakilomentros(self, miles):
        """Suma la cantidad dada a la lectura del cuentakilometros."""
        self.cuentakilomentros_lectura += miles

mi_coche_usado = Coche('opel', 'corsa', 2019)
print(mi_coche_usado.nombra_descriptivamente())

mi_coche_usado.actualiza_cuentakilomentros(23_500)
mi_coche_usado.lee_cuentakilometros()

mi_coche_usado.incrementa_cuentakilomentros(100)
mi_coche_usado.lee_cuentakilometros()
2019 Opel Corsa
Este coche ha recorrido 23500 kilometros.
Este coche ha recorrido 23600 kilometros.

Que ocurre aqui

  • incrementa_cuentakilomentros() recibe un numero de millas y suma ese valor a self.cuentakilomentros_lectura.
  • Primero creamos un coche usado (mi_coche_usado).
  • Le asignamos 23.500 millas con actualiza_cuentakilomentros().
  • Luego llamamos a incrementa_cuentakilomentros(100) para añadir las 100 millas recorridas entre la compra y el registro.

NOTA: Puedes usar metodos como estos para controlar como los usuarios de tu programa actualizan valores como el cuentakilometros, pero cualquier persona con acceso al programa puede asignar directamente cualquier valor accediendo al atributo. La seguridad efectiva requiere una atencion extrema al detalle, ademas de las comprobaciones basicas mostradas aqui.

Resumen de Conceptos

ConceptoDescripcionEjemplo
Atributo con parametroSe pasa al crear la instanciaself.fabricante = fabricante
Atributo por defectoSe define en __init__() sin parametroself.cuentakilomentros_lectura = 0
Acceso directoModificar atributo con notacion de puntocoche.cuentakilomentros_lectura = 23
Metodo setterMetodo que asigna un nuevo valoractualiza_cuentakilomentros(23)
Metodo incrementoMetodo que suma al valor existenteincrementa_cuentakilomentros(100)
Logica de proteccionValidacion dentro del setterif kilometraje > self.cuentakilomentros…=