Pular para o conteúdo

    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:

    intermediario/cap27_atributos_metodos.pylinhas 10 a 20
    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)
    
    Saída
    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:

    intermediario/cap27_atributos_metodos.pylinhas 22 a 29
    class Config:
        nivel = "padrão"
    
    
    c = Config()
    print(c.nivel)
    c.nivel = "personalizado"
    print(c.nivel, Config.nivel)
    
    Saída
    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:

    intermediario/cap27_atributos_metodos.pylinhas 34 a 43
    class Turma:
        alunos = []
    
        def adicionar(self, nome):
            self.alunos.append(nome)
    
    
    t1, t2 = Turma(), Turma()
    t1.adicionar("Ana")
    print(t2.alunos)
    
    Saída
    ['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:

    intermediario/cap27_atributos_metodos.pylinhas 45 a 55
    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)
    
    Saída
    []
    

    Métodos de instância, de classe e estáticos

    TipoDecoradorPrimeiro parâmetroQuando usar
    De instâncianenhumselfUsa ou altera os dados do objeto
    De classe@classmethodclsConstrutores alternativos e lógica da classe
    Estático@staticmethodnenhumFunçã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:

    intermediario/cap27_atributos_metodos.pylinhas 60 a 79
    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))
    
    Saída
    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": ...}.