Python Atributos e Métodos
Um objeto junta duas coisas: o que ele tem e o que ele faz. Uma conta bancária tem titular e saldo, e faz depósitos e saques. O que o objeto tem são os atributos (os dados); o que ele faz são os métodos (as funções que moram na classe). Na aula __init__ e self você escreveu o primeiro método, mostrar. Nesta aula você vai escrever métodos que mudam o objeto, métodos que respondem uma pergunta, e vai conhecer um segundo tipo de atributo: o que pertence à classe inteira, e não a cada objeto.
- A escrever métodos que mudam os dados do objeto (
depositar,sacar). - A escrever métodos que devolvem um valor com
return, e um método que chama outro comself.. - A diferença entre atributo de instância (de cada objeto) e atributo de classe (de todos).
- A contar quantos objetos uma classe já criou.
- A armadilha da lista guardada na classe, que todos os objetos dividem sem querer.
- A perguntar se um objeto tem um atributo, com
hasattregetattr. - Os erros reais: o método sem parênteses, o atributo com o nome do método, o método chamado sem objeto e o
self.esquecido.
A ideia: substantivos e verbos
Pense na ficha de um aluno da escola do bairro. Os campos da ficha (nome, turma, notas) são substantivos: coisas que o aluno tem. Mas a secretaria também faz coisas com a ficha: lança uma nota, calcula a média, diz se o aluno passou. Isso são verbos.
| Atributo | Método | |
|---|---|---|
| O que é | Um dado guardado no objeto | Uma função que mora na classe |
| Parece com | Um substantivo: saldo, nome | Um verbo: depositar, mostrar |
| Como se usa | Sem parênteses: conta.saldo | Com parênteses: conta.depositar(50) |
| Você já usou | — | "ana".upper(), lista.append(5) |
Os métodos de texto e de lista que você já conhece são exatamente isso: métodos escritos dentro das classes str e list. O compras.append("leite") é um método que muda a lista; o nome.upper() é um método que devolve um texto novo. Vamos escrever os dois tipos.
1. Métodos que mudam o objeto
class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def depositar(self, valor):
self.saldo += valor
print(f"Depósito de R$ {valor:.2f}. Saldo: R$ {self.saldo:.2f}")
def sacar(self, valor):
if valor > self.saldo:
print(f"Saque de R$ {valor:.2f} negado. Saldo: R$ {self.saldo:.2f}")
else:
self.saldo -= valor
print(f"Saque de R$ {valor:.2f}. Saldo: R$ {self.saldo:.2f}")
conta = ContaBancaria("Ana", 100)
conta.depositar(50)
conta.sacar(30)
conta.sacar(500)
print(conta.saldo)
- def depositar(self, valor):
- Um método com um parâmetro além do
self. Quem chama passa só ovalor:conta.depositar(50). Oselfé aconta, que vem antes do ponto. - self.saldo += valor
- Muda o atributo
saldodo próprio objeto. A mudança fica guardada: o próximo método que olhar o saldo vai ver o valor novo. - valor
- Um parâmetro comum, que só existe enquanto o método roda. Não precisa de
self.: não é um dado do objeto, é só o valor desta operação. - if valor > self.saldo:
- O método pode decidir. É a vantagem de ter o saque dentro da classe: a regra "não dá para sacar mais do que tem" fica num lugar só, e vale para toda conta.
$ python3 conta.py Depósito de R$ 50.00. Saldo: R$ 150.00 Saque de R$ 30.00. Saldo: R$ 120.00 Saque de R$ 500.00 negado. Saldo: R$ 120.00 120
Cada chamada partiu do saldo que a anterior deixou: 100 + 50 = 150, menos 30 = 120, e o saque de 500 foi negado. O objeto lembra o que aconteceu com ele. (O último print mostra 120, sem casas decimais, porque a conta começou com o inteiro 100 e só recebeu inteiros.)
Missão: a conta da Bia sabe depositar, mas ainda não sabe sacar, e o programa quebra na linha do saque. Escreva o método sacar(self, valor), que tira o valor do saldo. A Bia tem 100 reais e saca 30: o programa deve mostrar Saldo: R$ 70.00.
class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def depositar(self, valor):
self.saldo += valor
# escreva aqui o método sacar
def mostrar(self):
print(f"Saldo: R$ {self.saldo:.2f}")
conta = ContaBancaria("Bia", 100)
conta.sacar(30)
conta.mostrar()
class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def depositar(self, valor):
self.saldo += valor
def sacar(self, valor):
self.saldo -= valor
def mostrar(self):
print(f"Saldo: R$ {self.saldo:.2f}")
conta = ContaBancaria("Bia", 100)
conta.sacar(30)
conta.mostrar()
2. Métodos que respondem: return
Nem todo método precisa imprimir. Muitos só calculam e devolvem uma resposta, com return (aula return), e quem chamou decide o que fazer com ela: somar, comparar, mostrar.
class Produto:
def __init__(self, nome, preco, estoque=0):
self.nome = nome
self.preco = preco
self.estoque = estoque
def valor_em_estoque(self):
return self.preco * self.estoque
def tem_estoque(self):
return self.estoque > 0
def resumo(self):
situacao = "tem" if self.tem_estoque() else "esgotado"
return f"{self.nome} ({situacao}): R$ {self.valor_em_estoque():.2f} parados"
cafe = Produto("Café", 18.90, 12)
leite = Produto("Leite", 6.50)
total = cafe.valor_em_estoque() + leite.valor_em_estoque()
print(f"Total parado no estoque: R$ {total:.2f}")
print(cafe.resumo())
print(leite.resumo())
- return self.preco * self.estoque
- Calcula quanto dinheiro está "parado" na prateleira e devolve. O método não mostra nada: quem chamou recebe o número.
- return self.estoque > 0
- Um método que responde sim ou não (
TrueouFalse). É costume dar a esses métodos nomes de pergunta:tem_estoque,esta_vazio,pode_sacar. - self.tem_estoque()
- Um método chamando outro método do mesmo objeto. Repare no
self.antes: é o "eu mesmo, verifique se eu tenho estoque". - cafe.valor_em_estoque() + leite.valor_em_estoque()
- Como o método devolve um número, dá para fazer conta com o resultado.
$ python3 devolve.py Total parado no estoque: R$ 226.80 Café (tem): R$ 226.80 parados Leite (esgotado): R$ 0.00 parados
Prefira métodos que devolvem. Um método que só imprime serve para uma coisa: imprimir. Um que devolve serve para imprimir, somar, comparar, guardar num arquivo. O "ana".upper() não imprime nada: devolve "ANA", e você faz o que quiser com ele.
3. Atributos de instância × atributos de classe
Todos os atributos que você criou até aqui foram escritos com self., dentro do __init__. Eles se chamam atributos de instância: cada objeto tem o seu (o nome do café não é o nome do pão).
Às vezes, um dado é o mesmo para todos os objetos. Todos os produtos são da mesma loja, por exemplo. Em vez de guardar uma cópia em cada objeto, você escreve o dado direto na classe, fora dos métodos. É um atributo de classe. Na comparação da forma de bolo: o sabor é de cada bolo (instância); a marca gravada no fundo da forma sai igual em todos (classe).
class Produto:
loja = "Mercadinho da Ana"
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cafe = Produto("Café", 18.90)
pao = Produto("Pão de forma", 9.50)
print(cafe.loja)
print(pao.loja)
print(Produto.loja)
Produto.loja = "Mercadinho Aurora"
print(cafe.loja)
print(pao.loja)
print(vars(cafe))
- loja = "Mercadinho da Ana"
- Escrito dentro da classe, mas fora de qualquer método e sem
self.. É um atributo da classeProduto. - cafe.loja
- O café não tem um atributo
lojapróprio. Quando não acha o nome no objeto, o Python procura na classe, e acha lá. - Produto.loja
- O atributo de classe também pode ser lido pela própria classe, sem objeto nenhum.
- Produto.loja = "Mercadinho Aurora"
- Trocar o valor na classe muda o que todos os objetos enxergam, de uma vez.
$ python3 de_classe.py
Mercadinho da Ana
Mercadinho da Ana
Mercadinho da Ana
Mercadinho Aurora « mudou na classe…
Mercadinho Aurora « …e todos os objetos enxergam
{'nome': 'Café', 'preco': 18.9}
A última linha é a prova: dentro do objeto cafe só há nome e preco. A loja não está nele; está na classe, e o objeto só "pega emprestado".
A ordem da busca: primeiro o objeto, depois a classe
E se a Ana escrever cafe.loja = "Barraca da feira"? A regra é: escrever com objeto. cria (ou troca) o atributo no objeto, nunca na classe. O café ganha uma loja só dele, que passa a tapar a da classe:
class Produto:
loja = "Mercadinho da Ana"
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cafe = Produto("Café", 18.90)
pao = Produto("Pão de forma", 9.50)
cafe.loja = "Barraca da feira"
print(cafe.loja)
print(pao.loja)
print(Produto.loja)
print(vars(cafe))
del cafe.loja
print(cafe.loja)
$ python3 sombra.py
Barraca da feira « o café tem uma loja própria
Mercadinho da Ana « o pão continua lendo a da classe
Mercadinho da Ana « a classe não mudou
{'nome': 'Café', 'preco': 18.9, 'loja': 'Barraca da feira'}
Mercadinho da Ana « depois do del, o café volta a ler a da classe
Quando o Python procura cafe.loja, ele olha primeiro no objeto; só se não achar, olha na classe. O del cafe.loja apaga o atributo próprio do café, e aí a busca volta a cair na classe.
| Atributo de instância | Atributo de classe | |
|---|---|---|
| Onde se escreve | Num método, com self.: self.nome = nome | No corpo da classe, fora dos métodos: loja = "…" |
| De quem é | De cada objeto | Da classe, dividido por todos os objetos |
| Como se lê | cafe.nome | Produto.loja (ou cafe.loja, se o café não tiver uma própria) |
| Como se troca | cafe.nome = "…" | Produto.loja = "…" (com o nome da classe) |
| Bom para | Os dados de cada um: nome, preço, saldo | Constantes e configurações iguais para todos; contadores |
Um contador de objetos
Um uso clássico do atributo de classe: contar quantos objetos já foram criados. Cada __init__ soma 1 no contador da classe:
class Produto:
total = 0
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
Produto.total += 1
print(Produto.total)
cafe = Produto("Café", 18.90)
pao = Produto("Pão de forma", 9.50)
leite = Produto("Leite", 6.50)
print(f"Produtos cadastrados: {Produto.total}")
$ python3 contador.py 0 Produtos cadastrados: 3
Repare no Produto.total += 1, com o nome da classe. Com self.total += 1 não funcionaria, e sem erro nenhum: veja em Quando dá errado.
4. Perguntando se o objeto tem um atributo
Duas funções embutidas ajudam quando você não sabe se um atributo existe:
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
cafe = Produto("Café", 18.90)
print(hasattr(cafe, "preco"))
print(hasattr(cafe, "estoque"))
print(getattr(cafe, "nome"))
print(getattr(cafe, "estoque", 0))
$ python3 getattr_.py True False Café 0 « não existe: devolveu o valor reserva
- hasattr(cafe, "preco")
- has attribute, "tem o atributo?". Responde
TrueouFalse, sem erro. O nome do atributo vai entre aspas, como texto. - getattr(cafe, "nome")
- get attribute, "pegue o atributo". O mesmo que
cafe.nome, mas com o nome num texto, útil quando o nome está guardado numa variável. - getattr(cafe, "estoque", 0)
- Com um terceiro valor: se o atributo não existir, devolve esse valor em vez de dar
AttributeError. Parecido com ogetdos dicionários.
No dia a dia, essas funções aparecem pouco: se o __init__ cria todos os atributos, você sabe que eles existem. Elas são úteis com objetos que vêm de fora do seu programa.
Formas diferentes de mudar um atributo
Para aumentar o saldo de uma conta, você pode mexer no atributo de fora ou pedir ao objeto, por um método:
| Forma | Exemplo | Quando usar |
|---|---|---|
| Pelo método | conta.depositar(50) | Quase sempre. A regra (validar o valor, avisar, registrar) fica num lugar só |
| Direto no atributo | conta.saldo += 50 | Em dados simples, sem regra nenhuma. Numa conta bancária, evite: ninguém confere se o valor faz sentido |
Com setattr | setattr(conta, "saldo", 150) | Raramente: só quando o nome do atributo está guardado numa variável |
O Python não proíbe ninguém de escrever conta.saldo = -1000 direto. Como avisar "não mexa aqui" é o assunto da aula Encapsulamento, e como validar cada mudança é o da aula @property.
Quando dá errado
Este é um dos erros mais famosos do Python. A Ana guardou a lista de notas como atributo de classe:
class Aluno:
notas = []
def __init__(self, nome):
self.nome = nome
def adicionar_nota(self, nota):
self.notas.append(nota)
bia = Aluno("Bia")
caio = Aluno("Caio")
bia.adicionar_nota(9.0)
caio.adicionar_nota(4.5)
print(bia.nome, bia.notas)
print(caio.nome, caio.notas)
$ python3 lista_compartilhada.py Bia [9.0, 4.5] « a Bia ganhou o 4,5 do Caio Caio [9.0, 4.5] « e o Caio, o 9 da Bia
Nenhuma mensagem de erro, e as notas se misturaram. Siga a busca: self.notas não existe na Bia, então o Python acha a lista da classe; o append põe o 9.0 nela. Depois, self.notas do Caio também cai na classe: a mesma lista. Existe uma lista só, dividida por todos. (Diferente do cafe.loja = …, o append não cria nada no objeto: ele muda a lista que já existe.)
Como consertar: crie a lista no __init__, com self.. Assim cada aluno nasce com a sua lista vazia:
class Aluno:
def __init__(self, nome):
self.nome = nome
self.notas = []
def adicionar_nota(self, nota):
self.notas.append(nota)
bia = Aluno("Bia")
caio = Aluno("Caio")
bia.adicionar_nota(9.0)
caio.adicionar_nota(4.5)
print(bia.nome, bia.notas)
print(caio.nome, caio.notas)
$ python3 lista_certa.py Bia [9.0] Caio [4.5]
Regra prática: listas, dicionários e conjuntos (as coisas que mudam, aula Mutável × Imutável) vão no __init__, com self.. Na classe, só valores que ninguém vai mudar por dentro.
self.total += 1
class Produto:
total = 0
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
self.total += 1
cafe = Produto("Café", 18.90)
pao = Produto("Pão de forma", 9.50)
leite = Produto("Leite", 6.50)
print(f"Produtos cadastrados: {Produto.total}")
print(cafe.total, pao.total, leite.total)
$ python3 contador_errado.py Produtos cadastrados: 0 1 1 1
Três produtos, e o contador da classe continua em 0. O self.total += 1 é o mesmo que self.total = self.total + 1: a parte da direita lê o 0 da classe, soma 1, e a parte da esquerda escreve no objeto (escrever com self. sempre cria no objeto). Cada produto ganhou um total próprio valendo 1, e a classe nem ficou sabendo. Como consertar: para mudar um atributo de classe, use o nome da classe: Produto.total += 1.
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def mostrar(self):
print(f"{self.nome}: R$ {self.preco:.2f}")
cafe = Produto("Café", 18.90)
cafe.mostrar
print("Fim do programa")
print(cafe.mostrar)
$ python3 sem_parenteses.py Fim do programa <bound method Produto.mostrar of <__main__.Produto object at 0x7fdd129906e0>>
A linha 11, cafe.mostrar, não fez nada, e sem erro: sem os parênteses, o método não é chamado, só citado. O último print mostra o que é o cafe.mostrar sem parênteses: um bound method, "método amarrado" ao objeto cafe, pronto para ser chamado, mas não chamado. Como consertar: cafe.mostrar(). Métodos são verbos: precisam dos parênteses para acontecer.
'int' object is not callable
class ContaBancaria:
def __init__(self, titular, saldo=0):
self.titular = titular
self.saldo = saldo
def saldo(self):
print(f"Saldo: R$ {self.saldo:.2f}")
conta = ContaBancaria("Ana", 100)
conta.saldo()
$ python3 mesmo_nome.py
Traceback (most recent call last):
File "/home/ana/mesmo_nome.py", line 11, in <module>
conta.saldo()
~~~~~~~~~~~^^
TypeError: 'int' object is not callable
"Um objeto int não pode ser chamado". O método saldo mora na classe; o atributo saldo (o número 100) mora no objeto. Na busca, o objeto vem primeiro: conta.saldo achou o número, e os parênteses tentaram "chamar" o 100, como se fosse uma função. Como consertar: dê nomes diferentes. Atributos com substantivos (saldo), métodos com verbos (mostrar_saldo).
missing 1 required positional argument: 'self'
class Produto:
def __init__(self, nome, preco):
self.nome = nome
self.preco = preco
def mostrar(self):
print(f"{self.nome}: R$ {self.preco:.2f}")
Produto.mostrar()
$ python3 sem_objeto.py
Traceback (most recent call last):
File "/home/ana/sem_objeto.py", line 10, in <module>
Produto.mostrar()
~~~~~~~~~~~~~~~^^
TypeError: Produto.mostrar() missing 1 required positional argument: 'self'
"Falta o argumento self". Mostrar qual produto? Pela classe não há objeto antes do ponto, então ninguém preencheu o self. É como pedir para a forma de bolo dizer o seu sabor. Como consertar: crie um objeto e chame nele: cafe = Produto("Café", 18.90) e cafe.mostrar().
self.
class Produto:
def __init__(self, nome, preco, estoque=0):
self.nome = nome
self.preco = preco
self.estoque = estoque
def tem_estoque(self):
return self.estoque > 0
def mostrar(self):
if tem_estoque():
print(f"{self.nome}: disponível")
else:
print(f"{self.nome}: esgotado")
leite = Produto("Leite", 6.50)
leite.mostrar()
$ python3 sem_self_chamada.py
Traceback (most recent call last):
File "/home/ana/sem_self_chamada.py", line 18, in <module>
leite.mostrar()
~~~~~~~~~~~~~^^
File "/home/ana/sem_self_chamada.py", line 11, in mostrar
if tem_estoque():
^^^^^^^^^^^
NameError: name 'tem_estoque' is not defined. Did you mean: 'self.tem_estoque'?
O rastro tem dois trechos: a chamada na linha 18 e, dentro do mostrar, a linha 11. "O nome tem_estoque não está definido. Você quis dizer self.tem_estoque?" O próprio Python sugere o conserto. O método mora na classe, não solto no programa; para chegar nele de dentro de outro método, passe pelo objeto. Como consertar: if self.tem_estoque():.
Missão: as notas da Bia e do Caio estão se misturando. Conserte a classe para cada aluno ter a sua própria lista de notas. O programa deve mostrar Bia [9.0] e Caio [4.5].
class Aluno:
notas = []
def __init__(self, nome):
self.nome = nome
def adicionar_nota(self, nota):
self.notas.append(nota)
bia = Aluno("Bia")
caio = Aluno("Caio")
bia.adicionar_nota(9.0)
caio.adicionar_nota(4.5)
print(bia.nome, bia.notas)
print(caio.nome, caio.notas)
class Aluno:
def __init__(self, nome):
self.nome = nome
self.notas = []
def adicionar_nota(self, nota):
self.notas.append(nota)
bia = Aluno("Bia")
caio = Aluno("Caio")
bia.adicionar_nota(9.0)
caio.adicionar_nota(4.5)
print(bia.nome, bia.notas)
print(caio.nome, caio.notas)
Referência rápida
| Escrita | O que faz |
|---|---|
def depositar(self, valor): | Método com parâmetro; chamado com conta.depositar(50) |
self.saldo += valor | Muda um atributo do próprio objeto |
return self.preco * self.estoque | O método devolve um valor para quem chamou |
self.tem_estoque() | Um método chamando outro do mesmo objeto |
loja = "…" no corpo da classe | Atributo de classe, dividido por todos os objetos |
Produto.total += 1 | Muda um atributo de classe (pelo nome da classe) |
hasattr(obj, "nome") | True se o objeto tiver o atributo |
getattr(obj, "nome", padrao) | Lê o atributo; se não existir, devolve padrao |
del obj.nome | Apaga o atributo do objeto |
| Sintoma | Causa provável |
|---|---|
| Objetos diferentes com a mesma lista | Lista criada na classe, e não no __init__ com self. |
| Contador da classe parado em 0 | self.total += 1 no lugar de Produto.total += 1 |
O método "não fez nada"; aparece <bound method …> | Faltaram os parênteses na chamada |
TypeError: 'int' object is not callable | Atributo e método com o mesmo nome |
missing 1 required positional argument: 'self' | Método chamado pela classe, sem objeto |
NameError … Did you mean: 'self.…'? | Faltou o self. para chamar outro método (ou ler um atributo) |
AttributeError: … object has no attribute 'sacar' | O método não existe na classe (ou o nome está errado) |
Exercícios
1. Dentro do método
Complete para o método aumentar o estoque do próprio produto:
+= quantidade
2. O contador da turma
Na classe Aluno, há o atributo de classe matriculados = 0. Complete a linha do __init__ que soma 1 no contador da classe:
3. Na classe Produto, loja = "Mercadinho da Ana" está fora dos métodos. A Ana escreve cafe.loja = "Feira". Quanto vale pao.loja?
4. Por que notas = [] no corpo da classe faz todos os alunos dividirem as notas?
5. print(conta.depositar) mostrou <bound method ContaBancaria.depositar of …>. O que faltou?
6. Qual destes é um bom nome para um método que responde se a conta pode pagar um boleto?
Missão final: a média da turma
Missão: a classe Aluno já guarda as notas. Escreva o método media(self), que devolve (com return) a média das notas do aluno: a soma dividida pela quantidade. O programa deve mostrar Bia: média 8.50 e Caio: média 5.25.
class Aluno:
def __init__(self, nome):
self.nome = nome
self.notas = []
def adicionar_nota(self, nota):
self.notas.append(nota)
# escreva aqui o método media
bia = Aluno("Bia")
bia.adicionar_nota(8.0)
bia.adicionar_nota(9.0)
caio = Aluno("Caio")
caio.adicionar_nota(4.5)
caio.adicionar_nota(6.0)
for aluno in [bia, caio]:
print(f"{aluno.nome}: média {aluno.media():.2f}")
class Aluno:
def __init__(self, nome):
self.nome = nome
self.notas = []
def adicionar_nota(self, nota):
self.notas.append(nota)
def media(self):
return sum(self.notas) / len(self.notas)
bia = Aluno("Bia")
bia.adicionar_nota(8.0)
bia.adicionar_nota(9.0)
caio = Aluno("Caio")
caio.adicionar_nota(4.5)
caio.adicionar_nota(6.0)
for aluno in [bia, caio]:
print(f"{aluno.nome}: média {aluno.media():.2f}")
Repare no for aluno in [bia, caio]: uma lista de objetos, e o mesmo método chamado em cada um. O sum e o len estão na aula Funções Embutidas. Para ir além: e se o aluno ainda não tiver nenhuma nota? Experimente tirar as notas do Caio e veja o ZeroDivisionError; um if not self.notas: return 0 no começo do método resolve.
Resumo
- Atributos são o que o objeto tem (substantivos, sem parênteses); métodos são o que ele faz (verbos, com parênteses).
- Um método muda o objeto mexendo em
self.atributo; a mudança fica guardada para os próximos métodos. - Prefira métodos que devolvem com
return; um método chama outro comself.metodo(). - Atributo de instância:
self.xno__init__, um por objeto. Atributo de classe: escrito no corpo da classe, dividido por todos. - Na leitura, o Python procura primeiro no objeto, depois na classe. Escrever com
objeto.x = …sempre cria no objeto; para mudar o da classe, useClasse.x. - Listas e dicionários vão no
__init__, comself.; na classe, eles viram uma lista só para todo mundo. hasattregetattrperguntam e leem atributos pelo nome, semAttributeError.