Blog

__init__ e self em Python

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). O self é 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__.

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.