Duas coisas sobre __init__ e self surpreendem quem veio de outra linguagem, e as duas ficam mais fáceis quando você vê o que o Python está realmente fazendo.
self é um parâmetro, não uma palavra-chave
Quando você chama d.speak(), o Python chama Dog.speak(d). A instância é passada como primeiro argumento, e self é só o nome que damos a ele.
# 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
Ele imprime:
Rex says woof
Rex says woof
As duas linhas são a mesma chamada. d.speak() é atalho.
Você pode provar que self é só convenção:
# 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())
Ele imprime:
Mia says meow
Funciona. Nunca escreva assim — todo mundo que lê Python espera self, e quebrar isso custa mais do que qualquer coisa que você poderia ganhar. Mas saber que é um parâmetro explica por que você precisa escrevê-lo em toda assinatura de método, o que de outra forma parece só ruído.
__init__ não cria o objeto
O nome sugere um construtor. Não é. Quando o __init__ roda, o objeto já existe — o __init__ só preenche ele.
# __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)
Ele imprime:
__new__ ran, object does not exist yet
__init__ ran, self already exists: Tracked
value = 5
O __new__ é o construtor de verdade. Você quase nunca vai escrever um — ele importa para tipos imutáveis e metaclasses, e em nenhum outro lugar. Mas explica a nomenclatura: __init__ inicializa, ele não cria.
Atributos de classe são compartilhados
Um atributo atribuído no corpo da classe pertence à classe. Existe um só, não importa quantas instâncias você crie.
# 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)
Ele imprime:
a.count = 2 | b.count = 1 | Counter.total = 3
count é por instância. total é um número só para a classe inteira.
A parte que derruba as pessoas
Ler um atributo de classe pela instância funciona. Atribuir pela instância não atualiza a classe — cria um novo atributo de instância que sombreia o da classe.
# 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__)
Ele imprime:
a.total = 99 | b.total = 3 | Counter.total = 3
a has its own: True | b does not: False
O a agora tem o próprio total. O b e a classe continuam compartilhando o original. É por isso que o Counter.total += 1 dentro do tick é escrito contra a classe e não como self.total += 1 — este último leria o valor da classe, somaria um, e guardaria o resultado na instância em silêncio.
A mesma regra é o motivo de um atributo de classe mutável ser uma armadilha. Uma lista no corpo da classe é compartilhada por todas as instâncias, e self.items.append(x) altera a lista compartilhada em vez de sombreá-la. Atribua atributos mutáveis no __init__.
O que lembrar
-
d.speak()éDog.speak(d). Oselfé o primeiro parâmetro, nomeado por convenção. -
__init__inicializa um objeto que já existe.__new__cria ele, e você raramente vai escrever um. -
Um atributo de classe é compartilhado. Ler pela instância encontra ele; atribuir pela instância sombreia ele.
-
Atributos de classe mutáveis são compartilhados e mutáveis — coloque eles no
__init__.