Curso de Python / Distinguir atributos de instancia y de clase
Un atributo de instancia pertenece a un objeto concreto; un atributo de clase se define en la clase y puede ser consultado por sus instancias. Esta distinción evita un error frecuente: compartir una lista entre objetos que deberían conservar datos independientes.
Decidir a quién pertenece cada valor
El propietario de un registro y sus movimientos cambian de un objeto a otro. Por eso se asignan mediante self.owner y self.items dentro de __init__. La moneda predeterminada, en cambio, puede declararse como un atributo de clase si todos los registros empiezan con la misma etiqueta.
Un atributo definido en la clase no se copia automáticamente a cada instancia. En el caso ordinario de este ejemplo, Python busca primero el atributo propio del objeto y, si no existe, consulta la clase. Más adelante pueden intervenir descriptores, como las propiedades, que tienen otras reglas de prioridad.
Observar listas independientes y un valor predeterminado
Guarda este programa como attributes.py y ejecútalo con python attributes.py. No necesita dependencias adicionales.
class Ledger:
currency = "KRW"
def __init__(self, owner):
self.owner = owner
self.items = []
def add(self, amount):
self.items.append(amount)
a = Ledger("A")
b = Ledger("B")
a.add(3500)
print(a.items, b.items)
b.currency = "USD"
print(a.currency, b.currency, Ledger.currency)
El primer registro recibe un movimiento y el segundo conserva su lista vacía. La asignación a b.currency crea un atributo en esa instancia:
[3500] []
KRW USD KRW
La clase mantiene Ledger.currency == "KRW". La instancia a consulta ese valor, mientras que b encuentra su propio "USD". Cambiar después el valor de la clase afectaría a las instancias que siguen consultándolo, pero no reemplazaría el atributo que ya tiene b. Cambiar una etiqueta tampoco convierte importes entre monedas.
Reproducir el problema de la lista compartida
Crea otro archivo, attributes_shared.py, con el siguiente ejemplo. Aquí se comparan los mismos cambios en dos diseños; no sustituyas todavía el programa anterior.
class Shared:
items = []
class Separate:
def __init__(self):
self.items = []
a, b = Shared(), Shared()
a.items.append(1)
print(a.items, b.items)
x, y = Separate(), Separate()
x.items.append(1)
print(x.items, y.items)
[1] [1]
[1] []
En Shared, ambas referencias llegan a la misma lista definida en la clase. append modifica esa lista; no crea un atributo nuevo en la instancia. En Separate, cada llamada al inicializador crea una lista diferente. La diferencia entre modificar un objeto y reasignar un nombre también aparece al estudiar mutabilidad y alias.
Elegir una solución y comprobarla
Usa atributos de instancia para pedidos, gastos o sesiones independientes. Un contenedor compartido solo resulta adecuado si el diseño necesita precisamente ese estado común y define su duración y acceso. En clases de datos, field(default_factory=list) permite expresar la creación de una lista por objeto; se desarrolla en dataclass.
Para experimentar, mueve temporalmente items = [] a la clase del primer programa y elimina su asignación del inicializador. Repite la operación y observa el intercambio no deseado; después restaura self.items = []. Si recibes una lista externa como argumento y la guardas directamente, también compartes ese objeto. Una copia superficial separa el contenedor, pero conserva referencias a sus elementos internos.
Comprueba estos comportamientos con la explicación oficial de variables de clase e instancia. No atribuyas la independencia al nombre de la variable: depende de dónde se crea y guarda el objeto.




