Translate

jueves, 3 de abril de 2025

JEP 456: Variables y Patrones Anónimos en Java



La Propuesta de Mejora de Java (JEP) 456, titulada "Variables y Patrones Anónimos", introduce en Java la capacidad de declarar variables y patrones sin nombre utilizando el carácter de subrayado (_). Esta característica, incorporada en Java 22, permite a los desarrolladores indicar explícitamente que ciertas variables o parámetros no serán utilizados, mejorando así la claridad y mantenibilidad del código. 

En situaciones donde una variable debe ser declarada pero su valor no es necesario, se puede emplear el carácter _ como nombre de la variable. Esto es útil en contextos como bucles for, bloques catch y expresiones lambda.

Veamos un ejemplo en un bucle for:


int[] numeros = {1, 2, 3, 4, 5};

int total = 0;

for (int _ : numeros) {

    total++;

}

System.out.println("Total: " + total);


En este caso, el bucle itera sobre el array numeros sin necesidad de utilizar el valor de cada elemento, incrementando simplemente el contador total.

Veamos un ejemplo en un bloque catch:


try {

    // Código que puede lanzar una IOException

} catch (IOException _) {

    System.out.println("Ocurrió un error de E/S.");

}


Aquí, se captura la excepción IOException sin necesidad de referenciarla dentro del bloque catch. 

Los patrones anónimos permiten omitir nombres y tipos en patrones de registros cuando no se requiere acceder a ciertos componentes. Esto simplifica la sintaxis y mejora la legibilidad del código al desestructurar objetos.


record Punto(int x, int y) {}

Punto punto = new Punto(5, 10);


if (punto instanceof Punto(int _, int y)) {

    System.out.println("Coordenada y: " + y);

}


En este ejemplo, se omite el nombre de la variable para la coordenada x, ya que no es necesaria, mientras que se accede directamente a la coordenada y. 

La introducción de variables y patrones anónimos en Java 22 representa un avance significativo hacia una sintaxis más concisa y expresiva, alineada con las necesidades modernas de desarrollo.

martes, 1 de abril de 2025

Librerías estándar de Python


Python cuenta con una extensa biblioteca estándar que proporciona módulos y funciones listas para usar sin necesidad de instalar paquetes adicionales. A continuación, exploramos algunas de las librerías más útiles y cómo utilizarlas.


Manipulación de Archivos y Directorios


os - Interacción con el sistema operativo

import os

print(os.name)  # Nombre del sistema operativo

print(os.getcwd())  # Directorio actual

os.mkdir("nueva_carpeta")  # Crear una carpeta


shutil - Operaciones de archivos y directorios

import shutil

shutil.copy("archivo.txt", "copia.txt")  # Copiar un archivo

shutil.rmtree("nueva_carpeta")  # Eliminar un directorio


Manejo de Fechas y Tiempos


datetime - Fechas y horas

from datetime import datetime


ahora = datetime.now()

print(ahora.strftime("%Y-%m-%d %H:%M:%S"))  # Formateo de fecha


time - Control de tiempo y pausas

import time


time.sleep(2)  # Pausar ejecución por 2 segundos

print("Dos segundos después...")


Operaciones Matemáticas y Estadísticas


math - Funciones matemáticas


import math


print(math.sqrt(25))  # Raíz cuadrada

print(math.pi)  # Valor de PI


random - Generación de números aleatorios


import random

print(random.randint(1, 100))  # Número aleatorio entre 1 y 100


statistics - Cálculos estadísticos


import statistics


datos = [10, 20, 30, 40, 50]

print(statistics.mean(datos))  # Media

print(statistics.median(datos))  # Mediana


Manejo de Datos en Formato Texto


json - Trabajar con JSON

import json


datos = {"nombre": "Emanuel", "edad": 30}

cadena_json = json.dumps(datos)  # Convertir a JSON

print(json.loads(cadena_json))  # Convertir de JSON a diccionario


csv - Lectura y escritura de archivos CSV

import csv


with open("datos.csv", "w", newline="") as archivo:

    escritor = csv.writer(archivo)

    escritor.writerow(["Nombre", "Edad"])

    escritor.writerow(["Emanuel", 30])


Expresiones Regulares y Manejo de Texto


re - Expresiones regulares


import re


texto = "Correo: usuario@example.com"

patron = r"[\w.-]+@[\w.-]+\.\w+"

resultado = re.search(patron, texto)

print(resultado.group())  # usuario@example.com


Concurrencia y Multiprocesamiento


threading - Programación con hilos


import threading


def tarea():

    print("Ejecutando en un hilo")


hilo = threading.Thread(target=tarea)

hilo.start()


multiprocessing - Procesos en paralelo


import multiprocessing


def tarea():

    print("Ejecutando en un proceso")


proceso = multiprocessing.Process(target=tarea)

proceso.start()


Manejo de Errores y Depuración


logging - Registro de eventos y depuración


import logging


logging.basicConfig(level=logging.INFO)

logging.info("Esto es un mensaje informativo")


La biblioteca estándar de Python proporciona herramientas esenciales para diversas tareas sin necesidad de instalar paquetes adicionales. Conocer y utilizar estos módulos puede hacer que tu código sea más eficiente y organizado.


Novedades de java 22


¡JDK 22 ya está disponible!

JDK 22 ofrece 12 mejoras significativas. Estas mejoras abarcan mejoras en el lenguaje Java, sus API, su rendimiento y las herramientas incluidas en el JDK.

1) Mejoras del lenguaje:

  • Variables y patrones sin nombre - JEP 456: Mejora la legibilidad cuando se requieren declaraciones de variables o patrones anidados, pero no se utilizan. Ambos se indican con el guión bajo.

1.1) Vistas preview del lenguaje

  • Declaraciones antes de super(…) - JEP 447: En los constructores, permite que las declaraciones que no hacen referencia a la instancia que se está creando aparezcan antes de una invocación explícita del constructor.
  • Plantillas de cadena (Segunda versión preliminar) - JEP 459: Las plantillas de cadena complementan los literales de cadena y los bloques de texto existentes de Java al combinar el texto literal con expresiones integradas y procesadores de plantillas para producir resultados especializados.
  • Clases declaradas implícitamente y métodos principales de instancia (Segunda vista previa) - JEP 463: Los estudiantes pueden escribir sus primeros programas en Java sin necesidad de comprender las características del lenguaje diseñadas para programas grandes. En lugar de usar un dialecto independiente del lenguaje, pueden escribir declaraciones simplificadas para programas de una sola clase y luego ampliarlos sin problemas para usar funciones más avanzadas a medida que mejoran sus habilidades.


