Las Clases

flor

Las clases son uno de los elementos principales de la programación
orientada a objetos. Cuando creamos un objeto de una clase, decimos que lo
estamos instanciando. En ese momento, estamos dotando al objeto de unos
atributos propios de la clase. También le estamos facilitando unos métodos
(funciones) característicos de dicha clase, y que no se pueden utilizar fuera de los elementos que pertenecen a la clase. Las clases se suelen definir con la primera letra en mayúsculas y el resto en minúsculas.

Por lo tanto podemos decir que los atributos de un objeto son los valores o
propiedades que tiene dicho objeto. Los métodos serían las funciones o acciones que pueden actuar sobre los atributos del objeto.

Para empezar podemos definir la clase “Libro”. Al igual que ocurre con las
funciones, las clases que nosotros vayamos a crear se pueden guardar en un
archivo y utilizar en nuestro programa o reutilizar en otros programas diferentes.

class Libro:
    def __init__(self,tema,paginas,editorial):
        self.tema=tema
        self.paginas=paginas
        self.editorial=editorial
        
    def ejemplares(self,cantidad):
        self.cantidad=cantidad
        if cantidad>20:
            self.existencias="muchas"
        elif cantidad>10:
            self.existencias="algunas"
        elif cantidad>0:
            self.existencias="pocas"
        else:
            self.existencias="agotado."
            
    def neto(self,inicial,iva,descuento):
        bruto=inicial+(inicial*iva/100)
        self.precio=bruto-(bruto*descuento/100)
        
    def __str__(self):
        cadena=("Mi libro de "+self.tema+" tiene "+str(self.paginas)+" páginas.")
        return cadena

El primer método, que además es obligatorio es def __init__ (dos guiones bajos a ambos lados de la palabra init). Entre paréntesis vendrán, como con cualquier otra función, los parámetros que va a necesitar este método.

El primer parámetro es obligado, y viene representado por la palabra
obligada self. Para hacernos una idea, esta palabra reservada self, vendría a representar el nombre que le damos a cada uno de los objetos que instanciamos. A continuación vendrían el resto de parámetros que vayamos a utilizar en este método inicial y obligado.

A continuación vamos a inicializar cada uno de los atributos obligados que va a tener la clase creada. Estos atributos van a quedar declarados mediante la palabra reservada “self” (que viene a sustituir inicialmente al nombre que luego va a tener cada objeto creado). Después de “self” viene un punto y a continuación el
nombre que va a tener el atributo. Seguimos con el signo “=” y completamos el valor inicial que va a tener dicho atributo.

El resto de métodos y atributos funcionan de igual manera, pero son
opcionales (podemos llamarlos o no, según necesitemos).

Supongamos que hemos guardado el código anterior en un archivo llamado claselibros.py (aunque la definición de la clase también podría ir incluida dentro del código principal y en su mismo archivo). El archivo claselibros.py podrá contener la clase Libro y cualquier otra clase que nos pueda ser de utilidad. Estas clases, las podremos llamar desde cualquier archivo de código que se encuentre en el mismo directorio que el archivo llamado. Por supuesto el archivo con las clases puede ser reutilizado en cualquier otro código.

from claselibros import Libro

milibro=Libro("Historia",254,"Ed.Libraco")
print (f'Mi libro de {milibro.tema} tiene {milibro.paginas} páginas.')
print (f'La editorial es {milibro.editorial}.')
milibro.ejemplares(18)
print(f'Actualmente hay {milibro.existencias} existencias.')
milibro.neto(20,7,2)
print(f'Su precio de venta es {round(milibro.precio,2)} euros.')
print(milibro)
Resultado:

Mi libro de Historia tiene 254 páginas.
La editorial es Ed.Libraco.
Actualmente hay algunas existencias.
Su precio de venta es 20.97 euros.
Mi libro de Historia tiene 254 páginas.

Lo primero que hemos hecho ha sido importar la clase Libro desde el archivo
claselibros.py. A continuación hemos creado el objeto milibro como perteneciente a la clase Libro. Automáticamente ha adquirido los atributos .tema .páginas y .editorial .

Los atributos .existencias y .precio han sido creados al llamar a los
métodos .ejemplares y .neto (respectivamente). También hemos utilizado el
comando round sobre el valor numérico milibro.precio, para redondear su valor en dos decimales.

Mención especial requiere el último método que está definido en la clase. Es
el método __str__ (con doble guion bajo a ambos lados de str). Con este método
que podemos incluir en cualquier clase, podemos definir un objeto de una clase determinada como una cadena. Cada vez que hagamos referencia a ese objeto (sin ningún atributo añadido), estaremos haciendo referencia a dicha cadena.

