Capítulo 27, Intermediário
Atributos e métodos
Atributo de instância, atributo de classe, método de instância, de classe e estático. Cada um tem um papel, e confundi-los gera bugs sutis.
Código deste capítulo: intermediario/cap27_atributos_metodos.py
Atributos de instância e de classe
Um atributo de instância pertence a um objeto. Um atributo de classe é definido no corpo da classe e é compartilhado por todas as instâncias:
class Contador:
total_criados = 0
def __init__(self, nome):
self.nome = nome
Contador.total_criados += 1
a = Contador("a")
b = Contador("b")
print(Contador.total_criados, a.total_criados)
2 2
Ao ler a.total_criados, o Python procura primeiro no objeto, depois na classe, depois nas classes pai. Ao atribuir a.nivel = ..., ele sempre cria o atributo no objeto, sem tocar na classe:
class Config:
nivel = "padrão"
c = Config()
print(c.nivel)
c.nivel = "personalizado"
print(c.nivel, Config.nivel)
padrão
personalizado padrão
A armadilha do atributo de classe mutável
É o mesmo problema do valor padrão mutável do capítulo 18. Uma lista no corpo da classe é uma só para todas as instâncias:
class Turma:
alunos = []
def adicionar(self, nome):
self.alunos.append(nome)
t1, t2 = Turma(), Turma()
t1.adicionar("Ana")
print(t2.alunos)
['Ana']
A segunda turma "ganhou" a Ana sem ninguém pedir. A correção é criar a lista dentro do __init__, para cada instância ter a sua:
class TurmaCorreta:
def __init__(self):
self.alunos = []
def adicionar(self, nome):
self.alunos.append(nome)
t1, t2 = TurmaCorreta(), TurmaCorreta()
t1.adicionar("Ana")
print(t2.alunos)
[]
Métodos de instância, de classe e estáticos
| Tipo | Decorador | Primeiro parâmetro | Quando usar |
|---|---|---|---|
| De instância | nenhum | self | Usa ou altera os dados do objeto |
| De classe | @classmethod | cls | Construtores alternativos e lógica da classe |
| Estático | @staticmethod | nenhum | Função utilitária que pertence ao tema da classe |
O uso mais valioso do @classmethod é o construtor alternativo: criar um objeto a partir de outra representação, como um texto:
class Pessoa:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
@classmethod
def de_texto(cls, texto):
nome, idade = texto.split(",")
return cls(nome.strip(), int(idade))
@staticmethod
def eh_maior_de_idade(idade):
return idade >= 18
def __repr__(self):
return f"Pessoa({self.nome!r}, {self.idade})"
p = Pessoa.de_texto("Ana, 30")
print(p, Pessoa.eh_maior_de_idade(p.idade))
Pessoa('Ana', 30) True
Repare no cls(...) em vez de Pessoa(...): assim, uma subclasse que usar de_texto recebe um objeto da subclasse, e não da classe base.
Exercício 1
Produto a partir de um dicionário
Crie Produto(nome, preco) com o construtor alternativo de_dict(dados), que receba {"nome": ..., "preco": ...}.