2) Bibliotecas

  • API de Funciones Externas y Memoria - JEP 454: Permite que los programas Java interoperen con código y datos fuera del entorno de ejecución de Java. Al invocar eficientemente funciones externas (es decir, código fuera de la JVM) y acceder de forma segura a la memoria externa (es decir, memoria no gestionada por la JVM), la API permite que los programas Java invoquen bibliotecas nativas y procesen datos nativos sin la fragilidad ni los riesgos de JNI.


2.1) Vistas previas de bibliotecas e incubadora

  • API de archivo de clase (Vista previa) - JEP 457: Proporciona una API estándar para analizar, generar y transformar archivos de clase Java.
  • Recolectores de streams (Vista previa) - JEP 461: Mejora la API de streams para admitir operaciones intermedias personalizadas. Esto permitirá que las canalizaciones de flujos transformen datos de maneras que no son fáciles de lograr con las operaciones intermedias integradas existentes.
  • Concurrencia estructurada (2.ª vista previa) - JEP 462: Simplifica la programación concurrente. La concurrencia estructurada trata grupos de tareas relacionadas que se ejecutan en diferentes hilos como una sola unidad de trabajo, optimizando así la gestión y cancelación de errores, mejorando la fiabilidad y la observabilidad.
  • Valores con Alcance (2.ª Vista Previa) - JEP 464: Permite compartir eficientemente datos inmutables dentro y entre subprocesos.
  • API Vector (7.ª Incubadora) - JEP 460: Una API para expresar cálculos vectoriales que se compilan de forma fiable en tiempo de ejecución para obtener instrucciones vectoriales óptimas en arquitecturas de CPU compatibles, logrando así un rendimiento superior al de cálculos escalares equivalentes. Este JEP propone reincubar la API en JDK 22, con mejoras menores en la API con respecto a JDK 21. La implementación incluye correcciones de errores y mejoras de rendimiento. Incluimos los siguientes cambios notables: Compatibilidad con el acceso vectorial con segmentos de memoria del montón respaldados por un array de cualquier tipo de elemento primitivo. Anteriormente, el acceso estaba limitado a segmentos de memoria del montón respaldados por un array de bytes.


3) Rendimiento

  • Fijación regional para G1 - JEP 423: Reduce la latencia al implementar la fijación regional en G1, de modo que no es necesario deshabilitar la recolección de elementos no utilizados durante las regiones críticas de la Interfaz Nativa de Java (JNI).


4) Herramientas

Iniciar programas de código fuente con múltiples archivos - JEP 458: Permite a los usuarios ejecutar un programa proporcionado como múltiples archivos de código fuente Java sin tener que compilarlo primero.


Java 22 continúa la evolución del lenguaje, incorporando mejoras que aumentan su expresividad, rendimiento y seguridad. Estas actualizaciones refuerzan el compromiso de la plataforma con la innovación y la adaptabilidad a las necesidades actuales de desarrollo.

En proximos post vamos a detallar las mejoras del lenguaje. 

Dejo link: https://blogs.oracle.com/java/post/the-arrival-of-java-22

Importación de módulos y uso de paquetes en Python


Python es un lenguaje que permite organizar el código en módulos y paquetes, facilitando la reutilización y la modularidad. 

Un módulo en Python es simplemente un archivo `.py` que contiene funciones, clases y variables que pueden ser reutilizadas en otros archivos.


Ejemplo de módulo (`mi_modulo.py`):

Definimos una función en el módulo


def saludar(nombre):

    return f"Hola, {nombre}!"


Para usar un módulo en otro script, se puede importar con import:


import mi_modulo

print(mi_modulo.saludar("Emanuel"))  # Salida: Hola, Emanuel!


También se puede importar solo un elemento específico:


from mi_modulo import saludar

print(saludar("Mundo"))


Si se quiere importar todo el contenido de un módulo pero con un alias:


import mi_modulo as mod

print(mod.saludar("Python"))


Un paquete es una carpeta que contiene múltiples módulos y un archivo especial `__init__.py`, el cual indica que la carpeta debe tratarse como un paquete.


Estructura de un paquete:


mi_paquete/

│── __init__.py

│── modulo1.py

│── modulo2.py



Ejemplo de modulo1.py en mi_paquete:


def sumar(a, b):

    return a + b


Ejemplo de modulo2.py en mi_paquete:


def restar(a, b):

    return a - b


Para importar módulos desde un paquete:


from mi_paquete import modulo1, modulo2

print(modulo1.sumar(5, 3))  # Salida: 8

print(modulo2.restar(10, 4))  # Salida: 6


También se puede importar solo una función específica:


from mi_paquete.modulo1 import sumar

print(sumar(7, 2))


Importación relativa vs. absoluta


- Importación absoluta: Se usa la ruta completa desde el paquete raíz.

 

 from mi_paquete.modulo1 import sumar


- Importación relativa: Se usa `.` o `..` para referirse a módulos dentro del mismo paquete.

 

 from .modulo1 import sumar  # Desde el mismo paquete

 from ..otro_paquete.modulo3 import dividir  # Desde un paquete hermano


Python incluye una gran cantidad de módulos en su biblioteca estándar, que pueden ser importados directamente:


import math

print(math.sqrt(25))  # Salida: 5.0


Para instalar e importar paquetes de terceros (ejemplo: requests):


$ pip install requests



import requests

response = requests.get("https://api.github.com")

print(response.status_code)


El uso de módulos y paquetes en Python permite mantener el código organizado, reutilizable y fácil de mantener. Con las diferentes opciones de importación, puedes estructurar tus proyectos de manera eficiente.

martes, 25 de marzo de 2025

Sobrecarga de Métodos en Python


La sobrecarga de métodos (method overloading) ocurre cuando una clase define múltiples métodos con el mismo nombre pero diferentes parámetros. Sin embargo, Python no admite sobrecarga de métodos de la misma manera que lenguajes como Java o C++. En Python, si se define un método con el mismo nombre más de una vez, la última definición sobrescribe a las anteriores.

Ejemplo de Sobrecarga (Que No Funciona en Python)


class Calculadora:

    def sumar(self, a, b):

        return a + b


    def sumar(self, a, b, c):  # Sobrescribe el método anterior

        return a + b + c


calc = Calculadora()

print(calc.sumar(2, 3))  # Error: Argumentos incorrectos


El método sumar(a, b) se sobrescribe con sumar(a, b, c), por lo que llamar sumar(2, 3) genera un error.