En el código de ejemplo, cuando se ejecuta print(milibro), se imprime la cadena que se devuelve al invocar al método __str__, y que se corresponde con la cadena definida en dicho método.

Las clases y la Herencia.

Partiendo de una clase, podemos crear otra clase con los mismos atributos
y métodos, a los que podemos añadir otros nuevos que necesitemos para que se adapte mejor a nuestras necesidades. A esto se le llama “herencia”. Siguiendo con la clase que hemos creado en el apartado anterior y que habíamos guardado en el archivo claselibros.py :

from claselibros import Libro
class Enciclopedia(Libro):
    def __init__(self, tema, paginas, editorial):
        super().__init__(tema, paginas, editorial)
        self.tipolibro="consulta"
        
    def caracteristicas(self,anyo,numerotomos):
        self.anyo=anyo
        self.numerotomos=numerotomos
        
mienciclopedia=Enciclopedia("Informática",2500,"Canaya")
mienciclopedia.caracteristicas(2020,20)
mienciclopedia.ejemplares(8)
print(f"Tengo una enciclopedia de {mienciclopedia.tema}.")
print(f"Tiene {mienciclopedia.paginas} páginas.")
print(f"Es un libro de {mienciclopedia.tipolibro}.")
print(f"Tiene {mienciclopedia.numerotomos} tomos.")
print(f"Es del año {mienciclopedia.anyo}.")
print(f"En tienda, sus existencias son: {mienciclopedia.existencias}.")
Resultado:

Tengo una enciclopedia de Informática.
Tiene 2500 páginas.
Es un libro de consulta.
Tiene 20 tomos.
Es del año 2020.
En tienda, sus existencias son: pocas.

Al definir la clase que hereda, incluimos entre paréntesis el nombre de la
clase original. El método __init__ de la clase que hereda debe incluir entre
paréntesis los mismos parámetros que la clase original, pero además podríamos incluir otros parámetros adicionales, propios de la nueva clase. El método super().__init__ (escrito exactamente así), hace referencia a la clase __init__ de la clase que hereda, y por lo tanto debe de incluir exactamente sus mismos parámetros (sin poder añadir, ni quitar ninguno).

Vemos que hemos incluido en la nueva clase, un nuevo atributo. También
hemos añadido un nuevo método, que nos incluye las nuevas características.

Los demás métodos y atributos son los mismos que los de la clase de la cual se hereda (aunque no aparezcan en la clase que hereda).

Es probable que alguno de los atributos o métodos de la clase inicial, no se
adapten completamente a nuestra nueva clase. Este caso se soluciona reescribiendo el método o el atributo dentro de la nueva clase, para que así se adapte a lo que se necesita.

from claselibros import Libro

class Enciclopedia(Libro):
    def __init__(self, tema, paginas, editorial):
        super().__init__(tema, paginas, editorial)
        self.tipolibro="consulta"
        
    def ejemplares(self,cantidad):
        self.cantidad=cantidad
        if cantidad>15:
            self.existencias="muchas"
        elif cantidad>5:
            self.existencias="algunas"
        elif cantidad>0:
            self.existencias="pocas"
        else:
            self.existencias="agotado."
    def caracteristicas(self,anyo,numerotomos):
        self.anyo=anyo
        self.numerotomos=numerotomos
        
mienciclopedia=Enciclopedia("Informática",2500,"Canaya")
mienciclopedia.caracteristicas(2020,20)
mienciclopedia.ejemplares(8)
print(f"Tengo una enciclopedia de {mienciclopedia.tema}.")
print(f"Tiene {mienciclopedia.paginas} páginas.")
print(f"Es un libro de {mienciclopedia.tipolibro}.")
print(f"Tiene {mienciclopedia.numerotomos} tomos.")
print(f"Es del año {mienciclopedia.anyo}.")
print(f"En tienda, sus existencias son: {mienciclopedia.existencias}.")
Resultado:

Tengo una enciclopedia de Informática.
Tiene 2500 páginas.
Es un libro de consulta.
Tiene 20 tomos.
Es del año 2020.
En tienda, sus existencias son: algunas.

Hemos añadido un nuevo método a la clase que hereda, que coincide en el nombre con la clase original. Por lo tanto el nuevo método reemplaza al método anterior para adaptarse a la nueva clase.
También se podía haber definido la clase Enciclopedia, incluyendo en su definición el parámetro “utilidad”, (que sería asignado en este caso al atributo .tipolibro). Esto nos daría más flexibilidad en la creación de objetos Enciclopedia, ya que podríamos definir varios tipos de enciclopedia (consulta, ocio, idiomas…).

