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:
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
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:
class Config:
nivel = "padrão"
c = Config()
print(c.nivel)
c.nivel = "personalizado"
print(c.nivel, Config.nivel)
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:
class Turma:
alunos = []
def adicionar(self, nome):
self.alunos.append(nome)
t1, t2 = Turma(), Turma()
t1.adicionar("Ana")
print(t2.alunos)
['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:
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)
[]
Instance, class, and static methods
| Type | Decorator | First parameter | When to use |
|---|---|---|---|
| Instance | none | self | Uses or alters the object's data |
| Class | @classmethod | cls | Alternative constructors and class logic |
| Static | @staticmethod | none | Utility 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:
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
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": ...}.