Dado que Python no permite sobrecarga nativa, podemos lograr un comportamiento similar con valores por defecto, *args y @singledispatch.


Podemos definir parámetros opcionales para manejar diferentes cantidades de argumentos.


class Calculadora:

    def sumar(self, a, b, c=0):  # c es opcional

        return a + b + c


calc = Calculadora()

print(calc.sumar(2, 3))     # 5  (usa c=0 por defecto)

print(calc.sumar(2, 3, 4))  # 9  (usa c=4)


Esto imita la sobrecarga permitiendo diferentes cantidades de argumentos.

Si queremos permitir una cantidad dinámica de argumentos, podemos usar *args.


class Calculadora:

    def sumar(self, *args):

        return sum(args)  # Suma todos los argumentos recibidos


calc = Calculadora()

print(calc.sumar(2, 3))       # 5

print(calc.sumar(2, 3, 4, 5)) # 14


Aquí *args permite cualquier número de argumentos.

El decorador functools.singledispatch permite definir funciones con el mismo nombre pero con diferentes tipos de argumento.


from functools import singledispatch


@singledispatch

def procesar(valor):

    raise NotImplementedError("Tipo no soportado")


@procesar.register(int)

def _(valor):

    return f"Procesando número: {valor}"


@procesar.register(str)

def _(valor):

    return f"Procesando texto: {valor.upper()}"


print(procesar(10))     # Procesando número: 10

print(procesar("hola")) # Procesando texto: HOLA


Este enfoque permite sobrecargar la función dependiendo del tipo de dato del argumento.


lunes, 24 de marzo de 2025

Diferencias entre @Component, @Controller, @Service y @Repository en Spring Boot


Spring Boot proporciona varias anotaciones para marcar clases como beans dentro del contenedor de Spring. Entre ellas, @Component, @Controller, @Service y @Repository son las más utilizadas. Aunque todas registran un bean dentro del contexto de Spring, cada una tiene un propósito específico. A continuación, explicamos sus diferencias y usos recomendados.

@Component es la anotación más genérica y sirve para marcar cualquier clase como un bean de Spring. Cualquier clase anotada con @Component será detectada automáticamente por el escaneo de componentes de Spring y registrada en el contexto de la aplicación.


@Component

public class MiComponente {

    public void hacerAlgo() {

        System.out.println("Ejecutando lógica en MiComponente");

    }

}

Se recomienda usar @Component cuando la clase no encaje específicamente en @Controller, @Service o @Repository.


@Controller es una especialización de @Component que se utiliza en el contexto de Spring MVC para manejar solicitudes HTTP. Las clases anotadas con`@Controller se encargan de procesar peticiones web y devolver respuestas.


@Controller

public class MiControlador {

    @GetMapping("/saludo")

    public String saludo(Model model) {

        model.addAttribute("mensaje", "¡Hola desde el controlador!");

        return "saludo";

    }

}


Si el controlador necesita devolver solo datos en formato JSON o XML, se recomienda usar @RestController, que combina`@Controller y @ResponseBody.


@Service también es una especialización de @Component, pero se utiliza para indicar que una clase contiene lógica de negocio. Su uso es más semántico, ayudando a la organización y mantenimiento del código.


@Service

public class MiServicio {

    public String obtenerSaludo() {

        return "Hola desde el servicio";

    }

}


Se recomienda usar @Service para clases que encapsulen lógica de negocio y sean reutilizables en distintos lugares de la aplicación.


@Repository es otra especialización de @Component, pero está orientada a la capa de acceso a datos. Además de marcar la clase como un bean, @Repository proporciona integración con mecanismos de persistencia, como la gestión automática de excepciones relacionadas con bases de datos.


@Repository

public interface MiRepositorio extends JpaRepository<Entidad, Long> {

}

Spring usa @Repository para traducir excepciones de bases de datos en excepciones no verificadas de Spring (DataAccessException).


Cada anotación tiene su uso específico y contribuye a mantener una arquitectura limpia y organizada. Elegir la anotación adecuada ayuda a mejorar la mantenibilidad y escalabilidad de la aplicación.


domingo, 23 de marzo de 2025

Programación Orientada a Objetos en Python parte 5


Seguimos con poo y python.

La clase object en Python es la superclase base de todas las clases. Esto significa que cualquier clase que crees hereda, directa o indirectamente, de object. 

object es la raíz de la jerarquía de clases en Python. Si defines una clase sin especificar una superclase, automáticamente hereda de object.


class MiClase:  # Equivalente a class MiClase(object)

    pass


print(issubclass(MiClase, object))  # True


En versiones modernas de Python (desde Python 3), todas las clases heredan implícitamente de object.

Al heredar de object, las clases obtienen algunos métodos fundamentales:


__str__ y __repr__ : Estos métodos definen cómo se representa una instancia como cadena:


class Ejemplo:

    def __str__(self):

        return "Ejemplo como str"


    def __repr__(self):

        return "Ejemplo()"


obj = Ejemplo()

print(str(obj))   # "Ejemplo como str"

print(repr(obj))  # "Ejemplo()"


__eq__ y __hash__ : Permiten comparar objetos y usarlos en estructuras como set o dict.


class Persona:

    def __init__(self, nombre):

        self.nombre = nombre


    def __eq__(self, otro):

        return isinstance(otro, Persona) and self.nombre == otro.nombre


    def __hash__(self):

        return hash(self.nombre)


p1 = Persona("Alice")

p2 = Persona("Alice")

print(p1 == p2)  # True (porque definimos __eq__)

print(hash(p1) == hash(p2))  # True (porque __hash__ usa el nombre)


__class__ : Permite acceder a la clase de un objeto.


print(obj.__class__)  # <class '__main__.Ejemplo'>


Si bien no es necesario, se puede hacer explícita la herencia:


class MiClase(object):  # En Python 3 esto es redundante

    pass


Esto era más importante en Python 2, donde existían diferencias entre clases clásicas y clases de nuevo estilo.

La sobrescritura de métodos (method overriding) ocurre cuando una subclase redefine un método de su superclase con la misma firma (mismo nombre y parámetros). Esto permite modificar o extender el comportamiento heredado.

Cuando una subclase sobrescribe un método de su superclase, la nueva versión reemplaza a la anterior.


class Animal:

    def hacer_sonido(self):

        return "Sonido genérico"


class Perro(Animal):

    def hacer_sonido(self):  # Sobrescribe el método

        return "Guau Guau!"


animal = Animal()

