Python
Tutorial Python: Clases y objetos
Aprende los conceptos clave de clases y objetos en Python para dominar la programación orientada a objetos con ejemplos prácticos y buenas prácticas.
Aprende Python y certifícateConcepto teórico de clase y objeto
La programación orientada a objetos (POO) representa un paradigma que nos permite modelar el mundo real en nuestro código. En Python, como en otros lenguajes de programación modernos, este enfoque se implementa mediante dos conceptos fundamentales: clases y objetos.
Imagina que quieres construir casas. Antes de construir una casa real, necesitas un plano que defina cómo será esa casa: cuántas habitaciones tendrá, cómo se distribuirán, qué materiales se usarán, etc. En programación, este plano sería una clase. Una vez que tienes el plano, puedes construir múltiples casas basadas en él; estas casas serían los objetos.
Clases: los planos
Una clase es esencialmente una plantilla o un plano que define:
- Las características (atributos) que tendrán los objetos creados a partir de ella
- Los comportamientos (métodos) que podrán realizar estos objetos
En Python, definimos una clase usando la palabra clave class
:
class Coche:
# Aquí definiremos los atributos y métodos
pass
Esta definición crea un "plano" para coches, pero aún no hemos creado ningún coche real. Es como tener el diseño de un coche en papel, pero sin haber fabricado ninguno todavía.
Objetos: las instancias concretas
Un objeto (también llamado instancia) es una realización concreta de una clase. Si la clase es el plano, el objeto es la casa construida siguiendo ese plano. Cada objeto:
- Tiene su propio conjunto de valores para los atributos definidos en la clase
- Puede realizar las acciones (métodos) definidas en la clase
Creamos un objeto a partir de una clase llamando a la clase como si fuera una función:
# Creamos dos objetos de tipo Coche
mi_coche = Coche()
coche_de_amigo = Coche()
Ahora tenemos dos objetos diferentes (mi_coche
y coche_de_amigo
), ambos basados en la misma clase Coche
. Son como dos casas construidas siguiendo el mismo plano, pero que pueden tener características diferentes (color, tamaño, etc.).
La relación entre clases y objetos
Para entender mejor la relación entre clases y objetos, consideremos algunos ejemplos cotidianos:
- Clase: Perro → Objetos: Fido, Rex, Lassie
- Clase: Smartphone → Objetos: Mi iPhone, Tu Samsung Galaxy
- Clase: Cuenta Bancaria → Objetos: Mi cuenta corriente, Tu cuenta de ahorros
Cada objeto es una instancia única de su clase, con sus propios valores para los atributos definidos en la clase. Por ejemplo, todos los perros tienen una raza y una edad (atributos definidos en la clase Perro
), pero cada perro concreto tendrá valores específicos para esos atributos (Fido puede ser un Labrador de 3 años, mientras que Rex puede ser un Pastor Alemán de 5 años).
Abstracción y encapsulamiento
La POO nos permite implementar dos conceptos importantes:
Abstracción: Nos permite centrarnos en lo que hace un objeto, sin preocuparnos por cómo lo hace internamente. Por ejemplo, cuando usamos un smartphone, no necesitamos saber cómo funciona su circuitería interna.
Encapsulamiento: Nos permite agrupar datos (atributos) y comportamientos (métodos) relacionados en una sola unidad (la clase), y controlar el acceso a estos componentes.
Ventajas del enfoque orientado a objetos
El uso de clases y objetos en Python ofrece varias ventajas:
- Organización del código: Agrupar datos y comportamientos relacionados hace que el código sea más organizado y fácil de entender.
- Reutilización: Una vez definida una clase, podemos crear múltiples objetos basados en ella sin repetir código.
- Modularidad: Podemos modificar la implementación interna de una clase sin afectar al código que la utiliza, siempre que mantengamos la misma interfaz.
- Modelado del mundo real: Las clases y objetos nos permiten representar entidades del mundo real de manera intuitiva en nuestro código.
Ejemplo conceptual
Para ilustrar estos conceptos, pensemos en una clase Libro
:
class Libro:
# Aquí definiremos atributos como título, autor, páginas
# Y métodos como abrir(), leer(), cerrar()
pass
# Creamos objetos (instancias) de la clase Libro
libro_python = Libro() # Un libro específico sobre Python
novela_fantasia = Libro() # Una novela de fantasía específica
Cada libro tendrá sus propios valores para atributos como título y autor, y podrá realizar acciones como ser abierto o leído. La clase Libro
define qué características y comportamientos tendrán todos los libros, mientras que cada objeto representa un libro específico con sus propios valores para esas características.
En las siguientes secciones, veremos cómo implementar concretamente estos conceptos en Python, definiendo constructores, atributos y métodos para nuestras clases.
Clase y constructor
Una vez comprendido el concepto teórico de clases y objetos, es momento de aprender cómo implementar clases en Python y cómo crear objetos a partir de ellas. El primer paso para definir una clase funcional es entender el constructor, que es el método especial que se ejecuta automáticamente cuando creamos un nuevo objeto.
En Python, una clase se define utilizando la palabra clave class
seguida del nombre de la clase (generalmente en formato PascalCase) y dos puntos. Dentro del bloque de la clase, definiremos sus atributos y métodos:
class Persona:
# Aquí irá el código de la clase
pass
El método constructor: __init__
El constructor es un método especial llamado __init__
(con doble guion bajo al inicio y al final) que Python ejecuta automáticamente cada vez que creamos una nueva instancia de la clase. Su principal función es inicializar los atributos del objeto recién creado.
class Persona:
def __init__(self, nombre, edad):
self.nombre = nombre
self.edad = edad
Analicemos este constructor:
def __init__(self, nombre, edad):
- Define el método constructor que recibe tres parámetros:self
- Referencia al objeto que se está creando (siempre es el primer parámetro)nombre
yedad
- Datos que queremos asignar al nuevo objetoself.nombre = nombre
- Crea un atributo llamadonombre
en el objeto y le asigna el valor del parámetronombre
self.edad = edad
- Crea un atributo llamadoedad
en el objeto y le asigna el valor del parámetroedad
El parámetro self
El parámetro self
es una referencia al objeto que se está creando o manipulando. Es obligatorio como primer parámetro en todos los métodos de instancia (incluido el constructor), pero no lo proporcionamos explícitamente al crear el objeto o llamar a sus métodos. Python lo pasa automáticamente.
# Creamos un objeto Persona
ana = Persona("Ana García", 28)
# Python internamente hace algo equivalente a:
# Persona.__init__(ana, "Ana García", 28)
Cuando creamos el objeto ana
, Python llama automáticamente al método __init__
pasando el nuevo objeto como self
y los argumentos que proporcionamos.
Creación de objetos
Para crear un objeto (instancia) de una clase, simplemente llamamos a la clase como si fuera una función, pasando los argumentos que requiere su constructor:
# Creamos dos objetos Persona
ana = Persona("Ana García", 28)
juan = Persona("Juan López", 35)
# Accedemos a sus atributos
print(ana.nombre) # Imprime: Ana García
print(juan.edad) # Imprime: 35
Cada objeto tiene sus propios valores para los atributos definidos en la clase. En este caso, ana
y juan
son dos objetos independientes, cada uno con sus propios valores de nombre
y edad
.
Valores predeterminados en el constructor
Podemos definir valores predeterminados para los parámetros del constructor, haciendo que sean opcionales:
class Producto:
def __init__(self, nombre, precio, stock=0):
self.nombre = nombre
self.precio = precio
self.stock = stock
# Creamos productos con y sin especificar el stock
laptop = Producto("Laptop XPS", 1200) # stock será 0
teclado = Producto("Teclado mecánico", 80, 15) # stock será 15
print(laptop.stock) # Imprime: 0
print(teclado.stock) # Imprime: 15
Inicialización de atributos con cálculos
El constructor no solo asigna valores directamente, también puede realizar cálculos para inicializar atributos:
class Rectangulo:
def __init__(self, ancho, alto):
self.ancho = ancho
self.alto = alto
self.area = ancho * alto # Calculamos y almacenamos el área
self.perimetro = 2 * (ancho + alto) # Calculamos y almacenamos el perímetro
# Creamos un rectángulo
rect = Rectangulo(5, 3)
print(rect.area) # Imprime: 15
print(rect.perimetro) # Imprime: 16
Atributos con validación
El constructor también puede incluir validaciones para asegurar que los objetos se creen con valores válidos:
class Cuenta:
def __init__(self, titular, saldo_inicial):
self.titular = titular
# Validamos que el saldo inicial no sea negativo
if saldo_inicial < 0:
raise ValueError("El saldo inicial no puede ser negativo")
self.saldo = saldo_inicial
# Esto funcionará
cuenta_ana = Cuenta("Ana García", 1000)
# Esto lanzará un ValueError
try:
cuenta_problematica = Cuenta("Juan López", -500)
except ValueError as e:
print(f"Error: {e}") # Imprime: Error: El saldo inicial no puede ser negativo
Ejemplo práctico: Modelando una biblioteca
Veamos un ejemplo más completo modelando libros en una biblioteca:
class Libro:
def __init__(self, titulo, autor, paginas, isbn, disponible=True):
self.titulo = titulo
self.autor = autor
self.paginas = paginas
self.isbn = isbn
self.disponible = disponible
self.pagina_actual = 0 # Inicializamos en la página 0 (cerrado)
# Creamos algunos libros
libro1 = Libro("Python Crash Course", "Eric Matthes", 544, "9781593279288")
libro2 = Libro("Clean Code", "Robert C. Martin", 464, "9780132350884", False)
# Verificamos si están disponibles
print(f"{libro1.titulo} está {'disponible' if libro1.disponible else 'prestado'}")
print(f"{libro2.titulo} está {'disponible' if libro2.disponible else 'prestado'}")
Constructores alternativos con métodos de clase
A veces necesitamos diferentes formas de crear objetos. Python no admite sobrecarga de constructores como otros lenguajes, pero podemos usar métodos de clase como constructores alternativos:
class Fecha:
def __init__(self, dia, mes, año):
self.dia = dia
self.mes = mes
self.año = año
@classmethod
def desde_texto(cls, texto):
"""Constructor alternativo que crea una Fecha desde un texto con formato DD-MM-AAAA"""
dia, mes, año = map(int, texto.split('-'))
return cls(dia, mes, año)
@classmethod
def hoy(cls):
"""Constructor alternativo que crea una Fecha con la fecha actual"""
import datetime
fecha_actual = datetime.date.today()
return cls(fecha_actual.day, fecha_actual.month, fecha_actual.year)
# Diferentes formas de crear objetos Fecha
fecha1 = Fecha(15, 3, 2023) # Constructor normal
fecha2 = Fecha.desde_texto("25-12-2023") # Constructor alternativo
fecha3 = Fecha.hoy() # Constructor alternativo que usa la fecha actual
print(f"{fecha1.dia}/{fecha1.mes}/{fecha1.año}") # Imprime: 15/3/2023
print(f"{fecha2.dia}/{fecha2.mes}/{fecha2.año}") # Imprime: 25/12/2023
En este ejemplo, @classmethod
es un decorador que indica que el método recibe la clase (cls
) en lugar del objeto (self
) como primer parámetro, permitiéndonos crear nuevas instancias de la clase.
Buenas prácticas al definir constructores
- Mantén los constructores simples: Enfócate en inicializar atributos, evitando operaciones complejas.
- Usa valores predeterminados para parámetros opcionales.
- Valida los datos de entrada para evitar estados inválidos.
- Documenta el constructor explicando qué parámetros espera y qué hace.
- Considera constructores alternativos para diferentes formas de crear objetos.
El constructor es la puerta de entrada a tus objetos, y definirlo correctamente es fundamental para crear clases robustas y fáciles de usar. En las siguientes secciones, exploraremos con más detalle los atributos y métodos que dan vida a nuestras clases.
Atributos
Los atributos son las características o propiedades que definen a los objetos creados a partir de una clase. Si continuamos con nuestra analogía del plano y la casa, los atributos serían las características específicas de cada casa: su color, número de habitaciones, tamaño, etc. En Python, los atributos son simplemente variables asociadas a un objeto.
Existen diferentes tipos de atributos y formas de trabajar con ellos en Python. Vamos a explorarlos en detalle.
Tipos de atributos
En Python podemos distinguir principalmente dos tipos de atributos:
- Atributos de instancia: Pertenecen a cada objeto individual (instancia) y pueden tener valores diferentes para cada objeto.
- Atributos de clase: Pertenecen a la clase en sí y son compartidos por todas las instancias de esa clase.
Atributos de instancia
Los atributos de instancia son los más comunes. Se definen típicamente en el constructor (__init__
) y son específicos para cada objeto:
class Estudiante:
def __init__(self, nombre, edad):
self.nombre = nombre # Atributo de instancia
self.edad = edad # Atributo de instancia
self.activo = True # Atributo de instancia con valor predeterminado
# Creamos dos estudiantes
estudiante1 = Estudiante("María", 20)
estudiante2 = Estudiante("Carlos", 22)
# Cada estudiante tiene sus propios valores para los atributos
print(estudiante1.nombre) # Imprime: María
print(estudiante2.nombre) # Imprime: Carlos
Cada objeto Estudiante
tiene sus propios valores para nombre
, edad
y activo
. Modificar el atributo de un objeto no afecta a los demás.
Atributos de clase
Los atributos de clase se definen directamente en la clase, fuera de cualquier método, y son compartidos por todas las instancias:
class Estudiante:
# Atributo de clase
universidad = "Universidad Autónoma"
def __init__(self, nombre, edad):
self.nombre = nombre # Atributo de instancia
self.edad = edad # Atributo de instancia
# Creamos dos estudiantes
estudiante1 = Estudiante("María", 20)
estudiante2 = Estudiante("Carlos", 22)
# Ambos comparten el mismo atributo de clase
print(estudiante1.universidad) # Imprime: Universidad Autónoma
print(estudiante2.universidad) # Imprime: Universidad Autónoma
print(Estudiante.universidad) # También podemos acceder desde la clase
# Si modificamos el atributo de clase, afecta a todas las instancias
Estudiante.universidad = "Universidad Complutense"
print(estudiante1.universidad) # Imprime: Universidad Complutense
print(estudiante2.universidad) # Imprime: Universidad Complutense
Los atributos de clase son útiles para:
- Definir constantes o valores predeterminados compartidos por todos los objetos
- Mantener contadores o estadísticas sobre todas las instancias
- Almacenar configuración común a todos los objetos
Acceso a atributos
Para acceder a los atributos de un objeto, utilizamos la notación de punto:
class Producto:
impuesto = 0.21 # Atributo de clase
def __init__(self, nombre, precio):
self.nombre = nombre
self.precio = precio
# Creamos un producto
laptop = Producto("Laptop", 1000)
# Accedemos a sus atributos
print(laptop.nombre) # Atributo de instancia
print(laptop.precio) # Atributo de instancia
print(laptop.impuesto) # Atributo de clase (accedido desde la instancia)
print(Producto.impuesto) # Atributo de clase (accedido desde la clase)
Modificación de atributos
Los atributos pueden modificarse después de crear el objeto:
class Coche:
def __init__(self, marca, modelo, color):
self.marca = marca
self.modelo = modelo
self.color = color
self.kilometraje = 0
# Creamos un coche nuevo
mi_coche = Coche("Toyota", "Corolla", "Azul")
print(f"Color inicial: {mi_coche.color}") # Imprime: Color inicial: Azul
print(f"Kilometraje inicial: {mi_coche.kilometraje}") # Imprime: Kilometraje inicial: 0
# Modificamos sus atributos
mi_coche.color = "Rojo" # Pintamos el coche
mi_coche.kilometraje = 1500 # Actualizamos el kilometraje
print(f"Nuevo color: {mi_coche.color}") # Imprime: Nuevo color: Rojo
print(f"Kilometraje actual: {mi_coche.kilometraje}") # Imprime: Kilometraje actual: 1500
Atributos dinámicos
Una característica poderosa de Python es que podemos añadir atributos a un objeto después de crearlo:
class Persona:
def __init__(self, nombre):
self.nombre = nombre
# Creamos una persona
juan = Persona("Juan")
# Añadimos atributos dinámicamente
juan.edad = 30
juan.profesion = "Ingeniero"
print(f"{juan.nombre} tiene {juan.edad} años y es {juan.profesion}")
# Imprime: Juan tiene 30 años y es Ingeniero
Aunque esta flexibilidad es útil, puede llevar a errores si no se maneja con cuidado. Es mejor definir todos los atributos en el constructor para mantener la consistencia.
Atributos privados y convenciones de nomenclatura
Python no tiene un mecanismo estricto de encapsulamiento como otros lenguajes, pero utiliza convenciones de nomenclatura para indicar la visibilidad prevista de los atributos:
- Atributos públicos: Nombres normales como
nombre
oedad
. Se pueden acceder desde cualquier parte. - Atributos "protegidos": Prefijados con un guion bajo como
_saldo
. Indica que no deberían accederse directamente desde fuera de la clase o sus subclases. - Atributos "privados": Prefijados con doble guion bajo como
__pin
. Python aplica "name mangling" (modificación del nombre) para dificultar el acceso accidental desde fuera.
class CuentaBancaria:
tasa_interes = 0.03 # Atributo de clase público
def __init__(self, titular, saldo_inicial, pin):
self.titular = titular # Atributo público
self._saldo = saldo_inicial # Atributo "protegido"
self.__pin = pin # Atributo "privado"
def verificar_pin(self, pin_ingresado):
return self.__pin == pin_ingresado
# Creamos una cuenta
cuenta = CuentaBancaria("Ana López", 1000, "1234")
# Acceso a atributos según su visibilidad
print(cuenta.titular) # Funciona: atributo público
print(cuenta._saldo) # Funciona, pero no deberíamos hacerlo por convención
# print(cuenta.__pin) # Error: no existe tal atributo debido al name mangling
# El atributo privado existe, pero con un nombre modificado
print(cuenta._CuentaBancaria__pin) # Funciona, pero es una mala práctica
Es importante entender que los guiones bajos son solo convenciones (excepto el doble guion que sí modifica el nombre). Depende de los desarrolladores respetar estas convenciones.
Propiedades: atributos con comportamiento controlado
Las propiedades nos permiten definir métodos que se comportan como atributos, añadiendo lógica al acceso y modificación:
class Temperatura:
def __init__(self):
self._celsius = 0
# Definimos la propiedad celsius
@property
def celsius(self):
"""Obtiene la temperatura en grados Celsius"""
return self._celsius
@celsius.setter
def celsius(self, valor):
"""Establece la temperatura en grados Celsius"""
if valor < -273.15:
raise ValueError("La temperatura no puede ser menor que el cero absoluto")
self._celsius = valor
# Definimos la propiedad fahrenheit
@property
def fahrenheit(self):
"""Obtiene la temperatura en grados Fahrenheit"""
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, valor):
"""Establece la temperatura en grados Fahrenheit"""
self.celsius = (valor - 32) * 5/9
# Creamos un objeto temperatura
temp = Temperatura()
# Usamos las propiedades como si fueran atributos normales
temp.celsius = 25
print(f"{temp.celsius}°C = {temp.fahrenheit}°F") # Imprime: 25°C = 77.0°F
temp.fahrenheit = 68
print(f"{temp.celsius}°C = {temp.fahrenheit}°F") # Imprime: 20.0°C = 68.0°F
# La validación funciona
try:
temp.celsius = -300 # Esto lanzará un error
except ValueError as e:
print(f"Error: {e}") # Imprime: Error: La temperatura no puede ser menor que el cero absoluto
Las propiedades son ideales para:
- Validar datos antes de asignarlos
- Calcular valores derivados
- Mantener la consistencia entre atributos relacionados
- Implementar atributos de solo lectura
Atributos calculados
A veces, algunos atributos pueden derivarse de otros. En lugar de almacenarlos, podemos calcularlos cuando se necesiten:
class Rectangulo:
def __init__(self, ancho, alto):
self.ancho = ancho
self.alto = alto
@property
def area(self):
"""Área del rectángulo, calculada dinámicamente"""
return self.ancho * self.alto
@property
def perimetro(self):
"""Perímetro del rectángulo, calculado dinámicamente"""
return 2 * (self.ancho + self.alto)
# Creamos un rectángulo
rect = Rectangulo(5, 3)
# Accedemos a los atributos calculados
print(f"Área: {rect.area}") # Imprime: Área: 15
print(f"Perímetro: {rect.perimetro}") # Imprime: Perímetro: 16
# Si modificamos el rectángulo, los atributos calculados se actualizan automáticamente
rect.ancho = 7
print(f"Nueva área: {rect.area}") # Imprime: Nueva área: 21
Atributos especiales
Python define algunos atributos especiales para todas las clases e instancias, que comienzan y terminan con doble guion bajo. Algunos útiles son:
class Ejemplo:
"""Clase de ejemplo para mostrar atributos especiales"""
def __init__(self, valor):
self.valor = valor
# Creamos una instancia
obj = Ejemplo(42)
# Atributos especiales
print(obj.__class__) # Muestra la clase del objeto
print(Ejemplo.__name__) # Nombre de la clase
print(Ejemplo.__doc__) # Documentación de la clase
print(obj.__dict__) # Diccionario que almacena los atributos de instancia
Gestión de atributos con funciones integradas
Python proporciona funciones útiles para trabajar con atributos:
class Persona:
def __init__(self, nombre, edad):
self.nombre = nombre
self.edad = edad
p = Persona("Laura", 29)
# Verificar si un objeto tiene un atributo
print(hasattr(p, "nombre")) # True
print(hasattr(p, "apellido")) # False
# Obtener el valor de un atributo
print(getattr(p, "nombre")) # Laura
print(getattr(p, "apellido", "No especificado")) # No especificado (valor predeterminado)
# Establecer un atributo
setattr(p, "apellido", "García")
print(p.apellido) # García
# Eliminar un atributo
delattr(p, "apellido")
# print(p.apellido) # Esto daría error porque ya no existe
Estas funciones son especialmente útiles cuando necesitamos trabajar con atributos de forma dinámica, por ejemplo, cuando el nombre del atributo se determina en tiempo de ejecución.
Buenas prácticas con atributos
- Inicializa todos los atributos en el constructor para evitar errores.
- Usa atributos de clase para valores compartidos entre todas las instancias.
- Respeta las convenciones de nomenclatura para indicar la visibilidad prevista.
- Utiliza propiedades para validar datos y mantener la consistencia.
- Documenta el propósito de cada atributo, especialmente si no es obvio.
- Evita modificar atributos directamente desde fuera de la clase si tienen lógica asociada.
Los atributos son fundamentales en la programación orientada a objetos, ya que definen el estado de nuestros objetos. Combinados con los métodos, que veremos en la siguiente sección, nos permiten crear modelos completos y funcionales de entidades del mundo real.
Métodos
Los métodos son funciones definidas dentro de una clase que describen los comportamientos o acciones que pueden realizar los objetos creados a partir de esa clase. Si los atributos representan lo que un objeto "es" o "tiene", los métodos representan lo que un objeto "hace" o "puede hacer".
Siguiendo nuestra analogía del plano y la casa, si los atributos son las características de la casa (número de habitaciones, color, tamaño), los métodos serían las acciones que podemos realizar con ella (abrir puertas, encender luces, calentar agua).
Definición de métodos
Para definir un método en Python, simplemente creamos una función dentro de la clase. El primer parámetro de un método de instancia siempre es self
, que hace referencia al objeto específico que está ejecutando el método:
class Coche:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
self.velocidad = 0
self.encendido = False
# Método para encender el coche
def encender(self):
if not self.encendido:
self.encendido = True
return f"{self.marca} {self.modelo} encendido"
return f"{self.marca} {self.modelo} ya estaba encendido"
# Método para apagar el coche
def apagar(self):
if self.encendido:
self.encendido = False
self.velocidad = 0
return f"{self.marca} {self.modelo} apagado"
return f"{self.marca} {self.modelo} ya estaba apagado"
Llamada a métodos
Para llamar a un método, utilizamos la notación de punto después del nombre del objeto:
mi_coche = Coche("Toyota", "Corolla")
print(mi_coche.encender()) # Imprime: Toyota Corolla encendido
print(mi_coche.encender()) # Imprime: Toyota Corolla ya estaba encendido
print(mi_coche.apagar()) # Imprime: Toyota Corolla apagado
Métodos con parámetros
Los métodos pueden recibir parámetros adicionales además de self
:
class Coche:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
self.velocidad = 0
self.encendido = False
self.velocidad_maxima = 200
def encender(self):
if not self.encendido:
self.encendido = True
return f"{self.marca} {self.modelo} encendido"
return f"{self.marca} {self.modelo} ya estaba encendido"
# Método con parámetro
def acelerar(self, incremento):
if not self.encendido:
return f"No se puede acelerar: {self.marca} {self.modelo} está apagado"
nueva_velocidad = self.velocidad + incremento
if nueva_velocidad > self.velocidad_maxima:
self.velocidad = self.velocidad_maxima
return f"Velocidad máxima alcanzada: {self.velocidad} km/h"
self.velocidad = nueva_velocidad
return f"Velocidad actual: {self.velocidad} km/h"
# Otro método con parámetro
def frenar(self, decremento):
if self.velocidad == 0:
return "El coche ya está detenido"
nueva_velocidad = self.velocidad - decremento
if nueva_velocidad < 0:
self.velocidad = 0
return "Coche detenido"
self.velocidad = nueva_velocidad
return f"Velocidad actual: {self.velocidad} km/h"
Ahora podemos usar estos métodos con parámetros:
mi_coche = Coche("Toyota", "Corolla")
print(mi_coche.encender()) # Toyota Corolla encendido
print(mi_coche.acelerar(50)) # Velocidad actual: 50 km/h
print(mi_coche.acelerar(30)) # Velocidad actual: 80 km/h
print(mi_coche.frenar(20)) # Velocidad actual: 60 km/h
print(mi_coche.frenar(60)) # Coche detenido
Métodos que interactúan con atributos
Los métodos pueden leer y modificar los atributos del objeto, permitiendo mantener la consistencia interna y aplicar reglas de negocio:
class CuentaBancaria:
def __init__(self, titular, saldo_inicial=0):
self.titular = titular
self._saldo = saldo_inicial
def consultar_saldo(self):
return f"Saldo actual de {self.titular}: ${self._saldo}"
def depositar(self, cantidad):
if cantidad <= 0:
return "La cantidad a depositar debe ser positiva"
self._saldo += cantidad
return f"Depósito de ${cantidad} realizado. Nuevo saldo: ${self._saldo}"
def retirar(self, cantidad):
if cantidad <= 0:
return "La cantidad a retirar debe ser positiva"
if cantidad > self._saldo:
return "Fondos insuficientes"
self._saldo -= cantidad
return f"Retiro de ${cantidad} realizado. Nuevo saldo: ${self._saldo}"
Ejemplo de uso:
cuenta = CuentaBancaria("Ana López", 1000)
print(cuenta.consultar_saldo()) # Saldo actual de Ana López: $1000
print(cuenta.depositar(500)) # Depósito de $500 realizado. Nuevo saldo: $1500
print(cuenta.retirar(200)) # Retiro de $200 realizado. Nuevo saldo: $1300
print(cuenta.retirar(2000)) # Fondos insuficientes
Métodos que devuelven valores
Los métodos pueden devolver cualquier tipo de dato, incluyendo objetos complejos:
class Calculadora:
def sumar(self, a, b):
return a + b
def restar(self, a, b):
return a - b
def multiplicar(self, a, b):
return a * b
def dividir(self, a, b):
if b == 0:
return "Error: División por cero"
return a / b
def calcular_estadisticas(self, numeros):
if not numeros:
return {
"suma": 0,
"promedio": 0,
"minimo": None,
"maximo": None
}
return {
"suma": sum(numeros),
"promedio": sum(numeros) / len(numeros),
"minimo": min(numeros),
"maximo": max(numeros)
}
Ejemplo de uso:
calc = Calculadora()
print(calc.sumar(5, 3)) # 8
print(calc.dividir(10, 2)) # 5.0
print(calc.dividir(10, 0)) # Error: División por cero
# Método que devuelve un diccionario
estadisticas = calc.calcular_estadisticas([4, 7, 2, 9, 5])
print(f"Suma: {estadisticas['suma']}") # Suma: 27
print(f"Promedio: {estadisticas['promedio']}") # Promedio: 5.4
print(f"Mínimo: {estadisticas['minimo']}") # Mínimo: 2
print(f"Máximo: {estadisticas['maximo']}") # Máximo: 9
Métodos que llaman a otros métodos
Un método puede llamar a otros métodos del mismo objeto usando self
:
class Persona:
def __init__(self, nombre, apellido, edad):
self.nombre = nombre
self.apellido = apellido
self.edad = edad
def nombre_completo(self):
return f"{self.nombre} {self.apellido}"
def es_mayor_de_edad(self):
return self.edad >= 18
def presentarse(self):
estado = "mayor" if self.es_mayor_de_edad() else "menor"
return f"Hola, soy {self.nombre_completo()} y soy {estado} de edad."
Ejemplo de uso:
persona = Persona("Juan", "Pérez", 25)
print(persona.nombre_completo()) # Juan Pérez
print(persona.es_mayor_de_edad()) # True
print(persona.presentarse()) # Hola, soy Juan Pérez y soy mayor de edad.
Métodos especiales (dunder methods)
Python define varios métodos especiales (también llamados "dunder methods" por los dobles guiones bajos) que permiten a nuestras clases interactuar con operadores y funciones integradas del lenguaje:
class Punto:
def __init__(self, x, y):
self.x = x
self.y = y
# Representación para desarrolladores (detallada)
def __repr__(self):
return f"Punto({self.x}, {self.y})"
# Representación para usuarios (amigable)
def __str__(self):
return f"({self.x}, {self.y})"
# Soporte para el operador +
def __add__(self, otro):
return Punto(self.x + otro.x, self.y + otro.y)
# Soporte para el operador ==
def __eq__(self, otro):
if not isinstance(otro, Punto):
return False
return self.x == otro.x and self.y == otro.y
# Soporte para len()
def __len__(self):
# Distancia Manhattan desde el origen
return abs(self.x) + abs(self.y)
Ejemplo de uso:
p1 = Punto(3, 4)
p2 = Punto(1, 2)
# Uso de __str__ (implícito)
print(p1) # (3, 4)
# Uso de __repr__ (explícito)
print(repr(p1)) # Punto(3, 4)
# Uso de __add__
p3 = p1 + p2
print(p3) # (4, 6)
# Uso de __eq__
print(p1 == p2) # False
print(p1 == Punto(3, 4)) # True
# Uso de __len__
print(len(p1)) # 7 (3 + 4)
Algunos métodos especiales comunes:
__init__
: Constructor__str__
: Representación de cadena para usuarios__repr__
: Representación de cadena para desarrolladores__len__
: Soporte para la funciónlen()
__add__
,__sub__
, etc.: Soporte para operadores aritméticos__eq__
,__lt__
, etc.: Soporte para operadores de comparación__getitem__
,__setitem__
: Soporte para acceso con corchetesobj[key]
Métodos estáticos y de clase
Además de los métodos de instancia (que reciben self
), Python permite definir:
Métodos estáticos
Los métodos estáticos no reciben automáticamente ni la instancia ni la clase. Son funciones normales que están lógicamente agrupadas en la clase:
class MathUtils:
@staticmethod
def es_primo(n):
"""Verifica si un número es primo"""
if n <= 1:
return False
if n <= 3:
return True
if n % 2 == 0 or n % 3 == 0:
return False
i = 5
while i * i <= n:
if n % i == 0 or n % (i + 2) == 0:
return False
i += 6
return True
@staticmethod
def factorial(n):
"""Calcula el factorial de n"""
if n < 0:
raise ValueError("El factorial no está definido para números negativos")
if n == 0 or n == 1:
return 1
return n * MathUtils.factorial(n - 1)
Uso de métodos estáticos:
# No necesitamos crear una instancia
print(MathUtils.es_primo(17)) # True
print(MathUtils.es_primo(20)) # False
print(MathUtils.factorial(5)) # 120
Métodos de clase
Los métodos de clase reciben automáticamente la clase como primer parámetro (convencionalmente llamado cls
):
class Empleado:
# Atributo de clase
num_empleados = 0
def __init__(self, nombre, salario):
self.nombre = nombre
self.salario = salario
Empleado.num_empleados += 1
@classmethod
def desde_salario_anual(cls, nombre, salario_anual):
"""Constructor alternativo que recibe salario anual en lugar de mensual"""
salario_mensual = salario_anual / 12
return cls(nombre, salario_mensual)
@classmethod
def obtener_num_empleados(cls):
"""Devuelve el número total de empleados creados"""
return cls.num_empleados
Uso de métodos de clase:
# Creación normal
emp1 = Empleado("Ana", 3000)
# Usando el método de clase como constructor alternativo
emp2 = Empleado.desde_salario_anual("Carlos", 48000) # Salario mensual: 4000
print(f"Empleados creados: {Empleado.obtener_num_empleados()}") # Empleados creados: 2
Ejemplo práctico: Biblioteca
Veamos un ejemplo más completo que integra varios conceptos sobre métodos:
class Libro:
def __init__(self, titulo, autor, paginas):
self.titulo = titulo
self.autor = autor
self.paginas = paginas
self.pagina_actual = 0
self.abierto = False
def abrir(self):
if self.abierto:
return f"{self.titulo} ya está abierto"
self.abierto = True
return f"{self.titulo} ha sido abierto"
def cerrar(self):
if not self.abierto:
return f"{self.titulo} ya está cerrado"
self.abierto = False
return f"{self.titulo} ha sido cerrado"
def leer(self, num_paginas):
if not self.abierto:
return f"No puedes leer: {self.titulo} está cerrado"
if self.pagina_actual >= self.paginas:
return f"Ya has terminado de leer {self.titulo}"
paginas_restantes = self.paginas - self.pagina_actual
paginas_a_leer = min(num_paginas, paginas_restantes)
self.pagina_actual += paginas_a_leer
if self.pagina_actual >= self.paginas:
return f"Has leído {paginas_a_leer} páginas y has terminado {self.titulo}"
return f"Has leído {paginas_a_leer} páginas. Estás en la página {self.pagina_actual} de {self.paginas}"
def reiniciar_lectura(self):
self.pagina_actual = 0
return f"Has reiniciado la lectura de {self.titulo}"
def __str__(self):
estado = "abierto" if self.abierto else "cerrado"
progreso = f"{self.pagina_actual}/{self.paginas} páginas"
return f"{self.titulo} por {self.autor} - {progreso} - {estado}"
Ejemplo de uso:
libro = Libro("El Quijote", "Miguel de Cervantes", 863)
print(libro.leer(50)) # No puedes leer: El Quijote está cerrado
print(libro.abrir()) # El Quijote ha sido abierto
print(libro.leer(50)) # Has leído 50 páginas. Estás en la página 50 de 863
print(libro.leer(100)) # Has leído 100 páginas. Estás en la página 150 de 863
print(libro.cerrar()) # El Quijote ha sido cerrado
print(libro.abrir()) # El Quijote ha sido abierto
print(libro.leer(713)) # Has leído 713 páginas y has terminado El Quijote
print(libro.reiniciar_lectura()) # Has reiniciado la lectura de El Quijote
print(libro) # El Quijote por Miguel de Cervantes - 0/863 páginas - abierto
Buenas prácticas para métodos
- Nombres descriptivos: Usa verbos que describan claramente la acción que realiza el método.
- Responsabilidad única: Cada método debe hacer una sola cosa y hacerla bien.
- Tamaño adecuado: Mantén los métodos relativamente cortos y enfocados.
- Validación de parámetros: Verifica que los parámetros recibidos sean válidos.
- Documentación: Añade docstrings que expliquen qué hace el método, qué parámetros recibe y qué devuelve.
- Consistencia: Mantén un estilo consistente en todos los métodos de la clase.
- Encapsulamiento: Usa métodos para acceder y modificar atributos cuando sea necesario aplicar lógica adicional.
Los métodos son la parte activa de nuestras clases, definiendo el comportamiento de los objetos. Combinados con los atributos, nos permiten crear modelos completos y funcionales que representan entidades del mundo real en nuestro código.
Otras lecciones de Python
Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Python
Introducción
Instalación Y Creación De Proyecto
Introducción
Tema 2: Tipos De Datos, Variables Y Operadores
Introducción
Instalación De Python
Introducción
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Estructuras Control Iterativo
Sintaxis
Estructuras Control Condicional
Sintaxis
Testing Con Pytest
Sintaxis
Listas
Estructuras De Datos
Tuplas
Estructuras De Datos
Diccionarios
Estructuras De Datos
Conjuntos
Estructuras De Datos
Comprehensions
Estructuras De Datos
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Mixins Y Herencia Múltiple
Programación Orientada A Objetos
Métodos Especiales (Dunder Methods)
Programación Orientada A Objetos
Composición De Clases
Programación Orientada A Objetos
Funciones Lambda
Programación Funcional
Aplicación Parcial
Programación Funcional
Entrada Y Salida, Manejo De Archivos
Programación Funcional
Decoradores
Programación Funcional
Generadores
Programación Funcional
Paradigma Funcional
Programación Funcional
Composición De Funciones
Programación Funcional
Funciones Orden Superior Map Y Filter
Programación Funcional
Funciones Auxiliares
Programación Funcional
Reducción Y Acumulación
Programación Funcional
Archivos Comprimidos
Entrada Y Salida Io
Entrada Y Salida Avanzada
Entrada Y Salida Io
Archivos Temporales
Entrada Y Salida Io
Contexto With
Entrada Y Salida Io
Módulo Csv
Biblioteca Estándar
Módulo Json
Biblioteca Estándar
Módulo Datetime
Biblioteca Estándar
Módulo Math
Biblioteca Estándar
Módulo Os
Biblioteca Estándar
Módulo Re
Biblioteca Estándar
Módulo Random
Biblioteca Estándar
Módulo Time
Biblioteca Estándar
Módulo Collections
Biblioteca Estándar
Módulo Sys
Biblioteca Estándar
Módulo Statistics
Biblioteca Estándar
Módulo Pickle
Biblioteca Estándar
Módulo Pathlib
Biblioteca Estándar
Importar Módulos Y Paquetes
Paquetes Y Módulos
Crear Módulos Y Paquetes
Paquetes Y Módulos
Entornos Virtuales (Virtualenv, Venv)
Entorno Y Dependencias
Gestión De Dependencias (Pip, Requirements.txt)
Entorno Y Dependencias
Python-dotenv Y Variables De Entorno
Entorno Y Dependencias
Acceso A Datos Con Mysql, Pymongo Y Pandas
Acceso A Bases De Datos
Acceso A Mongodb Con Pymongo
Acceso A Bases De Datos
Acceso A Mysql Con Mysql Connector
Acceso A Bases De Datos
Novedades Python 3.13
Características Modernas
Operador Walrus
Características Modernas
Pattern Matching
Características Modernas
Instalación Beautiful Soup
Web Scraping
Sintaxis General De Beautiful Soup
Web Scraping
Tipos De Selectores
Web Scraping
Web Scraping De Html
Web Scraping
Web Scraping Para Ciencia De Datos
Web Scraping
Autenticación Y Acceso A Recursos Protegidos
Web Scraping
Combinación De Selenium Con Beautiful Soup
Web Scraping
Ejercicios de programación de Python
Evalúa tus conocimientos de esta lección Clases y objetos con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Módulo math
Reto herencia
Excepciones
Introducción a Python
Reto variables
Funciones Python
Reto funciones
Módulo datetime
Reto acumulación
Reto estructuras condicionales
Polimorfismo
Módulo os
Reto métodos dunder
Diccionarios
Reto clases y objetos
Reto operadores
Operadores
Estructuras de control
Funciones lambda
Reto diccionarios
Reto función lambda
Encapsulación
Reto coleciones
Reto funciones auxiliares
Crear módulos y paquetes
Módulo datetime
Excepciones
Operadores
Diccionarios
Reto map, filter
Reto tuplas
Proyecto gestor de tareas CRUD
Tuplas
Variables
Tipos de datos
Conjuntos
Reto mixins
Módulo csv
Módulo json
Herencia
Análisis de datos de ventas con Pandas
Reto fechas y tiempo
Reto estructuras de iteración
Funciones
Reto comprehensions
Variables
Reto serialización
Módulo csv
Reto polimorfismo
Polimorfismo
Clases y objetos
Reto encapsulación
Estructuras de control
Importar módulos y paquetes
Módulo math
Funciones lambda
Reto excepciones
Listas
Reto archivos
Encapsulación
Reto conjuntos
Clases y objetos
Instalación de Python y creación de proyecto
Reto listas
Tipos de datos
Crear módulos y paquetes
Tuplas
Herencia
Reto acceso a sistema
Proyecto sintaxis calculadora
Importar módulos y paquetes
Clases y objetos
Módulo os
Listas
Conjuntos
Reto tipos de datos
Reto matemáticas
Módulo json
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender el concepto de clases y objetos en programación orientada a objetos.
- Aprender a definir clases y crear objetos en Python.
- Entender el uso del método constructor init para inicializar objetos.
- Diferenciar entre atributos de instancia y de clase, y gestionar su acceso y modificación.
- Definir y utilizar métodos, incluyendo métodos especiales, estáticos y de clase, para modelar comportamientos de objetos.