Hola! Este capítulo es corto a propósito
Te lo digo de una: para analizar datos necesitas entender objetos, no diseñarlos 🌸
He visto cursos que meten tres semanas de herencia y polimorfismo antes de tocar un CSV, y la gente sale creyendo que Python es dificilísimo. No lo es.
Lo que sí necesitas es entender por qué escribes df.head() con
punto y con paréntesis, y por qué df.shape lleva punto pero no
paréntesis. Vamos a eso.
Todo lo que has usado ya era un objeto
texto = 'Minimarket El Sol'
print(type(texto))
print(texto.upper())
print(texto.count('l'))
<class 'str'> MINIMARKET EL SOL 2
Ese texto no es solo una cadena de letras: es un objeto de tipo
str que viene con funciones pegadas. Esas funciones pegadas se
llaman métodos, y se llaman con el punto.
La lista también:
ventas = [480.37, 154.83] ventas.append(2480.84) print(ventas) print(ventas.count(154.83))
[480.37, 154.83, 2480.84] 1
Atributo o método, que es la duda de todos
Esta es la pregunta que quería resolverte con este capítulo:
- 📊 Atributo: un dato que el objeto guarda. Se lee sin paréntesis.
- ⚙️ Método: una acción que el objeto sabe hacer. Se llama con paréntesis.
from datetime import date hoy = date(2026, 8, 15) print(hoy.year) # atributo: un dato guardado print(hoy.month) print(hoy.weekday()) # metodo: calcula algo y lo devuelve print(hoy.isoformat())
2026 8 5 2026-08-15
Y esto es lo que pasa si te equivocas, que te va a pasar:
print(hoy.year())
TypeError: 'int' object is not callable
Léelo: hoy.year es un int, o sea el número 2026, y
le pusiste paréntesis como si fuera una función. "No es llamable" significa
justo eso 🙃
Al revés también avisa:
print(hoy.weekday)
<built-in method weekday of datetime.date object at ...
No revienta, pero mira lo que imprimió: te describió el método en vez de ejecutarlo. Si tu salida dice "method" en alguna parte, te faltaron los paréntesis. Guárdate ese truco, que en pandas lo vas a usar 💜
Cuando dudes, dir() te lista todo lo que tiene un objeto:
print([n for n in dir(hoy) if not n.startswith('_')])
['ctime', 'day', 'fromisocalendar', 'fromisoformat', 'fromordinal', 'fromtimestamp', 'isocalendar', 'isoformat', 'isoweekday', 'max', 'min', 'month', 'replace', 'resolution', 'strftime', 'timetuple', 'today', 'toordinal', 'weekday', 'year']
Ese filtro del guion bajo saca los internos, que no te interesan por ahora.
Crear tu propia clase
Una clase es el molde y un objeto es lo que sale del molde. Vamos con un ejemplo que sí tiene sentido.
class Venta:
def __init__(self, cliente, ciudad, monto, unidades):
self.cliente = cliente
self.ciudad = ciudad
self.monto = monto
self.unidades = unidades
def precio_unitario(self):
return round(self.monto / self.unidades, 2)
def con_igv(self, igv=0.18):
return round(self.monto * (1 + igv), 2)
v = Venta('C0045', 'Trujillo', 480.37, 10)
print(v.cliente)
print(v.precio_unitario())
print(v.con_igv())
C0045 48.04 566.84
Tres cosas y ya sabes leer cualquier clase:
- 🏗️
__init__es lo que corre al crear el objeto. Ahí se guardan los datos. - 👉
selfes "este objeto en particular". Va como primer argumento de todos los métodos y nunca se pasa al llamarlos: Python lo pone solo. - 💾
self.cliente = clienteguarda el dato dentro del objeto para que siga ahí después.
Si te olvidas del self, pasa esto:
class Mal:
def __init__(self, monto):
self.monto = monto
def doble():
return 0
Mal(100).doble()
TypeError: Mal.doble() takes 0 positional arguments but 1 was given
Python le pasó el objeto como primer argumento y el método no lo esperaba. Ese
"1 was given" cuando tú no diste ninguno es la firma del self
olvidado 😄
Que se imprima bonito
print(v)
<__main__.Venta object at ...
Feo, ¿no? Se arregla con __repr__:
class Venta:
def __init__(self, cliente, ciudad, monto):
self.cliente = cliente
self.ciudad = ciudad
self.monto = monto
def __repr__(self):
return f'Venta({self.cliente}, {self.ciudad}, S/{self.monto})'
print(Venta('C0045', 'Trujillo', 480.37))
print([Venta('C0325', 'Lima', 524.9), Venta('C0260', 'Cusco', 154.83)])
Venta(C0045, Trujillo, S/480.37) [Venta(C0325, Lima, S/524.9), Venta(C0260, Cusco, S/154.83)]
Fíjate en la segunda línea: al imprimir una lista de objetos, cada uno usa su
__repr__. Eso hace que depurar sea infinitamente más cómodo 🌟
dataclass: lo mismo con mucho menos
Si la clase es solo para guardar datos, hay un atajo que te ahorra escribir el
__init__ y el __repr__:
from dataclasses import dataclass
@dataclass
class Venta:
cliente: str
ciudad: str
monto: float
unidades: int = 1
def precio_unitario(self):
return round(self.monto / self.unidades, 2)
v = Venta('C0045', 'Trujillo', 480.37, 10)
print(v)
print(v.precio_unitario())
print(Venta('C0325', 'Lima', 524.9))
Venta(cliente='C0045', ciudad='Trujillo', monto=480.37, unidades=10) 48.04 Venta(cliente='C0325', ciudad='Lima', monto=524.9, unidades=1)
Ese @dataclass de arriba es un decorador, o sea algo que le
agrega comportamiento a la clase. Escribió el __init__, el
__repr__ y la comparación por ti.
Y esos : str y : float son anotaciones de tipo. No
obligan nada en tiempo de ejecución, pero tu editor las lee y te avisa si metes
un texto donde iba un número 💜
Y ahora la pregunta importante: ¿te hace falta una clase?
Casi siempre no. De verdad.
| Situación | Qué usar |
|---|---|
| Una fila de datos suelta | Un diccionario |
| Muchas filas con las mismas columnas | Un DataFrame de pandas |
| Una transformación | Una función |
| Datos y comportamiento que van SIEMPRE juntos | Ahí sí, una clase |
Mira estas dos formas de hacer lo mismo:
venta_dict = {'cliente': 'C0045', 'monto': 480.37, 'unidades': 10}
print(round(venta_dict['monto'] / venta_dict['unidades'], 2))
venta_obj = Venta('C0045', 'Trujillo', 480.37, 10)
print(venta_obj.precio_unitario())
48.04 48.04
Para una fila, el diccionario gana por simple. Para tres mil filas, ninguna de las dos: ahí va pandas, que es el capítulo 11.
Mi regla después de varios proyectos: escribe funciones hasta que te duela, y recién ahí piensa en una clase. La clase se justifica cuando llevas cinco funciones que reciben siempre los mismos tres argumentos. Ahí esos tres argumentos querían ser un objeto 🌸
Ejercicios
1. Atributo o método
De un texto, saca su largo, su versión en mayúsculas y cuántas veces aparece una letra. Di cuál de las tres es un atributo.
t = 'Minimarket El Sol'
print(len(t))
print(t.upper())
print(t.count('l'))
17 MINIMARKET EL SOL 2
Ninguna de las tres es un atributo: len() es una función suelta y
las otras dos son métodos. Los textos casi no tienen atributos públicos, y por
eso todo lleva paréntesis.
2. Provoca el error de los paréntesis
Con una fecha, llama a un atributo como si fuera método.
from datetime import date d = date(2026, 8, 15) print(d.day) print(d.day())
TypeError: 'int' object is not callable
3. El método sin llamar
Imprime un método sin los paréntesis y aprende a reconocer la salida.
t = 'hola' print(t.upper)
<built-in method upper of str object at ...
Si en tu salida aparece la palabra "method", te faltaron los paréntesis. Es de los errores más rápidos de detectar una vez que sabes mirarlo.
4. Tu primera clase
Crea una clase Cliente con nombre y ciudad, y
un método que devuelva una etiqueta tipo "Nombre (Ciudad)".
class Cliente:
def __init__(self, nombre, ciudad):
self.nombre = nombre
self.ciudad = ciudad
def etiqueta(self):
return f'{self.nombre} ({self.ciudad})'
print(Cliente('Bodega Rosa', 'Piura').etiqueta())
Bodega Rosa (Piura)
5. Que se imprima bien
Agrégale __repr__ a esa clase e imprime una
lista de dos clientes.
class Cliente:
def __init__(self, nombre, ciudad):
self.nombre = nombre
self.ciudad = ciudad
def __repr__(self):
return f'Cliente({self.nombre!r}, {self.ciudad!r})'
print([Cliente('Bodega Rosa', 'Piura'), Cliente('Sol', 'Lima')])
[Cliente('Bodega Rosa', 'Piura'), Cliente('Sol', 'Lima')]
Ese !r dentro del f-string aplica repr() al valor, y
por eso salen las comillas. Es el detalle que hace que el repr se pueda copiar y
pegar para recrear el objeto.
6. Con dataclass
Reescribe la clase anterior como dataclass y
compara cuánto código te ahorraste.
from dataclasses import dataclass
@dataclass
class Cliente:
nombre: str
ciudad: str
activo: bool = True
print(Cliente('Bodega Rosa', 'Piura'))
print(Cliente('Sol', 'Lima', activo=False))
Cliente(nombre='Bodega Rosa', ciudad='Piura', activo=True) Cliente(nombre='Sol', ciudad='Lima', activo=False)
7. El self olvidado
Escribe un método sin self a propósito y lee el
mensaje de error con calma.
class Pedido:
def total():
return 0
Pedido().total()
TypeError: Pedido.total() takes 0 positional arguments but 1 was given
Ese "1 was given" cuando tú no diste ninguno es el objeto que Python metió
solo. Cuando veas ese mensaje, mira el self.
8. Diccionario o clase
Guarda tres ventas de las dos formas y calcula el total. Después decide cuál te gusta más para este caso.
from dataclasses import dataclass
@dataclass
class V:
ciudad: str
monto: float
como_dict = [{'ciudad': 'Lima', 'monto': 480.37},
{'ciudad': 'Cusco', 'monto': 154.83}]
como_obj = [V('Lima', 480.37), V('Cusco', 154.83)]
print(round(sum(d['monto'] for d in como_dict), 2))
print(round(sum(o.monto for o in como_obj), 2))
635.2 635.2
Mismo resultado. Para dos ventas el diccionario sobra; la clase empieza a ganar cuando además necesitas métodos, y pandas les gana a las dos en cuanto son más de cien filas.
Lo que te llevas
- 🔵 En Python casi todo es un objeto, y por eso todo se usa con puntos.
- 📊 Atributo sin paréntesis, método con paréntesis.
- 🔎 Si tu salida dice "method", te faltaron los paréntesis.
- 👉
selfes este objeto, va primero en cada método y no se pasa al llamar. - ⚡
@dataclasste escribe el__init__y el__repr__. - 🤏 Antes de crear una clase, pregúntate si un diccionario o una función bastan. Casi siempre bastan.
En el capítulo 9 abrimos archivos de verdad: CSV, Excel, JSON, y la codificación, que es la razón por la que a veces ves "á" donde debería haber una á.
Que tengas lindo día! 🌸