perro = Perro()


print(animal.hacer_sonido())  # Sonido genérico

print(perro.hacer_sonido())   # Guau Guau!


Aquí, Perro redefine hacer_sonido(), cambiando el comportamiento original.

Para reutilizar la implementación de la superclase y agregar nueva funcionalidad, usamos super().


class Vehiculo:

    def describir(self):

        return "Soy un vehículo."


class Coche(Vehiculo):

    def describir(self):

        return super().describir() + " Soy un coche."


c = Coche()

print(c.describir())  # Soy un vehículo. Soy un coche.


super().describir() llama al método original en Vehiculo, evitando reescribir código innecesario.

Podemos sobrescribir el constructor (__init__) de la superclase, llamando a super() para inicializar atributos.


class Persona:

    def __init__(self, nombre):

        self.nombre = nombre


    def presentarse(self):

        return f"Hola, soy {self.nombre}."


class Estudiante(Persona):

    def __init__(self, nombre, universidad):

        super().__init__(nombre)  # Llamamos al __init__ de Persona

        self.universidad = universidad


    def presentarse(self):

        return super().presentarse() + f" Estudio en {self.universidad}."


e = Estudiante("Carlos", "MIT")

print(e.presentarse())  # Hola, soy Carlos. Estudio en MIT.


Aquí, Estudiante extiende __init__ y presentarse(), reutilizando la lógica de Persona.


Podemos redefinir métodos especiales para personalizar el comportamiento de nuestras clases.


class Libro:

    def __init__(self, titulo, autor):

        self.titulo = titulo

        self.autor = autor


    def __str__(self):  # Representación amigable para humanos

        return f"Libro: {self.titulo} de {self.autor}"


    def __repr__(self):  # Representación para depuración

        return f"Libro({repr(self.titulo)}, {repr(self.autor)})"


libro = Libro("1984", "George Orwell")

print(str(libro))   # Libro: 1984 de George Orwell

print(repr(libro))  # Libro('1984', 'George Orwell')


En lenguajes como Java o C++, upcasting y downcasting son técnicas que permiten tratar objetos de una subclase como si fueran de su superclase (upcasting) o convertir un objeto de la superclase en su subclase específica (downcasting).

Python NO tiene upcasting ni downcasting en el sentido estricto porque su sistema de tipos es dinámico y no requiere conversiones explícitas. Sin embargo, se pueden imitar estos conceptos utilizando la herencia y el manejo de instancias.

En upcasting, un objeto de una subclase se trata como si fuera de su superclase. Esto es natural en Python, ya que una subclase hereda automáticamente los métodos y atributos de su superclase.


class Animal:

    def hacer_sonido(self):

        return "Sonido genérico"


class Perro(Animal):

    def hacer_sonido(self):

        return "Guau Guau!"


# Upcasting: Tratamos un Perro como un Animal

animal: Animal = Perro()  # No es necesario hacer conversión

print(animal.hacer_sonido())  # Guau Guau!


En Python, no es necesario realizar una conversión explícita para tratar un objeto de una subclase como su superclase. Simplemente se puede asignar una instancia de la subclase a una variable del tipo de la superclase.

El downcasting ocurre cuando convertimos un objeto de una superclase a su subclase específica. En Python, esto no es seguro y debe hacerse con validaciones, ya que un objeto de la superclase puede no tener los métodos de la subclase.


class Animal:

    def hacer_sonido(self):

        return "Sonido genérico"


class Perro(Animal):

    def hacer_sonido(self):

        return "Guau Guau!"


    def correr(self):

        return "El perro está corriendo."


# Upcasting: Perro como Animal

animal: Animal = Perro()


# Downcasting: Verificamos si el objeto es realmente un Perro

if isinstance(animal, Perro):

    perro: Perro = animal  # No hay conversión explícita, solo reasignación

    print(perro.correr())  # El perro está corriendo.

else:

    print("No se puede hacer downcasting.")


Python no impide el downcasting, pero no lo recomienda porque se basa en la verificación de tipo en tiempo de ejecución (isinstance()). Si intentamos acceder a un método de Perro en un objeto que realmente es Animal, fallará.

En Python, las clases abstractas y los métodos abstractos permiten definir estructuras base que las subclases deben implementar. Se logran con el módulo abc (Abstract Base Class).

Una clase abstracta es aquella que no puede instanciarse directamente y sirve como plantilla para otras clases.

  • Se define usando ABC del módulo abc.  
  • Si una clase tiene al menos un método abstracto, debe ser abstracta.  

Ejemplo:


from abc import ABC, abstractmethod


class Animal(ABC):  # Clase abstracta

    @abstractmethod

    def hacer_sonido(self):

        pass  # No tiene implementación


# Error si intentamos instanciar

# a = Animal()  # TypeError: Can't instantiate abstract class Animal


Animal no se puede instanciar porque tiene métodos abstractos.

Un método abstracto es aquel que debe ser implementado en las subclases.


Ejemplo:


class Perro(Animal):  # Subclase concreta

    def hacer_sonido(self):

        return "Guau Guau!"  # Implementación obligatoria


p = Perro()

print(p.hacer_sonido())  # Guau Guau!


Perro hereda de Animal y debe implementar hacer_sonido(), o Python lanzará un error.

En Python, un método abstracto puede tener una implementación base:


class Ave(ABC):

    @abstractmethod

    def volar(self):

        print("Algunas aves pueden volar")  # Implementación opcional


class Aguila(Ave):

    def volar(self):

        super().volar()

        print("El águila vuela alto")


a = Aguila()

a.volar()


Las subclases pueden llamar super().volar() para reutilizar la implementación base.

También podemos definir propiedades abstractas con @property:


class Figura(ABC):

    @property

    @abstractmethod

    def area(self):

        pass  # Obliga a definir `area` en las subclases


class Cuadrado(Figura):

    def __init__(self, lado):

        self._lado = lado


    @property

    def area(self):

        return self._lado ** 2


c = Cuadrado(4)

print(c.area)  # 16


La subclase debe implementar la propiedad area, o Python lanzará un error.

El polimorfismo es un concepto clave en la Programación Orientada a Objetos (POO) que permite que diferentes clases tengan métodos con el mismo nombre, pero con comportamientos distintos. En Python, el polimorfismo se aplica de manera dinámica y es muy flexible.

Podemos definir métodos con el mismo nombre en diferentes clases y usarlos sin importar el tipo de objeto.


class Perro:

    def hacer_sonido(self):

        return "Guau Guau!"


