Skip to content

    Chapter 27, Intermediate

    Attributes and methods

    Instance attribute, class attribute, instance method, class method, and static method. Each has a role, and confusing them generates subtle bugs.

    Code for this chapter: intermediario/cap27_atributos_metodos.py

    Instance and class attributes

    An attribute of instance belongs to an object. A class attribute is defined in the body of the class and is shared by all instances:

    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)
    
    Output
    2 2
    

    When reading a.total_criados, Python first looks in the object, then in the class, then in the parent classes. When assigning a.nivel = ..., it always creates the attribute in the object, without touching the class:

    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)
    
    Output
    padrão
    personalizado padrão
    

    The trap of mutable class attributes

    It's the same problem of the mutable default value from chapter 18. A list in the class body is the same one for all instances:

    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)
    
    Output
    ['Ana']
    

    The second group "gained" Ana without anyone asking. The fix is to create the list inside the __init__, so each instance has its own:

    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)
    
    Output
    []
    

    Instance, class, and static methods

    TypeDecoratorFirst parameterWhen to use
    InstancenoneselfUses or alters the object's data
    Class@classmethodclsAlternative constructors and class logic
    Static@staticmethodnoneUtility function that belongs to the class theme

    The most valuable use of the @classmethod is the alternative constructor: to create an object from another representation, like text:

    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))
    
    Output
    Pessoa('Ana', 30) True
    

    Notice in the cls(...) instead of Pessoa(...): thus, a subclass that uses de_texto receives an object of the subclass, not of the base class.

    Exercise 1

    Product from a dictionary

    Create Produto(nome, preco) with the alternative constructor de_dict(dados), which receives {"nome": ..., "preco": ...}.