from claselibros import Libro

class Enciclopedia(Libro):
    def __init__(self, tema, paginas, editorial,utilidad):
        super().__init__(tema, paginas, editorial)
        self.tipolibro=utilidad
        
    def ejemplares(self,cantidad):
        self.cantidad=cantidad
        if cantidad>15:
            self.existencias="muchas"
        elif cantidad>5:
            self.existencias="algunas"
        elif cantidad>0:
            self.existencias="pocas"
        else:
            self.existencias="agotado."
    def caracteristicas(self,anyo,numerotomos):
        self.anyo=anyo
        self.numerotomos=numerotomos
        
mienciclopedia=Enciclopedia("Informática",2500,"Canaya","estudio")
mienciclopedia.caracteristicas(2020,20)
mienciclopedia.ejemplares(8)
print(f"Tengo una enciclopedia de {mienciclopedia.tema}.")
print(f"Tiene {mienciclopedia.paginas} páginas.")
print(f"Es un libro de {mienciclopedia.tipolibro}.")
print(f"Tiene {mienciclopedia.numerotomos} tomos.")
print(f"En tienda, sus existencias son: {mienciclopedia.existencias}.")
Resultado:

Tengo una enciclopedia de Informática.
Tiene 2500 páginas.
Es un libro de estudio.
Tiene 20 tomos.
En tienda, sus existencias son: algunas.

Como hemos incluido un nuevo parámetro en la nueva clase, esta nueva clase se debe de llamar con los parámetros anteriores (los de la clase original), y además debemos de incluir el nuevo parámetro adicional de la nueva clase.

Ejemplo con clases

Como ejemplo práctico del gran poder de configuración que pueden tener las clases y los objetos que las forman, vamos a retomar la matriz ejemplo de listas de varias dimensiones. Abordaremos el mismo tema de las habitaciones del hotel, pero esta vez cada habitación será un objeto de la clase Habitacion. El complejo hotelero estará incluido dentro de una lista de objetos (cada objeto, una habitación).
Además hemos incluido un método para la clase, que nos permitirá
configurar los datos para una habitación ocupada, y otro método que nos desocupará la habitación.

from datetime import datetime,timedelta

class Habitacion:
    def __init__(self,edificio,planta,puerta):
        self.edificio=edificio
        self.planta=planta
        self.puerta=puerta
        self.ocupacion="LIBRE"
        
    def __str__(self):
        cadena=("La habitación del edificio "+str(self.edificio)\
        +" planta "+str(self.planta)\
        +" puerta "+str(self.puerta)\
        + " está "+self.ocupacion)
        return cadena
    
    def ocupar(self,fecha_entrada,dias_ocupacion):
        self.ocupacion="OCUPADA"
        dias=timedelta(days=dias_ocupacion)
        entrada=datetime.strptime(fecha_entrada,"%d-%m-%Y")
        self.fecha_entrada=entrada.strftime("Fecha entrada: %d-%m-%Y")
        salida=entrada+dias
        self.fecha_salida=salida.strftime("Fecha salida: %d-%m-%Y")
        
    def desocupar(self):
        self.ocupacion="LIBRE"
        
habitaciones=[]
for puerta in range (5):
    for planta in range(4):
        for edificio in range(3):
            nueva=Habitacion(puerta,planta,edificio)        
            habitaciones.append(nueva)
            
habitaciones[19].ocupar("26-12-2024",10)
habitaciones[33].ocupar("23-01-2025",30)
habitaciones[45].ocupar("14-03-2025",1)

for k in range (0,len(habitaciones)):
    if habitaciones[k].ocupacion=="OCUPADA":
        print(habitaciones[k])
        print(habitaciones[k].fecha_entrada)
        print(habitaciones[k].fecha_salida+"\n")
Resultado:

La habitación del edificio 1 planta 2 puerta 1 está OCUPADA
Fecha entrada: 26-12-2024
Fecha salida: 05-01-2025

La habitación del edificio 2 planta 3 puerta 0 está OCUPADA
Fecha entrada: 23-01-2025
Fecha salida: 22-02-2025

La habitación del edificio 3 planta 3 puerta 0 está OCUPADA
Fecha entrada: 14-03-2025
Fecha salida: 15-03-2025
Iniciocomandos Pythonbloques de código
comentar códigoprint ( )input ( )
captura de erroresCadenasDatos Numéricos
Módulo randomMódulo statisticsDatos Fecha/hora
ConstantesBucles forBucles while
Bloque ifTuplasListas.
match caseConjuntosDiccionarios
FuncionesClasesArchivos
Entorno gráfico TkinterVentanasWidgets básicos