Blog

__init__ y self en Python

Dos cosas sobre __init__ y self sorprenden a quien viene de otro lenguaje, y las dos se entienden mejor cuando ves lo que Python está haciendo en realidad.

self es un parámetro, no una palabra reservada

Cuando llamas d.speak(), Python llama Dog.speak(d). La instancia se pasa como primer argumento, y self es solo el nombre que le damos.

# self is a parameter name, not a keyword
class Dog:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return f"{self.name} says woof"

d = Dog('Rex')
print(d.speak())
print(Dog.speak(d))          # the same call, written out

Imprime:

Rex says woof
Rex says woof

Las dos líneas son la misma llamada. d.speak() es la forma corta.

Puedes comprobar que self es solo una convención:

# proof that self is only a convention
class Cat:
    def __init__(whatever, name):
        whatever.name = name
    def speak(this):
        return f"{this.name} says meow"

print(Cat('Mia').speak())

Imprime:

Mia says meow

Funciona. Nunca lo escribas así: todo el que lee Python espera self, y romper esa convención cuesta más de lo que podría ganar. Pero saber que es un parámetro explica por qué tienes que escribirlo en la firma de cada método, algo que de otro modo parece ruido.

__init__ no crea el objeto

El nombre sugiere un constructor. No lo es. Para cuando __init__ corre, el objeto ya existe: __init__ solo lo llena.

# __init__ does not create the object, it fills one in
class Tracked:
    def __new__(cls, *args):
        print('  __new__ ran, object does not exist yet')
        return super().__new__(cls)
    def __init__(self, value):
        print('  __init__ ran, self already exists:', type(self).__name__)
        self.value = value

t = Tracked(5)
print('value =', t.value)

Imprime:

  __new__ ran, object does not exist yet
  __init__ ran, self already exists: Tracked
value = 5

__new__ es el constructor de verdad. Casi nunca vas a escribir uno: importa para los tipos inmutables y las metaclases, y en ningún otro lado. Pero explica el nombre: __init__ inicializa, no crea.

Los atributos de clase son compartidos

Un atributo asignado en el cuerpo de la clase pertenece a la clase. Existe uno solo, sin importar cuántas instancias crees.

# instance attributes vs class attributes
class Counter:
    total = 0                      # shared by every instance
    def __init__(self):
        self.count = 0             # one per instance
    def tick(self):
        self.count += 1
        Counter.total += 1

a, b = Counter(), Counter()
a.tick(); a.tick(); b.tick()
print('a.count =', a.count, '| b.count =', b.count, '| Counter.total =', Counter.total)

Imprime:

a.count = 2 | b.count = 1 | Counter.total = 3

count es por instancia. total es un único número para toda la clase.

La parte que hace tropezar a la gente

Leer un atributo de clase a través de una instancia funciona. Asignar a través de una instancia no actualiza la clase: crea un atributo de instancia nuevo que lo tapa.

# assigning through the instance shadows the class attribute
a.total = 99
print('a.total =', a.total, '| b.total =', b.total, '| Counter.total =', Counter.total)
print('a has its own:', 'total' in a.__dict__, '| b does not:', 'total' in b.__dict__)

Imprime:

a.total = 99 | b.total = 3 | Counter.total = 3
a has its own: True | b does not: False

Ahora a tiene su propio total. b y la clase siguen compartiendo el original. Por eso Counter.total += 1 dentro de tick está escrito contra la clase y no como self.total += 1: esta última leería el valor de la clase, le sumaría uno, y guardaría el resultado en la instancia sin decir nada.

La misma regla explica por qué un atributo de clase mutable es una trampa. Una lista en el cuerpo de la clase la comparten todas las instancias, y self.items.append(x) muta la lista compartida en lugar de taparla. Asigna los atributos mutables en __init__.

Qué recordar

  • d.speak() es Dog.speak(d). self es el primer parámetro, nombrado por convención.

  • __init__ inicializa un objeto que ya existe. __new__ lo crea, y rara vez vas a escribir uno.

  • Un atributo de clase es compartido. Leerlo a través de una instancia lo encuentra; asignarlo a través de una instancia lo tapa.

  • Los atributos de clase mutables son compartidos y mutables: ponlos en __init__.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.