class Gato:

    def hacer_sonido(self):

        return "Miau Miau!"


# Función polimórfica

def sonido_animal(animal):

    print(animal.hacer_sonido())


# Ambas clases tienen el método `hacer_sonido`, pero con comportamientos diferentes

perro = Perro()

gato = Gato()


sonido_animal(perro)  # Guau Guau!

sonido_animal(gato)   # Miau Miau!


Python permite usar hacer_sonido() sin importar el tipo de objeto porque ambos lo implementan.

Si una clase base define un método, las subclases pueden sobrescribirlo con su propia implementación.


class Ave:

    def volar(self):

        return "Algunas aves pueden volar"


class Aguila(Ave):

    def volar(self):

        return "El águila vuela alto y rápido"


class Pinguino(Ave):

    def volar(self):

        return "El pingüino no puede volar"


# Lista de objetos polimórficos

aves = [Aguila(), Pinguino()]


for ave in aves:

    print(ave.volar())


# Salida:

# El águila vuela alto y rápido

# El pingüino no puede volar


volar() tiene diferentes comportamientos según la subclase.

Podemos forzar a las subclases a implementar métodos específicos usando clases abstractas (ABC).


from abc import ABC, abstractmethod


class Figura(ABC):

    @abstractmethod

    def area(self):

        pass


class Cuadrado(Figura):

    def __init__(self, lado):

        self.lado = lado


    def area(self):

        return self.lado ** 2


class Circulo(Figura):

    def __init__(self, radio):

        self.radio = radio


    def area(self):

        return 3.14 * self.radio ** 2


# Lista polimórfica

figuras = [Cuadrado(4), Circulo(3)]


for figura in figuras:

    print(figura.area())  # Python llama al método `area()` correspondiente


Gracias al polimorfismo, podemos iterar sobre figuras sin preocuparnos del tipo.

Python tambien permite redefinir operadores como +,*,== mediante métodos especiales (dunder methods).


class Punto:

    def __init__(self, x, y):

        self.x = x

        self.y = y


    def __add__(self, otro):

        return Punto(self.x + otro.x, self.y + otro.y)


    def __str__(self):

        return f"({self.x}, {self.y})"


p1 = Punto(2, 3)

p2 = Punto(5, 7)


print(p1 + p2)  # (7, 10)

+ se comporta diferente gracias a la sobrecarga de operadores.


viernes, 21 de marzo de 2025

Número variable de argumentos en C#


En C#, la palabra clave params se usa para permitir que un método acepte un número variable de argumentos del mismo tipo sin necesidad de definir múltiples sobrecargas.  

¿Cómo funciona?  

  • Solo puede haber un parámetro params por método, y debe ser el último parámetro en la lista.  
  • El argumento pasado puede ser una lista de valores separados por comas o un array del tipo especificado.  

Veamos un ejemplo: 


using System;


class Program

{

    static void Main()

    {

        ImprimirNumeros(1, 2, 3, 4, 5);

        ImprimirNumeros(10, 20);

        ImprimirNumeros(); // No pasa nada, la lista puede estar vacía

    }


    static void ImprimirNumeros(params int[] numeros)

    {

        foreach (var num in numeros)

        {

            Console.Write(num + " ");

        }

        Console.WriteLine();

    }

}

La salida va a ser:

1 2 3 4 5  

10 20  


También puedes pasar un array en lugar de valores separados por comas:  


int[] valores = { 100, 200, 300 };

ImprimirNumeros(valores);



Puedes mezclar params con otros parámetros, pero debe estar al final:  


static void Saludar(string mensaje, params string[] nombres)

{

    foreach (var nombre in nombres)

    {

        Console.WriteLine($"{mensaje}, {nombre}!");

    }

}


// Llamadas válidas:

Saludar("Hola", "Juan", "Ana", "Luis");  

Saludar("Bienvenido", "Carlos");  


Y la salida va a ser: 


Hola, Juan!  

Hola, Ana!  

Hola, Luis!  

Bienvenido, Carlos!  


¿Cuándo usar params?  

  • Cuando quieres un método flexible sin definir múltiples sobrecargas.  
  • Para métodos como Console.WriteLine(), que pueden recibir cualquier cantidad de argumentos.  
  • Para situaciones en las que puede haber 0 o más parámetros opcionales sin usar List<T> o IEnumerable<T>.  


Alquimista de código


Les quiero recomendar el blog alquimista de código; es un blog de muy buena calidad y con mucha información y opiniones. Esta bueno! 

También cuenta con mucha información de diferentes lenguajes de programación no solo los más conocidos sino tambien nuevos y no tan nuevos. Si les gusta mi blog, seguro este les va a gustar. 

Dejo link: 

https://alquimistadecodigo.blogspot.com/

jueves, 20 de marzo de 2025

Programación Orientada a Objetos en Python parte 4


Seguimos con poo y python.

Una vez que nos familiarizamos con los conceptos básicos de POO podemos ahora enfocarnos en herramientas que nos permiten aprovechar mejor sus beneficios, veremos a continuación los pilares fundamentales sobre los que se basa la Programación Orientada a Objetos.

La herencia es una herramienta que fomenta la reusabilidad del código. Permite a una clase poder reutilizar (o heredar) compartamientos y/o atributos de otra clase, denominada superclase o clase padre. Muchas veces nos encontramos con escenarios en donde tenemos implementada ya en cierta clase, atributos o métodos que necesitamos luego utilizar en otra clase. Por ejemplo supongamos que contamos con un método registrar en la clase Venta y ahora nos encontramos desarrollando una clase VentaEnLinea, la cual tiene comportamientos y atributos similares a la anterior, pero se diferencia en algunos atributos y comportamientos. Aplicando herencia podremos reutilizar lo definido en la clase anterior e inclusive modificar comportamientos definidos para solucionar nuestro problema. 

La herencia es un proceso mediante el cual se puede crear una clase hija que hereda de una clase padre, compartiendo sus métodos y atributos. Además de ello, una clase hija puede sobreescribir los métodos o atributos, o incluso definir unos nuevos.

Se puede crear una clase hija con tan solo pasar como parámetro la clase de la que queremos heredar. En el siguiente ejemplo vemos como se puede usar la herencia en Python, con la clase Perro que hereda de Animal. Así de fácil.


# Definimos una clase padre

class Animal:

    pass


# Creamos una clase hija que hereda de la padre

class Perro(Animal):

    pass


De hecho podemos ver como efectivamente la clase Perro es la hija de Animal usando __bases__


print(Perro.__bases__)

