La programación orientada a objetos (POO) es una técnica muy útil para estructurar y organizar el código de manera lógica y coherente, lo que hace que el código sea más fácil de entender, mantener y mejorar. Por ello, el uso de clases en Python es una práctica muy común y recomendada para proyectos de software de cualquier tamaño y complejidad.
Aquí veremos la definición de las clases, algunas ventajas y desventajas de su uso, así como los métodos para utilizarlas. ¡Comencemos!
Qué es una clase en Python
Una clase en Python es una estructura de programación que permite definir un conjunto de métodos y atributos que describen un objeto o entidad. Las clases son un concepto fundamental en la programación orientada a objetos, que se utilizan para modelar entidades del mundo real o abstracto en un programa de computadora.
Para qué sirven las clases en Python
Una clase define una plantilla o molde para crear objetos, los cuales son instancias de esa clase. Los objetos creados a partir de una clase tienen las mismas propiedades y comportamientos definidos por la clase, pero pueden tener valores diferentes para los atributos que se definen en la clase.
En Python, una clase se define mediante la palabra clave «class», seguida del nombre de la clase y dos puntos (:) y luego el cuerpo de la clase. El cuerpo de la clase contiene definiciones de métodos y atributos, que pueden ser públicos o privados según su acceso.
class Persona: def __init__(self, nombre, edad): self.nombre = nombre self.edad = edad def saludar(self): print("Hola, mi nombre es " + self.nombre) |
Ventajas y desventajas del uso de las clases en Python
Ventajas
- Reutilización de código: las clases pueden reutilizarse en diferentes partes del programa o en distintos programas, lo que ahorra tiempo y reduce la duplicación de código.
- Encapsulación: permiten ocultar la complejidad de un objeto y exponer solo una interfaz simple y fácil de usar para interactuar con él.
- Modularidad: pueden descomponer un programa en componentes más pequeños y manejables, lo que facilita el mantenimiento y la solución de problemas.
- Polimorfismo: ayudan a implementar el mismo conjunto de métodos con diferentes comportamientos para distintos tipos de objetos, lo que permite una mayor flexibilidad y extensibilidad en el diseño de programas.
Desventajas
- Sobrecarga de complejidad: las clases pueden agregar complejidad adicional a un programa y hacer que sea más difícil de entender y depurar.
- Curva de aprendizaje: el aprendizaje de las clases y la programación orientada a objetos en general pueden requerir una curva de aprendizaje más pronunciada para los programadores principiantes.
- Uso innecesario: a veces, las clases se utilizan innecesariamente en situaciones en las que una función simple podría haber hecho el trabajo de manera más eficiente.
Atributos en las clases de Python
Un atributo es una variable que se define dentro de una clase, la cual almacena datos que pertenecen a un objeto de esa clase. Los atributos se utilizan para representar características o propiedades de un objeto, como su estado actual, su identificador, su tamaño, su color, etc.
Los atributos pueden ser de diferentes tipos de datos, como enteros, flotantes, cadenas, listas, diccionarios, entre otros. Además, los atributos pueden tener distintos niveles de visibilidad, que se especifican mediante los modificadores de acceso en la definición de la clase. Por defecto, los atributos son públicos en Python, lo que significa que puede accederse a ellos desde cualquier lugar del programa.
En la definición de una clase, los atributos se definen como variables que se inicializan en el método especial __init__. Por ejemplo, en la clase Persona que definimos anteriormente, los atributos «nombre" y «edad» se definen de la siguiente manera:
class Persona: def __init__(self, nombre, edad): self.nombre = nombre self.edad = edad |
En este caso, «nombre» y «edad» son atributos públicos de la clase Persona, que se inicializan con los valores proporcionados al crear un objeto de la clase. Para acceder a los atributos de un objeto de la clase, se utiliza la notación de punto (.), seguida del nombre del atributo. Por ejemplo, para acceder al atributo «nombre» de un objeto «persona1» de la clase Persona, se utiliza el siguiente código:
nombre_persona1 = persona1.nombre |
En Python, los atributos de una clase pueden tener diferentes niveles de visibilidad, que se especifican mediante los modificadores de acceso en la definición de la clase. Los tres tipos principales de atributos son:
- Atributos públicos: se puede acceder a ellos desde cualquier parte del programa, incluso desde fuera de la clase. En Python, los atributos se consideran públicos por defecto, lo que significa que no se requiere ningún modificador de acceso para especificar que un atributo es público. Para acceder a un atributo público, se utiliza la notación de punto (.) seguida del nombre del atributo.
- Atributos privados: solo se puede acceder a ellos desde dentro de la clase en la que se definen. En Python, los atributos privados se definen mediante el prefijo «__» seguido del nombre del atributo. Por ejemplo, si queremos definir un atributo privado llamado «saldo» en una clase llamada CuentaBancaria, podemos hacerlo de la siguiente manera:
class CuentaBancaria: def __init__(self, saldo): self.__saldo = saldo |
En este caso, «__saldo» es un atributo privado de la clase CuentaBancaria, que solo se puede acceder a él desde dentro de la clase. Si intentamos acceder a este atributo desde fuera de la clase se producirá un error.
-
Atributos protegidos: solo se puede acceder a ellos desde dentro de la clase en la que se definen y desde las clases derivadas (heredadas) de esa clase. En Python, los atributos protegidos se definen mediante el prefijo "" seguido del nombre del atributo. Sin embargo, en Python no existe un verdadero modificador de acceso protegido como en otros lenguajes de programación orientados a objetos, por lo que el uso del prefijo "" es una convención para indicar que un atributo está protegido, pero aún es posible acceder a él desde fuera de la clase.
Métodos para las clases de Python
En Python, los métodos son funciones que se definen dentro de una clase y se utilizan para realizar operaciones en los objetos creados a partir de esa clase. Los métodos se definen de la misma manera que las funciones, pero siempre tienen como primer parámetro el objeto al que se aplicará el método, que suele llamarse «self» por convención.
Por ejemplo, si queremos definir una clase llamada CuentaBancaria que tenga un método para depositar dinero en la cuenta, podemos hacerlo de la siguiente manera:
class CuentaBancaria: def __init__(self, saldo): self.saldo = saldo def depositar(self, cantidad): self.saldo += cantidad |
En este ejemplo, la clase CuentaBancaria tiene un método llamado «depositar» que toma como parámetro la cantidad de dinero a depositar en la cuenta y actualiza el saldo de la cuenta en consecuencia.
Para utilizar un método de una clase, primero debemos crear un objeto a partir de esa clase y luego llamar al método sobre ese objeto. Por ejemplo, para crear una instancia de la clase CuentaBancaria y depositar $100 en la cuenta, podemos hacer lo siguiente:
cuenta = CuentaBancaria(0) cuenta.depositar(100) |
En este caso, primero creamos una instancia de la clase CuentaBancaria con un saldo inicial de cero, y luego llamamos al método «depositar» sobre esa instancia para depositar $100 en la cuenta.
El objeto self
En Python, «self» es una convención que se utiliza como nombre para el primer parámetro de un método en una clase. El objetivo de «self» es hacer referencia al objeto que se está manipulando cuando se llama al método.
Cuando se llama a un método sobre un objeto en Python, el intérprete automáticamente asigna una referencia a ese objeto al parámetro «self» del método. De esta forma, dentro del cuerpo del método podemos acceder a los atributos y métodos del objeto utilizando «self» como prefijo.
Por ejemplo, si tenemos una clase llamada «Persona» con un método «saludar» que imprime un saludo personalizado, podemos definir el método de la siguiente forma:
class Persona: def __init__(self, nombre): self.nombre = nombre def saludar(self): print("Hola, mi nombre es", self.nombre) |
En este caso, el método saludar toma como primer parámetro self, que hace referencia al objeto de la clase Persona, sobre el cual se está llamando al método. Dentro del cuerpo del método podemos acceder al atributo «nombre» del objeto utilizando self.nombre.
Cuando llamamos al método saludar sobre un objeto de la clase Persona, el intérprete automáticamente asigna una referencia a ese objeto al parámetro self. Por ejemplo:
p = Persona("Juan") p.saludar() # Imprime "Hola, mi nombre es Juan" |
Métodos dunder
En Python, los métodos dunder (abreviatura de «double underscore methods», también conocidos como «métodos mágicos» o «métodos especiales») son métodos especiales que tienen un doble guion bajo (__) al principio y al final de su nombre. Se emplean para definir el comportamiento especial de las clases y sus instancias, y son llamados automáticamente por el intérprete de Python en respuesta a ciertas operaciones.
Los métodos dunder pueden utilizarse para implementar características como la inicialización de objetos, la representación en forma de cadena, la sobrecarga de operadores, la comparación, entre otras.
Por ejemplo, el método __init__ es un método dunder que se usa para inicializar un objeto cuando se crea una instancia de una clase. El método __str__ es otro método dunder que se emplea para representar una instancia de la clase en forma de cadena.
A continuación se muestra una lista de algunos de los métodos dunder más comunes en Python:
__init__
Es un método especial (también llamado método dunder) en Python que se utiliza para inicializar una instancia de una clase. Cuando se crea una instancia de una clase, el método __init__ es llamado automáticamente por el intérprete de Python y se utiliza para realizar cualquier inicialización que sea necesaria para la instancia.
El método __init__ se usa para asignar valores iniciales a los atributos de una instancia de la clase. Los atributos son las variables que pertenecen a una instancia particular de la clase. Al llamar al método __init__, podemos establecer los valores de estos atributos y configurar la instancia de la clase para su uso posterior.
Por ejemplo, supongamos que queremos crear una clase llamada Persona con dos atributos: nombre y edad. Podríamos definir la clase de la siguiente manera:
class Persona: def __init__(self, nombre, edad): self.nombre = nombre self.edad = edad |
En este ejemplo, el método __init__ toma dos parámetros: nombre y edad. Estos se utilizan para inicializar los atributos nombre y edad de la instancia de la clase.
Al crear una instancia de la clase Persona, se debe proporcionar un valor para cada uno de los parámetros nombre y edad, como en el siguiente ejemplo:
persona1 = Persona("Juan", 30) |
__str__
Es otro método especial (dunder) en Python que se utiliza para devolver una representación de cadena (string) de una instancia de una clase. Este método se llama cuando se usa la función str() para convertir un objeto en una cadena de caracteres.
En otras palabras, el método __str__ se emplea para definir cómo se debe imprimir una instancia de la clase cuando se llama la función str() o cuando se utiliza la interpolación de cadenas (f-strings).
Por ejemplo, supongamos que tenemos una clase Persona con los atributos nombre y edad. Podemos definir el método __str__ de la siguiente manera:
class Persona: def __init__(self, nombre, edad): self.nombre = nombre self.edad = edad def __str__(self): return f"{self.nombre} ({self.edad} años)" |
En este ejemplo, el método __str__ devuelve una cadena de caracteres que representa la instancia de la clase Persona. La cadena contiene el nombre y la edad de la persona, separados por un espacio.
Al utilizar la función str() con una instancia de la clase Persona, se llamará automáticamente al método __str__, como en el siguiente ejemplo:
persona1 = Persona("Juan", 30) print(str(persona1)) # salida: Juan (30 años) |
__repr__()
Es un método especial en Python que se utiliza para devolver una representación de cadena legible de un objeto. Este método se define dentro de una clase y se llama cuando se usa la función repr() en un objeto de esa clase.
La representación de cadena devuelta por __repr__() debe ser una cadena que sea suficiente para recrear el objeto original. Por lo tanto, se espera que el resultado sea una cadena que contenga información relevante sobre el objeto y sus valores de atributo.
Es importante tener en cuenta que el método __repr__() no es lo mismo que el método __str__(). El método __str__() devuelve una representación de cadena legible para humanos del objeto, mientras que el método __repr__() devuelve una representación de cadena que es suficiente para recrear el objeto.
Aquí hay un ejemplo de cómo se puede implementar el método __repr__() en una clase de Python:
class Persona: def __init__(self, nombre, edad): self.nombre = nombre self.edad = edad def __repr__(self): return f"Persona(nombre='{self.nombre}', edad={self.edad})" |
Además, __add__, __sub__, __mul__ y __truediv__ se utilizan para sobrecargar los operadores aritméticos (+, -, *, /) para las instancias de una clase.
Características de la programación orientada a objetos usando las clases de Python
Encapsulación
Python soporta la encapsulación mediante el uso de métodos y atributos privados. Los métodos y atributos privados se definen mediante el uso del doble guion bajo (__). Solo puede accederse a los métodos privados por otros métodos dentro de la misma clase; no se puede acceder directamente a los atributos privados desde fuera de la clase.
class Automovil: def __init__(self, marca, modelo, color, velocidad_maxima): self.__marca = marca self.__modelo = modelo self.__color = color self.__velocidad_maxima = velocidad_maxima self.__velocidad_actual = 0 |
La encapsulación se implementa en los atributos marca, modelo, color, velocidad_maxima y velocidad_actual de la clase Automovil. Estos atributos se definen como atributos de instancia de la clase, lo que significa que son específicos de cada objeto Automóvil que se crea a partir de la clase.
Además, todos estos atributos son privados, ya que no se puede acceder directamente a ellos desde fuera de la clase. En Python, la convención es que los atributos privados se definen mediante el uso del doble guion bajo (__) al inicio del nombre del atributo, como se muestra en el código. Por ejemplo, el atributo marca se define como self.__marca.
La encapsulación en este caso permite que los atributos de la clase Automóvil no se modifiquen directamente desde fuera de la clase, lo que ayuda a garantizar que el estado interno del objeto se mantenga coherente y correcto. En cambio, se deben utilizar los métodos públicos «acelerar» y «frenar» para cambiar la velocidad del automóvil, lo que permite que la clase tenga un mayor control sobre el comportamiento del objeto y protege los datos del objeto de cambios no deseados.
Herencia
Python admite la herencia de clases mediante la creación de una clase hija que hereda los atributos y métodos de la clase padre. Para heredar una clase, simplemente se especifica el nombre de la clase padre dentro de los paréntesis después del nombre de la clase hija.
class AutomovilDeportivo(Automovil): def __init__(self, marca, modelo, color, velocidad_maxima, aleron): super().__init__(marca, modelo, color, velocidad_maxima) self.__aleron = aleron def encender_aleron(self): print("Encendiendo alerón") |
La herencia en Python se implementa utilizando la sintaxis de la palabra clave «class», y se puede lograr mediante la definición de una nueva clase que hereda los atributos y métodos de una clase base (o clase padre).
En el caso de la clase Automóvil, se puede implementar la herencia mediante la definición de una nueva clase que hereda los atributos y métodos de la clase Automóvil. Por ejemplo, se puede crear una nueva clase AutomovilDeportivo que sea heredera de la clase Automóvil, y que tenga algunos atributos y métodos adicionales específicos para los automóviles deportivos.
Para implementar la herencia, se utiliza la sintaxis class <nombre_de_la_clase_hija>(<nombre_de_la_clase_padre>):, donde <nombre_de_la_clase_hija> es el nombre de la nueva clase que se está creando, y <nombre_de_la_clase_padre> es el nombre de la clase base de la cual se heredan los atributos y métodos.
En este código, la clase AutomovilDeportivo se define como una subclase de la clase Automóvil mediante la sintaxis class AutomovilDeportivo(Automovil):. La clase AutomovilDeportivo hereda todos los atributos y métodos de la clase Automóvil, y también tiene un atributo adicional llamado aleron.
El método __init__ se redefine para la subclase AutomovilDeportivo, y se utiliza la función super() para llamar al constructor de la clase padre. De esta manera, se heredan los atributos de la clase Automóvil y se inicializan correctamente en la subclase AutomovilDeportivo.
Además, se define un nuevo método llamado encender_aleron que es específico de los automóviles deportivos, y que no existe en la clase base Automóvil. Este método puede utilizarse para encender el alerón del automóvil deportivo, lo que puede mejorar su aerodinámica en altas velocidades.
De esta forma, la herencia permite crear nuevas clases que extiendan el comportamiento de la clase base y que puedan tener sus propios atributos y métodos adicionales.
Polimorfismo
Python permite la implementación de polimorfismo a través del uso de herencia y sobreescritura de métodos. El polimorfismo permite que un objeto se comporte de manera diferente en distintos contextos.
En Python, el polimorfismo se implementa de manera natural gracias a la capacidad de las funciones y métodos para aceptar argumentos de diferentes tipos. Por lo tanto, para implementar el polimorfismo en la clase Automóvil, simplemente se deben definir diferentes métodos que compartan el mismo nombre pero que acepten distintos tipos de argumentos.
Por ejemplo, se podría definir un método llamado «acelerar» en la clase Automóvil, el cual acepte un argumento que represente la cantidad de gas que se quiere suministrar al motor. Luego, se podrían definir diferentes versiones de este método que acepten distintos tipos de argumentos.
class Automovil: def __init__(self, marca, modelo, color, velocidad_maxima): self.__marca = marca self.__modelo = modelo self.__color = color self.__velocidad_maxima = velocidad_maxima def acelerar(self, gas): print("Suministrando gas al motor:", gas) def frenar(self): print("Frenando el automóvil") class AutomovilElectrico(Automovil): def __init__(self, marca, modelo, color, velocidad_maxima, capacidad_bateria): super().__init__(marca, modelo, color, velocidad_maxima) self.__capacidad_bateria = capacidad_bateria def acelerar(self, nivel_carga): if nivel_carga >= 50: print("El automóvil eléctrico acelera rápidamente") else: print("El automóvil eléctrico acelera lentamente") class AutomovilDeportivo(Automovil): def __init__(self, marca, modelo, color, velocidad_maxima, aleron): super().__init__(marca, modelo, color, velocidad_maxima) self.__aleron = aleron def acelerar(self, pedal_a_fondo=True): if pedal_a_fondo: print("El automóvil deportivo acelera rápidamente") else: print("El automóvil deportivo acelera lentamente") |
En este ejemplo, la clase Automóvil tiene un método llamado acelerar que acepta un argumento gas, que representa la cantidad de gas que se suministra al motor. La subclase AutomovilElectrico redefine el método acelerar para aceptar un argumento nivel_carga, que representa el nivel de carga de la batería del automóvil eléctrico. Si el nivel de carga es mayor o igual a 50, el automóvil acelera rápidamente; de lo contrario, acelera lentamente.
La subclase AutomovilDeportivo también redefine el método acelerar, esta vez para aceptar un argumento opcional pedal_a_fondo. Si este argumento es True, el automóvil deportivo acelera rápidamente; de lo contrario, lo hace lentamente.
Abstracción
Este lenguaje admite la abstracción de datos y comportamientos complejos mediante la creación de clases y métodos abstractos. Una clase abstracta es aquella que no se puede instanciar y se utiliza como plantilla para crear clases hijas. Un método abstracto es aquel que no tiene una implementación en la clase abstracta y debe implementarse en la clase hija.
La abstracción en Python se implementa utilizando las clases abstractas. Las clases abstractas no pueden ser instanciadas directamente, sino que se usan como base para otras clases. En Python, las clases abstractas se definen utilizando el módulo abc (Abstract Base Class).
Para implementar la abstracción en la clase Automóvil, se podría definir una clase abstracta llamada AutomovilAbstracto, que define los métodos que deben implementarse en las subclases, pero que no proporciona una implementación real para estos métodos. Luego, se podrían definir las subclases AutomovilGasolina, AutomovilElectrico y AutomovilDeportivo que son herederas de la clase abstracta AutomovilAbstracto y proporcionan una implementación concreta para los métodos definidos en la clase abstracta.
from abc import ABC, abstractmethod class AutomovilAbstracto(ABC): @abstractmethod def acelerar(self): pass @abstractmethod def frenar(self): pass class AutomovilGasolina(AutomovilAbstracto): def acelerar(self): print("Suministrando gasolina al motor") # Código para acelerar el automóvil def frenar(self): print("Frenando el automóvil") # Código para frenar el automóvil class AutomovilElectrico(AutomovilAbstracto): def acelerar(self): print("Suministrando energía eléctrica al motor") # Código para acelerar el automóvil eléctrico def frenar(self): print("Frenando el automóvil eléctrico") # Código para frenar el automóvil eléctrico class AutomovilDeportivo(AutomovilAbstracto): def acelerar(self): print("Acelerando el automóvil deportivo") # Código para acelerar el automóvil deportivo def frenar(self): print("Frenando el automóvil deportivo") # Código para frenar el automóvil deportivo |
En este ejemplo, la clase AutomovilAbstracto es una clase abstracta que define dos métodos abstractos: acelerar y frenar. Estos métodos no tienen una implementación real, sino que simplemente están definidos con la palabra clave «abstractmethod». Esta clase se utiliza como base para las subclases AutomovilGasolina, AutomovilElectrico y AutomovilDeportivo.
Cada subclase ejecuta los métodos acelerar y frenar con una implementación concreta para el tipo de automóvil que representan. De esta manera, se proporciona una abstracción de la clase Automóvil, ya que la implementación real de los métodos acelerar y frenar depende del tipo de automóvil y se delega a las subclases.
Ahora sabes que las clases en Python son una herramienta muy útil para implementar la programación orientada a objetos. La POO es una técnica de programación que permite modelar el mundo real en términos de objetos y sus interacciones.
En general, las clases en Python permiten encapsular los datos y comportamientos, lo que hace que el código sea más seguro y menos propenso a errores. Además, la herencia permite la reutilización de código y la definición de jerarquías de objetos, lo que lo vuelve modular y escalable. A su vez, el polimorfismo permite que objetos de diferentes clases respondan a una misma acción de manera diferente, lo que aumenta la flexibilidad.