Open In Colab

1.2. Clases en Python#

Una clase es una plantilla que define la estructura y el comportamiento de un tipo de objeto. Las clases en Python permiten crear objetos que tienen propiedades (atributos) y pueden realizar acciones específicas (métodos). Son una parte fundamental de la programación orientada a objetos en Python.

Descripción de la imagen

1.2.1. Clases y objetos#

  • Una clase es una entidad que define una serie de elementos que determinan un estado (datos) y un comportamiento (operaciones sobre los datos que modifican su estado).

  • Por su parte, un objeto es una concreción o instancia de una clase.

Lo primero es crear una clase, para ello usaremos el ejemplo de perro. Para crear una clase se ocupa el comando class.

# Creando una clase vacía
class Perro:
    pass

Se trata de una clase vacía y sin mucha utilidad práctica, pero es la mínima clase que podemos crear. Nótese el uso del pass que no hace realmente nada, pero daría un error si después de los : no tenemos contenido.

Ahora que tenemos la clase, podemos crear un objeto de la misma. Podemos hacerlo como si de una variable normal se tratase. Nombre de la variable igual a la clase con (). Dentro de los paréntesis irían los parámetros de entrada si los hubiera.

# Creamos un objeto de la clase perro
mi_perro = Perro()

Observación: El nombre de la clase debe comenzar con Mayúscula (Ejemplo: Perro).

1.2.2. Definiendo atributos#

A continuación vamos a añadir algunos atributos a nuestra clase. Antes de nada es importante distinguir que existen dos tipos de atributos:

  • Atributos de instancia: Pertenecen a la instancia de la clase o al objeto. Son atributos particulares de cada instancia, en nuestro caso de cada perro.

  • Atributos de clase: Se trata de atributos que pertenecen a la clase, por lo tanto serán comunes para todos los objetos.

Empecemos creando un par de atributos de instancia para nuestro perro, el nombre y la raza. Para ello creamos un método __init__ que será llamado automáticamente cuando creemos un objeto. Se trata del constructor.

class Perro:
    # El método __init__ es llamado al crear el objeto
    def __init__(self, nombre, raza):
        print(f"Creando perro {nombre}, {raza}")

        # Atributos de instancia
        self.nombre = nombre
        self.raza = raza

Ahora que hemos definido el método __init__ con dos parámetros de entrada, podemos crear el objeto pasando el valor de los atributos. Usando type() podemos ver como efectivamente el objeto es de la clase Perro.

mi_perro = Perro("Toby", "Bulldog")
print(type(mi_perro))
Creando perro Toby, Bulldog
<class '__main__.Perro'>

Seguramente te hayas fijado en el self que se pasa como parámetro de entrada del método. Es una variable que representa la instancia de la clase, y deberá estar siempre ahí.

El uso de __init__ y el doble __ no es una coincidencia. Cuando veas un método con esa forma, significa que está reservado para un uso especial del lenguaje. En este caso sería lo que se conoce como constructor. Hay gente que llama a estos métodos mágicos.

Por último, podemos acceder a los atributos usando el objeto.

print(mi_perro.nombre) # Toby
print(mi_perro.raza)   # Bulldog
Toby
Bulldog

Hasta ahora hemos definido atributos de instancia, ya que son atributos que pertenecen a cada perro concreto. Ahora vamos a definir un atributo de clase, que será común para todos los perros. Por ejemplo, la especie de los perros es algo común para todos los objetos Perro.

class Perro:
    # Atributo de clase
    especie = 'mamífero'

    # El método __init__ es llamado al crear el objeto
    def __init__(self, nombre, raza):
        print(f"Creando perro {nombre}, {raza}")

        # Atributos de instancia
        self.nombre = nombre
        self.raza = raza

Dado que es un atributo de clase, no es necesario crear un objeto para acceder al atributos. Podemos hacer lo siguiente.

print(Perro.especie)
mamífero

Se puede acceder también al atributo de clase desde el objeto.

mi_perro = Perro("Toby", "Bulldog")
mi_perro.especie
Creando perro Toby, Bulldog
'mamífero'

De esta manera, todos los objetos que se creen de la clase perro compartirán ese atributo de clase, ya que pertenecen a la misma.

1.2.3. Definiendo métodos#

Los métodos son las funciones que se definen dentro de una clase y que, por consiguiente, pueden ser referenciadas por los objetos de dicha clase.