# (<class '__main__.Animal'>,)


De manera similar podemos ver que clases descienden de una en concreto con __subclasses__.


print(Animal.__subclasses__())

# [<class '__main__.Perro'>]


¿Y para que queremos la herencia? Dado que una clase hija hereda los atributos y métodos de la padre, nos puede ser muy útil cuando tengamos clases que se parecen entre sí pero tienen ciertas particularidades. En este caso en vez de definir un montón de clases para cada animal, podemos tomar los elementos comunes y crear una clase Animal de la que hereden el resto, respetando por tanto la filosofía DRY. Realizar estas abstracciones y buscar el denominador común para definir una clase de la que hereden las demás, es una tarea de lo más compleja en el mundo de la programación.

Para saber más: El principio DRY (Don't Repeat Yourself) es muy aplicado en el mundo de la programación y consiste en no repetir código de manera innecesaria. Cuanto más código duplicado exista, más difícil será de modificar y más fácil será crear inconsistencias. Las clases y la herencia a no repetir código.

Continuemos con nuestro ejemplo de perros y animales. Vamos a definir una clase padre Animal que tendrá todos los atributos y métodos genéricos que los animales pueden tener. Esta tarea de buscar el denominador común es muy importante en programación. Veamos los atributos:

Tenemos la especie ya que todos los animales pertenecen a una.

Y la edad, ya que todo ser vivo nace, crece, se reproduce y muere.

Y los métodos o funcionalidades:

Tendremos el método hablar, que cada animal implementará de una forma. Los perros ladran, las abejas zumban y los caballos relinchan.

Un método moverse. Unos animales lo harán caminando, otros volando.

Y por último un método descríbeme que será común.

Definimos la clase padre, con una serie de atributos comunes para todos los animales como hemos indicado.


class Animal:

    def __init__(self, especie, edad):

        self.especie = especie

        self.edad = edad


    # Método genérico pero con implementación particular

    def hablar(self):

        # Método vacío

        pass


    # Método genérico pero con implementación particular

    def moverse(self):

        # Método vacío

        pass


    # Método genérico con la misma implementación

    def describeme(self):

        print("Soy un Animal del tipo", type(self).__name__)


Tenemos ya por lo tanto una clase genérica Animal, que generaliza las características y funcionalidades que todo animal puede tener. Ahora creamos una clase Perro que hereda del Animal. Como primer ejemplo vamos a crear una clase vacía, para ver como los métodos y atributos son heredados por defecto.


# Perro hereda de Animal

class Perro(Animal):

    pass


mi_perro = Perro('mamífero', 10)

mi_perro.describeme()

# Soy un Animal del tipo Perro


Con tan solo un par de líneas de código, hemos creado una clase nueva que tiene todo el contenido que la clase padre tiene, pero aquí viene lo que es de verdad interesante. Vamos a crear varios animales concretos y sobreescrbir algunos de los métodos que habían sido definidos en la clase Animal, como el hablar o el moverse, ya que cada animal se comporta de una manera distinta.

Podemos incluso crear nuevos métodos que se añadirán a los ya heredados, como en el caso de la Abeja con picar().


class Perro(Animal):

    def hablar(self):

        print("Guau!")

    def moverse(self):

        print("Caminando con 4 patas")


class Vaca(Animal):

    def hablar(self):

        print("Muuu!")

    def moverse(self):

        print("Caminando con 4 patas")


class Abeja(Animal):

    def hablar(self):

        print("Bzzzz!")

    def moverse(self):

        print("Volando")


    # Nuevo método

    def picar(self):

        print("Picar!")


Por lo tanto ya podemos crear nuestros objetos de esos animales y hacer uso de sus métodos que podrían clasificarse en tres:

  • Heredados directamente de la clase padre: describeme()
  • Heredados de la clase padre pero modificados: hablar() y moverse()
  • Creados en la clase hija por lo tanto no existentes en la clase padre: picar()

      

mi_perro = Perro('mamífero', 10)

mi_vaca = Vaca('mamífero', 23)

mi_abeja = Abeja('insecto', 1)


mi_perro.hablar()

mi_vaca.hablar()

# Guau!

# Muuu!


mi_vaca.describeme()

mi_abeja.describeme()

# Soy un Animal del tipo Vaca

# Soy un Animal del tipo Abeja


mi_abeja.picar()

# Picar!


La función super() nos permite acceder a los métodos de la clase padre desde una de sus hijas. Volvamos al ejemplo de Animal y Perro.


class Animal:

    def __init__(self, especie, edad):

        self.especie = especie

        self.edad = edad        


    def hablar(self):

        pass


    def moverse(self):

        pass


    def describeme(self):

        print("Soy un Animal del tipo", type(self).__name__)


Tal vez queramos que nuestro Perro tenga un parámetro extra en el constructor, como podría ser el dueño. Para realizar esto tenemos dos alternativas:

  • Podemos crear un nuevo __init__ y guardar todas las variables una a una.
  • O podemos usar super() para llamar al __init__ de la clase padre que ya aceptaba la especie y edad, y sólo asignar la variable nueva manualmente.


class Perro(Animal):

    def __init__(self, especie, edad, dueño):

        # Alternativa 1

        # self.especie = especie

        # self.edad = edad

        # self.dueño = dueño


        # Alternativa 2

        super().__init__(especie, edad)

        self.dueño = dueño


mi_perro = Perro('mamífero', 7, 'Luis')

mi_perro.especie

mi_perro.edad

mi_perro.dueño


En Python es posible realizar herencia múltiple. En otros posts hemos visto como se podía crear una clase padre que heredaba de una clase hija, pudiendo hacer uso de sus métodos y atributos. La herencia múltiple es similar, pero una clase hereda de varias clases padre en vez de una sola.

Veamos un ejemplo. Por un lado tenemos dos clases Clase1 y Clase2, y por otro tenemos la Clase3 que hereda de las dos anteriores. Por lo tanto, heredará todos los métodos y atributos de ambas.


class Clase1:

    pass

class Clase2:

    pass

class Clase3(Clase1, Clase2):

    pass


Es posible también que una clase herede de otra clase y a su vez otra clase herede de la anterior.


class Clase1:

    pass

class Clase2(Clase1):

    pass

class Clase3(Clase2):

    pass


Llegados a este punto nos podemos plantear lo siguiente. Si llamo a un método que todas las clases tienen en común ¿a cuál se llama?. Pues bien, existe una forma de saberlo.

La forma de saber a que método se llama es consultar el MRO o Method Order Resolution. Esta función nos devuelve una tupla con el orden de búsqueda de los métodos. Como era de esperar se empieza en la propia clase y se va subiendo hasta la clase padre, de izquierda a derecha.


class Clase1:

    pass

class Clase2:

    pass

class Clase3(Clase1, Clase2):

    pass


print(Clase3.__mro__)

# (<class '__main__.Clase3'>, <class '__main__.Clase1'>, <class '__main__.Clase2'>, <class 'object'>)


Una curiosidad es que al final del todo vemos la clase object. Aunque pueda parecer raro, es correcto ya que en realidad todas las clases en Python heredan de una clase genérica object, aunque no lo especifiquemos explícitamente.

Y como último ejemplo,…el cielo es el límite. Podemos tener una clase heredando de otras tres. Fíjate en que el MRO depende del orden en el que las clases son pasadas: 1, 3, 2.


class Clase1:

    pass

class Clase2:

    pass

class Clase3:

    pass

class Clase4(Clase1, Clase3, Clase2):

    pass

print(Clase4.__mro__)

# (<class '__main__.Clase4'>, <class '__main__.Clase1'>, <class '__main__.Clase3'>, <class '__main__.Clase2'>, <class 'object'>)


miércoles, 19 de marzo de 2025

Funciones Lambda en C++


Las funciones lambda en C++ permiten definir funciones anónimas de manera concisa y flexible. Introducidas en C++11 y mejoradas en versiones posteriores, las lambdas son muy útiles para expresiones cortas y para capturar variables del contexto circundante.

La sintaxis general de una lambda es:


[captura](parámetros) -> tipo_de_retorno { cuerpo };


Veamos un ejemplo:


#include <iostream>


int main() {

    auto suma = [](int a, int b) -> int {

        return a + b;

    };

    

    std::cout << "La suma es: " << suma(3, 4) << std::endl;

    return 0;

}


Las lambdas pueden capturar variables del contexto donde se definen. Su puede captura por Valor


#include <iostream>


int main() {

    int x = 10;

    auto lambda = [x]() { std::cout << "x = " << x << std::endl; };

    lambda();

    return 0;

}


Como x se captura por valor, cualquier cambio dentro de la lambda no afectará la variable original.

Y captura por Referencia:


#include <iostream>


int main() {

    int x = 10;

    auto lambda = [&x]() { x += 5; };

    lambda();

    std::cout << "x después de la lambda: " << x << std::endl;

    return 0;

}


Dado que x se captura por referencia, su modificación dentro de la lambda afecta la variable original.

Se puede capturar todo el entorno automáticamente:


- [=] captura todas las variables por valor.

- [&] captura todas las variables por referencia.


#include <iostream>


int main() {

    int a = 5, b = 10;

    auto lambda = [=]() { std::cout << "a + b = " << (a + b) << std::endl; };

    lambda();

    return 0;

}


Las lambdas pueden almacenarse en std::function para mayor flexibilidad:


#include <iostream>

#include <functional>


int main() {

    std::function<int(int, int)> multiplicar = [](int x, int y) { return x * y; };

    std::cout << "Multiplicación: " << multiplicar(3, 4) << std::endl;

    return 0;

}


Las lambdas son útiles para definir comparaciones personalizadas:


Por ejemplo: 


#include <iostream>

#include <vector>

#include <algorithm>


int main() {

    std::vector<int> nums = {3, 1, 4, 1, 5, 9};

    

    std::sort(nums.begin(), nums.end(), [](int a, int b) {

        return a > b;

    });

    

    for (int n : nums) std::cout << n << " ";

    return 0;

}


Las funciones lambda en C++ permiten definir funciones anónimas de manera flexible y eficiente. Son especialmente útiles en programación funcional, expresiones cortas y manejo de algoritmos en la STL. 

sábado, 15 de marzo de 2025

Clases Selladas y coincidencia de patrones en Java


Con la evolución de Java, dos características han tomado mayor protagonismo: las clases selladas (sealed classes) y el coincidencia de patrones (pattern matching). La combinación de ambas permite escribir código más seguro, expresivo y mantenible.

Las clases selladas permiten restringir qué clases pueden extender o implementar una clase o interfaz. Se declaran con la palabra clave sealed y deben especificar sus subclases con permits.


Por ejemplo:


public sealed class Figura permits Circulo, Poligono {}

public final class Circulo extends Figura {}

public sealed class Poligono extends Figura permits Triangulo {}

public non-sealed class Triangulo extends Poligono {}


Aquí, Figura solo puede ser extendida por Circulo y Poligono, mientras que Triangulo puede ser extendido libremente.

El pattern matching simplifica la lógica condicional al combinar la comprobación de tipos y la conversión en una sola operación.

Por ejemplo:


Object obj = "Hola, mundo!";

if (obj instanceof String s) {

    System.out.println(s.toUpperCase());

}


Si obj es un String, se asigna automáticamente a s, evitando el casting manual.


Tambien lo podemos usar con switch: 


static void procesar(Object obj) {

    switch (obj) {

        case String s -> System.out.println("Es una cadena: " + s.toUpperCase());

        case Integer i -> System.out.println("Es un número: " + (i * 2));

        default -> System.out.println("Tipo no soportado");

    }

}


El switch maneja diferentes tipos de objetos de manera más clara y concisa.

La combinación de ambas características permite que el compilador conozca todas las posibles subclases en tiempo de compilación, facilitando el uso de switch y garantizando que se manejen todos los casos.

Por ejemplo: 


public sealed interface Operacion permits Suma, Resta, Multiplicacion {}


public record Suma(int a, int b) implements Operacion {}

public record Resta(int a, int b) implements Operacion {}

public record Multiplicacion(int a, int b) implements Operacion {}


public int evaluar(Operacion op) {

    return switch (op) {

        case Suma s -> s.a() + s.b();

        case Resta r -> r.a() - r.b();

        case Multiplicacion m -> m.a() * m.b();

    };

}


Aquí, el switch cubre todas las implementaciones posibles de Operacion, asegurando que no haya casos no manejados.

Las clases selladas y el pattern matching en Java permiten escribir código más seguro y expresivo. Al proporcionar control sobre la herencia y simplificar la lógica condicional, facilitan la creación de software más mantenible y alineado con las tendencias modernas del desarrollo en Java.


viernes, 14 de marzo de 2025

Vine: Un Lenguaje Minimalista y Funcional


Vine es un nuevo lenguaje de programación experimental basado en redes de interacción.

Vine es un lenguaje multiparadigma que ofrece una interoperabilidad fluida entre patrones funcionales e imperativos.

Vamos por parte ¿Qué son las redes de interacción? : Las redes de interacción son una representación gráfica de los cálculos en un programa, lo que permite una evaluación más eficiente y altamente paralela. En lugar de ejecutarse como instrucciones secuenciales, las operaciones en Vine pueden reorganizarse dinámicamente para optimizar la ejecución.  

Vine está diseñado para facilitar la programación funcional y la concurrencia. Esto significa que los desarrolladores pueden escribir código más limpio, modular y escalable, sin preocuparse tanto por problemas típicos de la concurrencia, como las condiciones de carrera.  


Los beneficios clave de Vine son:   

  • Ejecución optimizada: Aprovecha mejor los recursos del hardware al paralelizar automáticamente el procesamiento.  
  • Código más claro: Al estar basado en principios funcionales, evita efectos secundarios y hace que el código sea más fácil de entender y mantener.  
  • Escalabilidad: Diseñado para manejar aplicaciones concurrentes sin los bloqueos y problemas de sincronización típicos en otros lenguajes.  


El lenguaje todavía está en fase temprana de desarrollo, pero sus creadores creen que podría ser una alternativa prometedora para la programación funcional y concurrente en el futuro.  

Veamos un ejemplo: 


pub fn main(&io: &IO) {

  while io.prompt("> ") is Some(line) {

    let num = N32::parse(line);

    io.println(match num {

      Some(num) { fib(num).to_string() }

      None { "invalid number" }

    });

  }

}


pub fn fib(n: N32) -> N32 {

  let a = 0;

  let b = 1;

  while n != 0 {

    n -= 1;

    (a, b) = (b, a + b);

  }

  a

}


Muchas de las características de Vine están influenciadas por Rust, y tiene una sintaxis orientada a expresiones, un sistema de tipos y un sistema de módulos similares.

Vine promete ser una herramienta valiosa para aquellos interesados en paradigmas funcionales y en la optimización de aplicaciones concurrentes.


Dejo links: 

https://www.infoq.com/news/2025/03/new-programming-language-vine/

https://vine.dev/

miércoles, 12 de marzo de 2025

Programación Orientada a Objetos en Python parte 3



Seguimos con poo y python. 

En Python, el parámetro `self` se usa en los métodos de una clase para hacer referencia a la instancia actual del objeto. 

Cada vez que se llama a un método de una instancia de una clase, Python pasa automáticamente la instancia como primer argumento. El nombre convencional para este argumento es self, aunque puedes llamarlo de otra manera (aunque no se recomienda porque es una convención establecida).

Veamos un ejemplo:


class Persona:

    def __init__(self, nombre, edad):

        self.nombre = nombre  # `self.nombre` es un atributo de la instancia

        self.edad = edad      # `self.edad` también es un atributo de la instancia


    def saludar(self):

        print(f"Hola, mi nombre es {self.nombre} y tengo {self.edad} años.")


# Crear una instancia de la clase

p1 = Persona("Carlos", 30)


# Llamar al método saludar

p1.saludar()


  • self.nombre = nombre: Guarda el valor del parámetro nombre en el atributo nombre de la instancia.
  • def saludar(self): El método recibe self, lo que le permite acceder a los atributos nombre y edad de la instancia p1.
  • Cuando se llama a p1.saludar(), Python en realidad ejecuta Persona.saludar(p1), pasando la instancia p1 como primer argumento.

self es obligatorio en métodos de instancia porque permite acceder a los atributos y otros métodos de la instancia. Si se omite, se generará un error:


class Prueba:

    def mensaje():

        print("Este método no tiene self.")


p = Prueba()

p.mensaje()  # TypeError: mensaje() takes 0 positional arguments but 1 was given


Aquí, Python intenta pasar la instancia p automáticamente, pero como el método mensaje() no tiene self, se genera un error.


El parámetro self también se usa en métodos especiales como __str__, __repr__, __eq__, etc.:


class Auto:

    def __init__(self, marca):

        self.marca = marca


    def __str__(self):

        return f"Auto de marca {self.marca}"


a = Auto("Toyota")

print(a)  # Auto de marca Toyota


En Python, un constructor es un método especial que se ejecuta automáticamente cuando se crea una nueva instancia de una clase. Se define con el método __init__.

__init__ es el constructor de una clase en Python. Se usa para inicializar los atributos del objeto cuando se crea una instancia.


class Persona:

    def __init__(self, nombre, edad):

        self.nombre = nombre  # Atributo de la instancia

        self.edad = edad      # Atributo de la instancia


    def presentarse(self):

        print(f"Hola, soy {self.nombre} y tengo {self.edad} años.")


# Crear una instancia

p1 = Persona("Carlos", 30)


# Llamar a un método

p1.presentarse()


  • __init__ recibe self, que es la instancia del objeto.
  • self.nombre = nombre almacena el valor en el objeto.
  • Cuando se crea una instancia con Persona("Carlos", 30), Python ejecuta automáticamente __init__, asignando "Carlos" a self.nombre y 30 a self.edad.


Se pueden definir valores predeterminados en los parámetros del constructor:


class Coche:

    def __init__(self, marca="Toyota", modelo="Corolla"):

        self.marca = marca

        self.modelo = modelo


c1 = Coche()  # Usa los valores por defecto

c2 = Coche("Ford", "Focus")  # Pasa valores específicos


print(c1.marca, c1.modelo)  # Toyota Corolla

print(c2.marca, c2.modelo)  # Ford Focus


También es posible definir un constructor sin argumentos (además de `self`):


class Ejemplo:

    def __init__(self):

        print("Se ha creado una nueva instancia.")


obj = Ejemplo()  # Se imprimirá el mensaje


El constructor puede contener lógica para validar datos o modificar valores antes de asignarlos:



class Usuario:

    def __init__(self, nombre, edad):

        self.nombre = nombre

        self.edad = max(0, edad)  # Evita edades negativas


u1 = Usuario("Ana", -5)

print(u1.edad)  # 0


Para poder utilizar objetos debemos definir una variable de referencia y luego instanciar dicho objeto, como vimos anteriormente:

c1 = Coche()  # Usa los valores por defecto

c2 = Coche("Ford", "Focus")  # Pasa valores específicos


Una vez definido un objeto podemos interactuar con él invocando sus atributos (lo cual no es recomendado porque rompemos el principio del encapsulamiento) públicos e interactuando con sus métodos:

c1.marca = "Ford"

c1.modelo = ”Fiesta”