Clases en Python — Parte I 🏕️

Fundamentos: clases, constructores y atributos

🌱 Bienvenida al Reino de los Objetos

En Python, todo es un objeto. En esta primera parte revisamos cómo definir clases, crear instancias, usar el constructor __init__ y distinguir atributos de clase y de instancia. La meta es comprender la semántica básica y las diferencias respecto a Java (menos ceremonia, más expresividad).

1) ¿Qué es una clase?

Una clase define un tipo (molde) a partir del cual se crean instancias (objetos).
En Python, la palabra clave es class y no se declaran tipos de campo explícitos.

2) Crear clases (definición mínima)

class Punto:
    pass

p = Punto()       # instancia
print(type(p))    # <class '__main__.Punto'>

Nota: pass indica un bloque vacío. Podemos añadir atributos dinámicamente en tiempo de ejecución (no recomendado sin control).

3) Constructores con __init__

class Punto:
    def __init__(self, x: float, y: float):
        self.x = x    # atributo de instancia
        self.y = y

p = Punto(3.0, 4.0)
print(p.x, p.y)

Idea: self es la referencia a la instancia (explícita en Python). Equivale al this de Java, pero se escribe como primer parámetro.

4) Atributos de clase vs. de instancia

class Circulo:
    pi = 3.14159            # atributo de CLASE (compartido)

    def __init__(self, radio: float):
        self.radio = radio  # atributo de INSTANCIA

c1 = Circulo(1.0)
c2 = Circulo(2.0)
print(Circulo.pi, c1.radio, c2.radio)

Regla:
- Clase: espacio de nombres común (constantes, configuración).
- Instancia: datos propios de cada objeto.

5) Métodos de instancia y de clase

class Circulo:
    pi = 3.14159

    def __init__(self, radio):
        self.radio = radio

    def area(self):                 # método de instancia
        return Circulo.pi * self.radio**2

    @classmethod
    def unidad(cls):                # método de clase (fábrica)
        return cls(1.0)

c = Circulo.unidad()
print(round(c.area(), 3))

Distinción:
- Instancia: usa self (estado del objeto).
- Clase: usa cls (opera a nivel de tipo; factorías, configuraciones).

6) Métodos mágicos básicos: __str__ y __repr__

class Punto:
    def __init__(self, x, y):
        self.x = x; self.y = y

    def __repr__(self):
        return f"Punto(x={self.x}, y={self.y})"

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

p = Punto(1, 2)
print(repr(p))  # representación para desarrolladores
print(p)        # amigable para usuarios

Convención: __repr__ debe ser no ambigua; __str__ es legible.

💭 Checkpoint (reflexión)

  1. ¿Qué diferencia hay entre un atributo de clase y uno de instancia?
  2. ¿Para qué sirve @classmethod? ¿En qué se distingue de un método de instancia?
  3. ¿Por qué tener __repr__ y __str__ distintos puede ayudar en depuración y presentación?