Clases y objetos
Cada tipo con el que has trabajado hasta ahora (cadenas, listas, diccionarios) es en realidad una clase. Cuando llamas a "hola".upper(), estás llamando a un método sobre un objeto de cadena. Las clases te permiten definir tus propios tipos con sus propios datos y comportamiento. Una clase Player puede almacenar un nombre, una puntuación y un nivel, y saber cómo mostrarse a sí misma.
Plano e instancias
Una clase es un plano. Una instancia es algo específico hecho a partir de ese plano. Puedes crear tantas instancias como necesites, cada una con sus propios datos pero compartiendo los mismos métodos definidos en la clase.
class Dog:
def bark(self):
print("¡Guau!")
rex = Dog()
luna = Dog()
rex.bark() # "¡Guau!"
luna.bark() # "¡Guau!"Dog es la clase. rex y luna son instancias: dos perros distintos, cada uno compartiendo el mismo comportamiento definido en la clase.
__init__ y self
__init__ es el método que Python llama automáticamente cuando creas una nueva instancia. Es donde configuras los datos iniciales del objeto. self es la forma en que un método se refiere a la instancia específica sobre la que está operando, y siempre es el primer parámetro.
class Player:
def __init__(self, name, score=0):
self.name = name
self.score = score
def add_points(self, points):
self.score += points
def display(self):
print(f"{self.name}: {self.score} puntos")
sofia = Player("Sofía")
mateo = Player("Mateo", score=50)
sofia.add_points(30)
sofia.display() # "Sofía: 30 puntos"
mateo.display() # "Mateo: 50 puntos"self.name y self.score son atributos de instancia: pertenecen al objeto específico, no a la clase en sí. Cada instancia de Player tiene su propio name y score.
Métodos
Cualquier función definida dentro de una clase es un método. Los métodos de instancia siempre tienen self como primer parámetro; Python lo pasa automáticamente. Los métodos pueden leer y cambiar los datos de la instancia a través de self.
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def scale(self, factor):
self.radius *= factor
return self # devolver self permite encadenar: c.scale(2).scale(0.5)
c = Circle(5)
print(c.area()) # 78.53975
c.scale(2)
print(c.area()) # 314.159Variables de clase vs variables de instancia
Las variables definidas directamente en la clase (no dentro de __init__) son variables de clase. Todas las instancias comparten la misma variable de clase. Las variables establecidas en self dentro de __init__ son variables de instancia, únicas para cada objeto.
class Player:
max_lives = 3 # variable de clase, igual para cada Player
def __init__(self, name):
self.name = name # variable de instancia, única para cada Player
self.lives = Player.max_lives
def die(self):
self.lives -= 1
sofia = Player("Sofía")
mateo = Player("Mateo")
Player.max_lives = 5 # cambiar para todas las instancias actuales y futurasUsa variables de clase para valores compartidos entre todas las instancias: constantes, contadores, valores por defecto. Usa variables de instancia para datos que difieren por objeto.
__str__ y __repr__
__str__ controla lo que print() y las f-strings muestran para tu objeto. __repr__ controla la vista de desarrollador mostrada en la consola y para depuración. Define siempre __repr__. Define __str__ cuando quieras una visualización limpia para el usuario separada de la vista de depuración.
class Player:
def __init__(self, name, score):
self.name = name
self.score = score
def __str__(self):
return f"{self.name} ({self.score} pts)"
def __repr__(self):
return f"Player(name={self.name!r}, score={self.score})"
sofia = Player("Sofía", 87)
print(sofia) # "Sofía (87 pts)" (usa __str__)
repr(sofia) # "Player(name='Sofía', score=87)" (usa __repr__)Define siempre __repr__. Define __str__ cuando quieras una representación limpia para el usuario separada de la vista de depuración. Si solo se define __repr__, Python lo usa para ambos.
Convención de privacidad
Python no tiene variables verdaderamente privadas, pero un guión bajo al inicio de un nombre (_balance) es una convención que indica "esto es interno, no lo uses directamente desde fuera de la clase". No lo impone el lenguaje; es una comunicación a otros desarrolladores.
class BankAccount:
def __init__(self, balance):
self._balance = balance # _ significa "no tocar"
def deposit(self, amount):
if amount > 0:
self._balance += amount
def balance(self):
return self._balanceUn doble guión bajo (__name) activa el mangling de nombres; Python renombra el atributo a _ClassName__name para evitar conflictos en las subclases. Rara vez es necesario. El guión bajo simple es la convención en la mayoría del código.
Herencia
Una clase puede heredar de otra clase, obteniendo automáticamente todos sus atributos y métodos. Luego puedes sobreescribir métodos específicos en la subclase para cambiar su comportamiento. Esto te permite reutilizar una base común y especializar donde sea necesario.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return f"{self.name} dice ¡Guau!"
class Cat(Animal):
def speak(self):
return f"{self.name} dice ¡Miau!"
pets = [Dog("Rex"), Cat("Luna"), Dog("Max")]
for pet in pets:
print(pet.speak())Dog y Cat heredan __init__ de Animal, por lo que no necesitan el suyo propio. Sobreescriben speak() con su comportamiento específico.
super()
super() llama a un método de la clase padre. Úsalo cuando quieras extender el comportamiento del padre en lugar de reemplazarlo por completo: llama al __init__ del padre para ejecutar su configuración, luego agrega encima cualquier cosa que necesite tu subclase.
class Animal:
def __init__(self, name, sound):
self.name = name
self.sound = sound
class Dog(Animal):
def __init__(self, name):
super().__init__(name, "Guau") # llamar a Animal.__init__
self.tricks = [] # agregar algo extra
def learn(self, trick):
self.tricks.append(trick)
rex = Dog("Rex")
rex.learn("sentarse")
print(rex.tricks) # ["sentarse"]Llama siempre a super().__init__() cuando tu subclase tenga su propio __init__ y el padre también lo tenga.
Métodos de clase y métodos estáticos
@classmethod crea un método que recibe la clase en sí en lugar de una instancia. Es útil para constructores alternativos: crear una instancia a partir de una cadena, un archivo u otro formato. @staticmethod es una función simple que vive dentro de la clase por razones organizativas; no recibe ni la instancia ni la clase.
class Player:
def __init__(self, name, score):
self.name = name
self.score = score
@classmethod
def from_string(cls, data):
name, score = data.split(",")
return cls(name, int(score))
sofia = Player.from_string("Sofía,87")class Player:
@staticmethod
def is_valid_name(name):
return name.isalpha() and len(name) >= 2
Player.is_valid_name("Sofía") # True
Player.is_valid_name("A1") # FalseUsa @classmethod para constructores alternativos. Usa @staticmethod para funciones utilitarias que lógicamente pertenecen a la clase pero no necesitan datos de instancia ni de clase.
@property
@property te permite acceder a un método como si fuera un atributo, sin necesidad de paréntesis. Úsalo para valores que se calculan a partir de otros atributos y que se sienten naturales de leer como un acceso simple a atributos.
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self):
return 3.14159 * self.radius ** 2
@property
def diameter(self):
return self.radius * 2
c = Circle(5)
print(c.area) # 78.53975 (parece un atributo, se ejecuta como un método)
print(c.diameter) # 10Las propiedades son útiles para valores calculados: cosas derivadas de otros atributos que se sienten naturales de acceder sin ().
En la práctica
Una clase Player con atributos de instancia, métodos, un @property y __str__:
class Player:
max_lives = 3
def __init__(self, name: str):
self.name = name
self.score = 0
self.lives = Player.max_lives
def earn_points(self, amount: int) -> None:
self.score += amount
def take_hit(self) -> bool:
self.lives -= 1
return self.lives > 0
@property
def is_alive(self) -> bool:
return self.lives > 0
def __str__(self) -> str:
return f"{self.name} | Puntuación: {self.score} | Vidas: {self.lives}"
sofia = Player("Sofía")
sofia.earn_points(50)
sofia.take_hit()
print(sofia) # "Sofía | Puntuación: 50 | Vidas: 2"
print(sofia.is_alive) # True
