self, métodos de instancia, @classmethod y @staticmethod
Python tiene tres tipos de métodos en una clase: de instancia (acceden a self), de clase (acceden a cls y actúan sobre la clase) y estáticos (funciones que viven en la clase por organización). Conocer la diferencia es señal de madurez.
Método de instancia y self
Un método de instancia recibe self como primer argumento. self es el objeto en sí — a través de él puedes leer y modificar cualquier atributo de la instancia o llamar a otros métodos.
class Counter:
def __init__(self, start: int = 0) -> None:
self.count = start
self._history: list[int] = [start]
# Método de instancia: accede y modifica self
def increment(self, amount: int = 1) -> None:
self.count += amount
self._history.append(self.count)
def reset(self) -> None:
self.count = 0
self._history.append(0)
def history(self) -> list[int]:
return self._history.copy()
# Los métodos pueden llamarse entre sí a través de self
def double_increment(self) -> None:
self.increment() # self.increment() llama al método del mismo objeto
self.increment()
c = Counter(10)
c.increment(5)
c.double_increment()
print(c.count) # 22
print(c.history()) # [10, 15, 16, 17]22
[10, 15, 16, 17]@classmethod y cls
Un @classmethod recibe cls (la clase) en lugar de self (la instancia). Se llama sobre la clase directamente — Clase.metodo() — aunque también funciona sobre instancias. Tiene acceso a los atributos de clase pero no a los de instancia.
class Temperature:
absolute_zero_celsius: float = -273.15
def __init__(self, celsius: float) -> None:
if celsius < Temperature.absolute_zero_celsius:
raise ValueError(f"Temperatura por debajo del cero absoluto")
self.celsius = celsius
# @classmethod — recibe cls, no self
@classmethod
def from_fahrenheit(cls, fahrenheit: float) -> "Temperature":
"""Factory method: crea Temperature desde Fahrenheit."""
celsius = (fahrenheit - 32) * 5 / 9
return cls(celsius) # cls() crea una instancia de la clase (o subclase)
@classmethod
def from_kelvin(cls, kelvin: float) -> "Temperature":
"""Factory method: crea Temperature desde Kelvin."""
return cls(kelvin + cls.absolute_zero_celsius)
def to_fahrenheit(self) -> float:
return self.celsius * 9 / 5 + 32
def __repr__(self) -> str:
return f"Temperature({self.celsius:.2f}°C)"
# Crear instancias desde distintas unidades
t1 = Temperature(100)
t2 = Temperature.from_fahrenheit(212)
t3 = Temperature.from_kelvin(373.15)
print(t1) # Temperature(100.00°C)
print(t2) # Temperature(100.00°C)
print(t3) # Temperature(100.00°C)
# También funciona sobre instancias (aunque es raro usarlo así)
t4 = t1.from_fahrenheit(32)
print(t4) # Temperature(0.00°C)Temperature(100.00°C)
Temperature(100.00°C)
Temperature(100.00°C)
Temperature(0.00°C)@staticmethod
Un @staticmethod no recibe ni self ni cls. Es simplemente una función que vive dentro de la clase por razones organizativas — su lógica está relacionada con la clase, pero no necesita acceder a ningún estado de ella.
class MathUtils:
PI: float = 3.141592653589793
@staticmethod
def is_prime(n: int) -> bool:
"""Verifica si n es primo — no necesita ni self ni cls."""
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
@staticmethod
def gcd(a: int, b: int) -> int:
"""Máximo común divisor — algoritmo de Euclides."""
while b:
a, b = b, a % b
return a
@staticmethod
def lcm(a: int, b: int) -> int:
"""Mínimo común múltiplo."""
return abs(a * b) // MathUtils.gcd(a, b)
# Se puede llamar sin instancia
print(MathUtils.is_prime(17)) # True
print(MathUtils.is_prime(18)) # False
print(MathUtils.gcd(48, 18)) # 6
print(MathUtils.lcm(4, 6)) # 12
# O sobre una instancia (aunque es inusual para staticmethods)
util = MathUtils()
print(util.is_prime(97)) # TrueTrue
False
6
12
TrueFactory methods con @classmethod
El patrón factory method con @classmethod es la forma idiomática de ofrecer múltiples constructores. Usar cls(...) en vez del nombre de la clase hace que funcione correctamente con la herencia.
from datetime import date
class Person:
def __init__(self, name: str, birth_year: int) -> None:
self.name = name
self.birth_year = birth_year
@classmethod
def from_birth_date(cls, name: str, birth_date: date) -> "Person":
"""Crea una persona a partir de una fecha de nacimiento completa."""
return cls(name, birth_date.year)
@classmethod
def from_string(cls, data: str) -> "Person":
"""Parsea 'nombre,año' para crear una Person."""
name, year_str = data.split(",")
return cls(name.strip(), int(year_str.strip()))
@property
def age(self) -> int:
return date.today().year - self.birth_year
def __repr__(self) -> str:
return f"Person('{self.name}', {self.birth_year})"
# Tres formas de crear el mismo objeto
p1 = Person("Ana", 1990)
p2 = Person.from_birth_date("Ana", date(1990, 6, 15))
p3 = Person.from_string("Ana, 1990")
print(p1) # Person('Ana', 1990)
print(p2) # Person('Ana', 1990)
print(p3) # Person('Ana', 1990)
print(f"Edad: {p1.age} años")Person('Ana', 1990)
Person('Ana', 1990)
Person('Ana', 1990)
Edad: 36 años| Tipo de método | Primer arg | Cuándo usarlo |
|---|---|---|
| instancia (normal) | self | Opera sobre datos del objeto |
| @classmethod | cls | Factory, constructores alternativos |
| @staticmethod | (ninguno) | Función utilitaria relacionada con la clase |