Vamos a codificar dos métodos, ladrar y caminar. El primero no recibirá ningún parámetro y el segundo recibirá el número de pasos que queremos andar. Como hemos indicado anteriormente self hace referencia a la instancia de la clase. Se puede definir un método con def y el nombre, y entre () los parámetros de entrada que recibe, donde siempre tendrá que estar self el primero.

class Perro:
    # Atributo de clase
    especie = 'mamífero'

    # El método __init__ es llamado al crear el objeto
    def __init__(self, nombre, raza):
        print(f"Creando perro {nombre}, {raza}")

        # Atributos de instancia
        self.nombre = nombre
        self.raza = raza

    def ladra(self):
        print("Guau")

    def camina(self, pasos):
        print(f"Caminando {pasos} pasos")

Por lo tanto si creamos un objeto mi_perro, podremos hacer uso de sus métodos llamándolos con . y el nombre del método. Como si de una función se tratase, pueden recibir y devolver argumentos.

mi_perro = Perro("Toby", "Bulldog")
mi_perro.ladra()
mi_perro.camina(10)
Creando perro Toby, Bulldog
Guau
Caminando 10 pasos

1.2.3.1. Tipos de métodos#

Hemos visto como se pueden crear métodos con def dentro de una clase, pudiendo recibir parámetros como entrada y modificar el estado (como los atributos) de la instancia. Pues bien, haciendo uso de los decoradores, es posible crear diferentes tipos de métodos:

  • Lo métodos de instancia “normales” que ya hemos visto como metodo()

  • Métodos de clase usando el decorador @classmethod

  • Y métodos estáticos usando el decorador @staticmethod

En la siguiente clase tenemos un ejemplo donde definimos los tres tipos de métodos.

class Clase:
    def metodo(self):
        return 'Método normal', self

    @classmethod
    def metododeclase(cls):
        return 'Método de clase', cls

    @staticmethod
    def metodoestatico():
        return "Método estático"

1.2.3.1.1. Métodos de instancia#

Los métodos de instancia son los métodos normales, de toda la vida, que hemos visto anteriormente. Reciben como parámetro de entrada self que hace referencia a la instancia que llama al método. También pueden recibir otros argumentos como entrada.

class Clase:
    def metodo(self, arg1, arg2):
        return 'Método normal', self

Y como ya sabemos, una vez creado un objeto pueden ser llamados.

mi_clase = Clase()
mi_clase.metodo("a", "b")
('Método normal', <__main__.Clase at 0x2d6ce7e4ac0>)

En vista a esto, los métodos de instancia:

  • Pueden acceder y modificar los atributos del objeto.

  • Pueden acceder a otros métodos.

  • Dado que desde el objeto self se puede acceder a la clase con self.class, también pueden modificar el estado de la clase

1.2.3.1.2. Métodos de clase#

A diferencia de los métodos de instancia, los métodos de clase reciben como argumento cls, que hace referencia a la clase. Por lo tanto, pueden acceder a la clase pero no a la instancia.

class Clase:
    @classmethod
    def metododeclase(cls):
        return 'Método de clase', cls

Se pueden llamar sobre la clase.

Clase.metododeclase()
('Método de clase', __main__.Clase)

Pero también se pueden llamar sobre el objeto.

mi_clase = Clase()
mi_clase.metododeclase()
('Método de clase', __main__.Clase)

Por lo tanto, los métodos de clase:

  • No pueden acceder a los atributos de la instancia.

  • Pero si pueden modificar los atributos de la clase.

1.2.3.1.3. Métodos estáticos#

Los métodos estáticos se pueden definir con el decorador @staticmethod y no aceptan como parámetro ni la instancia ni la clase. Es por ello por lo que no pueden modificar el estado ni de la clase ni de la instancia. Pero por supuesto pueden aceptar parámetros de entrada.

class Clase:
    @staticmethod
    def metodoestatico():
        return "Método estático"
mi_clase = Clase()
Clase.metodoestatico()
mi_clase.metodoestatico()
'Método estático'

Por lo tanto el uso de los métodos estáticos pueden resultar útil para indicar que un método no modificará el estado de la instancia ni de la clase. Es cierto que se podría hacer lo mismo con un método de instancia por ejemplo, pero a veces resulta importante indicar de alguna manera estas peculiaridades, evitando así futuros problemas y malentendidos.

En otras palabras, los métodos estáticos se podrían ver como funciones normales, con la salvedad de que van ligadas a una clase concreta.