Projeto Contador de Palavras
A Ana escreveu uma crônica sobre um dia na padaria para o jornalzinho do bairro e ficou curiosa: qual palavra ela mais repetiu? Contar à mão, num texto de 150 palavras, já cansa; num livro, seria impossível. Neste projeto você escreve um programa que lê o texto de um arquivo, separa as palavras, conta quantas vezes cada uma aparece e mostra as 10 mais usadas, com uma barrinha para cada. No caminho aparecem os problemas de verdade: a vírgula grudada na palavra, o "O" maiúsculo que não é igual ao "o", as palavras pequenas que enchem a contagem.
- Ler um arquivo de texto com open e with (aula Lendo Arquivos).
- Separar e limpar o texto com split(), lower() e replace().
- Contar com um dicionário e o método
get, e depois com oCounterdo módulo collections. - Ordenar pelo número de vezes com sorted() e uma lambda no
key. - Filtrar palavras com list comprehension e um conjunto.
- Alinhar a saída com f-strings e organizar o programa em funções.
O que vamos construir
Este é o resultado final, no computador da Ana, contando a crônica do arquivo cronica.txt:
$ python3 contador.py Palavras: 148, sem as comuns: 88 As 10 mais usadas: pão 9 ######### café 5 ##### forno 3 ### padaria 2 ## quente 2 ## seis 2 ## cheiro 2 ## rua 2 ## pede 2 ## dez 2 ##
"Pão" ganhou com folga, e "café" vem em segundo. Faz sentido para uma crônica de padaria. Vamos chegar lá em quatro versões, cada uma acrescentando uma coisa só:
- Ler o arquivo e separar as palavras (
open,read,split). - Limpar: minúsculas e sem pontuação (
lower,replace). - Contar com um dicionário e ordenar (
get,sorted,lambda). - Counter, palavras comuns e barras (
Counter,most_common, conjunto).
Antes de começar: o texto
No seu computador, salve o texto abaixo num arquivo chamado cronica.txt, na mesma pasta do programa (com o nano, por exemplo, ou num editor de texto). Nas caixas Experimente desta página, ele já vem pronto numa aba ao lado do programa: clique na aba cronica.txt para ver (e mudar) o texto. Se você mudar o texto, as contagens mudam, e a missão pode deixar de bater.
A Padaria Pão Quente abre às seis. Antes do sol, o pão já está no forno, e o cheiro de pão quente chega à rua inteira. Dona Rosa é a primeira cliente: pede dez pães, um café e "o de sempre". O café da padaria é forte; o pão, macio. Depois vem o Seu Joaquim. Ele pede café, pão de queijo e conversa. Conversa sobre o tempo, sobre o time, sobre o preço do café! — O pão subiu? — pergunta ele. — O pão não, Seu Joaquim. Subiu o café — responde a Ana, rindo. Às dez, o bolo de milho sai do forno. Quem passa na rua sente o cheiro e entra. O bolo acaba antes do almoço; o pão, antes do jantar. No fim do dia, a Ana fecha o caixa, conta o pão que sobrou e anota tudo no caderno... Amanhã, às seis, o forno acende de novo.
Versão 1 · Ler o arquivo e separar as palavras
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
palavras = texto.split()
print(f"Caracteres: {len(texto)}")
print(f"Palavras: {len(palavras)}")
print(palavras[:12])
- with open("cronica.txt", encoding="utf-8") as arquivo:
- Abre o arquivo para leitura. O
withfecha o arquivo sozinho no fim do bloco. Oencoding="utf-8"diz como as letras estão gravadas, para os acentos virem certos (veja Quando dá errado). - texto = arquivo.read()
- Lê o arquivo inteiro de uma vez, como um texto só, com as quebras de linha dentro (
\n). - texto.split()
- Corta o texto nos espaços e nas quebras de linha e devolve uma lista com os pedaços.
- palavras[:12]
- As 12 primeiras, para dar uma olhada (aula Índices e Fatias, que vale também para listas).
$ python3 contador.py Caracteres: 716 Palavras: 152 ['A', 'Padaria', 'Pão', 'Quente', 'abre', 'às', 'seis.', 'Antes', 'do', 'sol,', 'o', 'pão']
152 palavras. Mas olhe a lista: 'seis.' veio com o ponto, 'sol,' com a vírgula, e há 'Pão' e 'pão'. Para o Python, 'pão', 'Pão' e 'pão,' são três textos diferentes, e contariam separado. É o que a versão 2 resolve.
Missão: o programa só mostra quantos caracteres o texto tem. Separe as palavras com split() e mostre também Palavras: … com a quantidade.
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
print(f"Caracteres: {len(texto)}")
A Padaria Pão Quente abre às seis. Antes do sol, o pão já está no forno, e o cheiro de pão quente chega à rua inteira. Dona Rosa é a primeira cliente: pede dez pães, um café e "o de sempre". O café da padaria é forte; o pão, macio. Depois vem o Seu Joaquim. Ele pede café, pão de queijo e conversa. Conversa sobre o tempo, sobre o time, sobre o preço do café! — O pão subiu? — pergunta ele. — O pão não, Seu Joaquim. Subiu o café — responde a Ana, rindo. Às dez, o bolo de milho sai do forno. Quem passa na rua sente o cheiro e entra. O bolo acaba antes do almoço; o pão, antes do jantar. No fim do dia, a Ana fecha o caixa, conta o pão que sobrou e anota tudo no caderno... Amanhã, às seis, o forno acende de novo.
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
palavras = texto.split()
print(f"Caracteres: {len(texto)}")
print(f"Palavras: {len(palavras)}")
Versão 2 · Limpar: minúsculas e sem pontuação
Duas limpezas, antes de separar: tudo em minúsculas, para "Pão" e "pão" virarem a mesma palavra; e cada sinal de pontuação trocado por um espaço, para a vírgula desgrudar da palavra. No fim, o programa confere se sobrou alguma "palavra" com algo que não é letra.
PONTUACAO = '.,;:!?"—'
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
texto = texto.lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
palavras = texto.split()
print(f"Palavras: {len(palavras)}")
print(palavras[:12])
com_sinais = [p for p in palavras if not p.isalpha()]
print(f"Palavras com sinais: {com_sinais}")
- PONTUACAO = '.,;:!?"—'
- Os sinais que aparecem na crônica, num texto só. O último é o travessão dos diálogos (
—), que é diferente do hífen (-). O texto está entre aspas simples porque tem uma aspa dupla dentro. - texto.lower()
- Devolve o texto em minúsculas. Funciona com acentos:
"PÃO"vira"pão". - for sinal in PONTUACAO:
- Um texto também pode ser percorrido com
for, letra por letra (aqui, sinal por sinal). - texto.replace(sinal, " ")
- Troca cada ocorrência do sinal por um espaço. Por que espaço, e não nada? Porque em
"seis.Antes"(sem espaço depois do ponto) as duas palavras grudariam. Osplit()depois ignora os espaços a mais. - [p for p in palavras if not p.isalpha()]
- As palavras que não são feitas só de letras.
isalpha()aceita letras acentuadas:"pão".isalpha()éTrue.
$ python3 contador.py Palavras: 148 ['a', 'padaria', 'pão', 'quente', 'abre', 'às', 'seis', 'antes', 'do', 'sol', 'o', 'pão'] Palavras com sinais: []
Agora são 148: os quatro travessões soltos, que o split() contava como "palavras", sumiram. E a lista de palavras com sinais está vazia: a limpeza pegou tudo. Veja o que acontece quando a lista de sinais esquece o ! e o —:
$ python3 contador.py Palavras: 152 ['a', 'padaria', 'pão', 'quente', 'abre', 'às', 'seis', 'antes', 'do', 'sol', 'o', 'pão'] Palavras com sinais: ['café!', '—', '—', '—', '—']
O 'café!' contaria separado do 'café', e os travessões virariam "palavras". A conferência no fim do programa serve justamente para pegar isso.
string.punctuation?
O módulo string tem uma lista pronta de sinais:
import string print(string.punctuation)
$ python3 pontuacao.py
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
Só que são apenas os sinais do teclado americano: o travessão —, as reticências … e as aspas curvas “ ” não estão lá. E ela tem o hífen e o apóstrofo, que em português às vezes fazem parte da palavra (guarda-chuva, d'água). Por isso o projeto usa a sua própria lista, com os sinais que o texto tem.
Missão: a lista PONTUACAO esqueceu dois sinais, e sobraram "palavras" com sinais. Rode, veja quais são, e acrescente os sinais que faltam até a lista ficar vazia. (Dica: copie o travessão de dentro do próprio texto, na aba cronica.txt.)
PONTUACAO = '.,;:?"'
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
texto = texto.lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
palavras = texto.split()
print(f"Palavras: {len(palavras)}")
print(palavras[:12])
com_sinais = [p for p in palavras if not p.isalpha()]
print(f"Palavras com sinais: {com_sinais}")
A Padaria Pão Quente abre às seis. Antes do sol, o pão já está no forno, e o cheiro de pão quente chega à rua inteira. Dona Rosa é a primeira cliente: pede dez pães, um café e "o de sempre". O café da padaria é forte; o pão, macio. Depois vem o Seu Joaquim. Ele pede café, pão de queijo e conversa. Conversa sobre o tempo, sobre o time, sobre o preço do café! — O pão subiu? — pergunta ele. — O pão não, Seu Joaquim. Subiu o café — responde a Ana, rindo. Às dez, o bolo de milho sai do forno. Quem passa na rua sente o cheiro e entra. O bolo acaba antes do almoço; o pão, antes do jantar. No fim do dia, a Ana fecha o caixa, conta o pão que sobrou e anota tudo no caderno... Amanhã, às seis, o forno acende de novo.
PONTUACAO = '.,;:!?"—'
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
texto = texto.lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
palavras = texto.split()
print(f"Palavras: {len(palavras)}")
print(palavras[:12])
com_sinais = [p for p in palavras if not p.isalpha()]
print(f"Palavras com sinais: {com_sinais}")
Versão 3 · Contar com um dicionário e ordenar
Agora, a contagem. A ideia é a de uma folha com duas colunas, palavra e quantidade: para cada palavra do texto, a Ana procura a linha dela na folha e soma 1; se ainda não tem linha, cria uma com 1. Um dicionário é exatamente essa folha.
PONTUACAO = '.,;:!?"—'
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
texto = texto.lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
palavras = texto.split()
contagem = {}
for palavra in palavras:
contagem[palavra] = contagem.get(palavra, 0) + 1
print(f"Palavras: {len(palavras)}, diferentes: {len(contagem)}")
mais_usadas = sorted(contagem.items(), key=lambda par: par[1], reverse=True)
for palavra, vezes in mais_usadas[:10]:
print(f"{palavra:<10} {vezes:>3}")
- contagem = {}
- A folha começa vazia: um dicionário sem nenhuma chave.
- contagem.get(palavra, 0)
- O número que já está anotado para a palavra, ou 0 se ela ainda não apareceu (aula Dicionários). É o que evita o
KeyError(veja Quando dá errado). - contagem[palavra] = … + 1
- Anota o número novo, criando a chave na primeira vez.
- len(contagem)
- Quantas chaves o dicionário tem, ou seja, quantas palavras diferentes.
- contagem.items()
- Os pares (palavra, vezes), como tuplas.
- key=lambda par: par[1]
- Ordena os pares pelo item 1 de cada um, o número de vezes (aula lambda).
- reverse=True
- Do maior para o menor.
- mais_usadas[:10]
- Só os 10 primeiros pares.
- {palavra:<10} {vezes:>3}
- A palavra à esquerda em 10 posições e o número à direita em 3: as colunas ficam retas.
$ python3 contador.py Palavras: 148, diferentes: 77 o 19 pão 9 do 6 e 5 de 5 café 5 a 4 às 3 antes 3 no 3
Das 148 palavras, 77 são diferentes. A campeã é o, com 19, seguida de pão, com 9. Quando duas palavras empatam (e, de e café, com 5), o sorted as deixa na ordem em que apareceram pela primeira vez no dicionário, que é a ordem do texto.
Repare que pães conta separado de pão: para o programa, são textos diferentes. Juntar plural e singular é bem mais difícil, e fica fora deste projeto.
lower()?
Tirando a linha texto = texto.lower(), as maiúsculas do começo das frases separam as contagens:
$ python3 contador.py Palavras: 148, diferentes: 88 o 15 pão 8 do 6 e 5 de 5 café 5 O 4 forno 3 a 3 sobre 3
São 88 "palavras diferentes" em vez de 77: o (15) e O (4) contaram separado, e o Pão do nome da padaria saiu do pão (8 em vez de 9). Somando, 15 + 4 dá os 19 da versão com lower().
Missão: o sorted está sem critério, e a lista sai em ordem alfabética (a, abre, acaba…). Acrescente o key com uma lambda que pega o número de vezes, e o reverse=True, para aparecerem as 10 mais usadas.
PONTUACAO = '.,;:!?"—'
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
texto = texto.lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
palavras = texto.split()
contagem = {}
for palavra in palavras:
contagem[palavra] = contagem.get(palavra, 0) + 1
print(f"Palavras: {len(palavras)}, diferentes: {len(contagem)}")
mais_usadas = sorted(contagem.items())
for palavra, vezes in mais_usadas[:10]:
print(f"{palavra:<10} {vezes:>3}")
A Padaria Pão Quente abre às seis. Antes do sol, o pão já está no forno, e o cheiro de pão quente chega à rua inteira. Dona Rosa é a primeira cliente: pede dez pães, um café e "o de sempre". O café da padaria é forte; o pão, macio. Depois vem o Seu Joaquim. Ele pede café, pão de queijo e conversa. Conversa sobre o tempo, sobre o time, sobre o preço do café! — O pão subiu? — pergunta ele. — O pão não, Seu Joaquim. Subiu o café — responde a Ana, rindo. Às dez, o bolo de milho sai do forno. Quem passa na rua sente o cheiro e entra. O bolo acaba antes do almoço; o pão, antes do jantar. No fim do dia, a Ana fecha o caixa, conta o pão que sobrou e anota tudo no caderno... Amanhã, às seis, o forno acende de novo.
PONTUACAO = '.,;:!?"—'
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
texto = texto.lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
palavras = texto.split()
contagem = {}
for palavra in palavras:
contagem[palavra] = contagem.get(palavra, 0) + 1
print(f"Palavras: {len(palavras)}, diferentes: {len(contagem)}")
mais_usadas = sorted(contagem.items(), key=lambda par: par[1], reverse=True)
for palavra, vezes in mais_usadas[:10]:
print(f"{palavra:<10} {vezes:>3}")
Versão 4 · Counter, palavras comuns e barras
A versão 3 funciona, mas o pódio é das palavras pequenas: o, do, e, de. Elas aparecem em qualquer texto e não dizem nada sobre ele. A versão final ignora essas palavras, troca a contagem manual pelo Counter, que já faz isso pronto, e desenha uma barra para cada palavra.
from collections import Counter
PONTUACAO = '.,;:!?"—'
IGNORAR = {
"a", "o", "as", "os", "e", "é", "de", "do", "da", "no", "na",
"à", "às", "um", "uma", "que", "ele", "sobre", "antes",
}
def ler_palavras(nome_arquivo):
with open(nome_arquivo, encoding="utf-8") as arquivo:
texto = arquivo.read().lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
return texto.split()
palavras = ler_palavras("cronica.txt")
importantes = [p for p in palavras if p not in IGNORAR]
contagem = Counter(importantes)
print(f"Palavras: {len(palavras)}, sem as comuns: {len(importantes)}")
print("As 10 mais usadas:")
for palavra, vezes in contagem.most_common(10):
print(f"{palavra:<10} {vezes:>3} {'#' * vezes}")
- from collections import Counter
- Traz o
Counter("contador") do módulocollections, que vem com o Python (aula O Módulo collections). - IGNORAR = {…}
- Um conjunto com as palavras comuns (aula Conjuntos). Perguntar
p not in IGNORARnum conjunto é rápido, mesmo com milhares de palavras. A lista foi escolhida olhando a saída da versão 3; para outro texto, você ajustaria. - def ler_palavras(nome_arquivo): … return texto.split()
- A leitura e a limpeza das versões 1 e 2, agora numa função que devolve a lista pronta.
- [p for p in palavras if p not in IGNORAR]
- Uma lista nova, só com as palavras que interessam (aula List Comprehension).
- Counter(importantes)
- Conta tudo de uma vez: é um dicionário palavra → vezes, igual ao da versão 3.
- contagem.most_common(10)
- "As mais comuns": os 10 pares com mais vezes, já em ordem. Faz o trabalho do
sortedcomlambdae da fatia[:10]. - '#' * vezes
- Repete o
#tantas vezes quanto a palavra apareceu: a barra (aula Juntar e Repetir Textos). Dentro da f-string, o'#'usa aspas simples porque a f-string usa aspas duplas.
$ python3 contador.py Palavras: 148, sem as comuns: 88 As 10 mais usadas: pão 9 ######### café 5 ##### forno 3 ### padaria 2 ## quente 2 ## seis 2 ## cheiro 2 ## rua 2 ## pede 2 ## dez 2 ##
Tirando as palavras comuns, sobraram 88 das 148, e o pódio agora conta a história do texto: pão, café, forno. A partir do quarto lugar, há várias palavras empatadas com 2; o most_common as mostra na ordem em que apareceram primeiro no texto.
Missão: no fim do programa, mostre também a palavra mais longa (sem as comuns), no formato Mais longa: primeira (8 letras). Dica: max(contagem, key=len) devolve a chave com mais letras; num empate, a primeira que apareceu.
from collections import Counter
PONTUACAO = '.,;:!?"—'
IGNORAR = {
"a", "o", "as", "os", "e", "é", "de", "do", "da", "no", "na",
"à", "às", "um", "uma", "que", "ele", "sobre", "antes",
}
def ler_palavras(nome_arquivo):
with open(nome_arquivo, encoding="utf-8") as arquivo:
texto = arquivo.read().lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
return texto.split()
palavras = ler_palavras("cronica.txt")
importantes = [p for p in palavras if p not in IGNORAR]
contagem = Counter(importantes)
print(f"Palavras: {len(palavras)}, sem as comuns: {len(importantes)}")
print("As 10 mais usadas:")
for palavra, vezes in contagem.most_common(10):
print(f"{palavra:<10} {vezes:>3} {'#' * vezes}")
A Padaria Pão Quente abre às seis. Antes do sol, o pão já está no forno, e o cheiro de pão quente chega à rua inteira. Dona Rosa é a primeira cliente: pede dez pães, um café e "o de sempre". O café da padaria é forte; o pão, macio. Depois vem o Seu Joaquim. Ele pede café, pão de queijo e conversa. Conversa sobre o tempo, sobre o time, sobre o preço do café! — O pão subiu? — pergunta ele. — O pão não, Seu Joaquim. Subiu o café — responde a Ana, rindo. Às dez, o bolo de milho sai do forno. Quem passa na rua sente o cheiro e entra. O bolo acaba antes do almoço; o pão, antes do jantar. No fim do dia, a Ana fecha o caixa, conta o pão que sobrou e anota tudo no caderno... Amanhã, às seis, o forno acende de novo.
from collections import Counter
PONTUACAO = '.,;:!?"—'
IGNORAR = {
"a", "o", "as", "os", "e", "é", "de", "do", "da", "no", "na",
"à", "às", "um", "uma", "que", "ele", "sobre", "antes",
}
def ler_palavras(nome_arquivo):
with open(nome_arquivo, encoding="utf-8") as arquivo:
texto = arquivo.read().lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
return texto.split()
palavras = ler_palavras("cronica.txt")
importantes = [p for p in palavras if p not in IGNORAR]
contagem = Counter(importantes)
print(f"Palavras: {len(palavras)}, sem as comuns: {len(importantes)}")
print("As 10 mais usadas:")
for palavra, vezes in contagem.most_common(10):
print(f"{palavra:<10} {vezes:>3} {'#' * vezes}")
mais_longa = max(contagem, key=len)
print(f"Mais longa: {mais_longa} ({len(mais_longa)} letras)")
Formas diferentes de limpar e de contar
Limpar a pontuação
Três jeitos que dão as mesmas 148 palavras com esta crônica:
import re
PONTUACAO = '.,;:!?"—'
with open("cronica.txt", encoding="utf-8") as arquivo:
texto = arquivo.read().lower()
limpo1 = texto
for sinal in PONTUACAO:
limpo1 = limpo1.replace(sinal, " ")
tabela = str.maketrans(PONTUACAO, " " * len(PONTUACAO))
limpo2 = texto.translate(tabela)
palavras3 = re.findall(r"\w+", texto)
print(len(limpo1.split()), len(limpo2.split()), len(palavras3))
print(limpo1.split() == limpo2.split() == palavras3)
$ python3 limpar.py 148 148 148 True
| Forma | Quando usar |
|---|---|
for sinal in PONTUACAO: texto = texto.replace(sinal, " ") | A mais fácil de ler, e a do projeto. Para textos do tamanho de uma crônica, a velocidade não importa |
str.maketrans + translate | Troca todos os sinais numa passada só, com uma "tabela de troca". Mais rápida para textos enormes |
re.findall(r"\w+", texto) | Em vez de tirar o que não é palavra, pega o que é: sequências de letras e algarismos, com acento. Curta e poderosa, mas exige saber expressões regulares (aula Expressões Regulares) |
Contar
from collections import Counter
palavras = ["o", "pão", "o", "café", "pão", "o"]
contagem1 = {}
for palavra in palavras:
contagem1[palavra] = contagem1.get(palavra, 0) + 1
contagem2 = Counter(palavras)
print(contagem1)
print(contagem2)
print(contagem2.most_common(2))
print(contagem1.get("bolo", 0), contagem2["bolo"])
$ python3 contar.py
{'o': 3, 'pão': 2, 'café': 1}
Counter({'o': 3, 'pão': 2, 'café': 1})
[('o', 3), ('pão', 2)]
0 0
| Forma | Quando usar |
|---|---|
Dicionário com get(palavra, 0) + 1 | Para entender como a contagem funciona, e quando não dá para importar nada |
Dicionário com if palavra in contagem: … else: … | Faz o mesmo, em mais linhas. Comum em código antigo |
Counter(lista) | Nos programas de verdade. Conta numa linha, tem o most_common e devolve 0 para uma palavra que não apareceu (veja o desafio) |
Quando dá errado
with open("cronicas.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
$ python3 nome_errado.py
Traceback (most recent call last):
File "/home/ana/nome_errado.py", line 1, in <module>
with open("cronicas.txt", encoding="utf-8") as arquivo:
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'cronicas.txt'
"Arquivo não encontrado: não existe o arquivo ou a pasta 'cronicas.txt'". O arquivo se chama cronica.txt, sem o s. O mesmo erro aparece quando o nome está certo, mas você rodou o programa de outra pasta: o open procura o arquivo na pasta onde o terminal está. Como consertar: confira o nome com ls e rode o programa da pasta onde estão os dois arquivos.
palavras = ["o", "pão", "o", "café"]
contagem = {}
for palavra in palavras:
contagem[palavra] += 1
print(contagem)
$ python3 keyerror.py
Traceback (most recent call last):
File "/home/ana/keyerror.py", line 4, in <module>
contagem[palavra] += 1
~~~~~~~~^^^^^^^^^
KeyError: 'o'
KeyError: 'o': "erro de chave". O += 1 precisa ler o valor atual antes de somar, e na primeira vez que o o aparece não há valor nenhum para ler. Como consertar: contagem[palavra] = contagem.get(palavra, 0) + 1, ou o if palavra in contagem, ou o Counter.
split(" ") no lugar de split()
texto = "o pão quente\nchega"
print(texto.split())
print(texto.split(" "))
$ python3 split_espaco.py ['o', 'pão', 'quente', 'chega'] ['o', 'pão', '', 'quente\nchega']
Sem nada nos parênteses, o split() corta em qualquer espaço em branco (espaço, quebra de linha, tabulação) e junta os repetidos. Com " ", ele corta só no espaço, um por um: os dois espaços seguidos geram uma "palavra" vazia (''), e a quebra de linha deixa 'quente\nchega' grudado. Seria a palavra "vazia" mais contada da crônica. Como consertar: split(), sem argumento.
Arquivos antigos, de programas do Windows, às vezes não estão em UTF-8, e sim em Latin-1. A Ana gravou um assim, de propósito, para ver o que acontece:
with open("antigo.txt", "w", encoding="latin-1") as arquivo:
arquivo.write("Pão de queijo e café\n")
with open("antigo.txt", encoding="utf-8") as arquivo:
texto = arquivo.read()
print(texto)
$ python3 latin1_criar.py
$ python3 latin1_ler.py
Traceback (most recent call last):
File "/home/ana/latin1_ler.py", line 2, in <module>
texto = arquivo.read()
File "<frozen codecs>", line 325, in decode
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe3 in position 1: invalid continuation byte
"O decodificador UTF-8 não consegue entender o byte 0xe3 na posição 1". A posição 1 é o ã de "Pão": em Latin-1 ele é gravado como um byte só (0xe3), que não forma uma letra válida em UTF-8. O trecho <frozen codecs> é código de dentro do próprio Python; o que importa é a linha 2 do seu programa, o read(). Como consertar: abra com a codificação certa, encoding="latin-1":
with open("antigo.txt", encoding="latin-1") as arquivo:
texto = arquivo.read()
print(texto)
$ python3 latin1_ok.py Pão de queijo e café
(A linha em branco no fim é o \n do arquivo mais a quebra de linha do próprio print.) Por isso o projeto sempre escreve o encoding="utf-8": deixa claro o que o programa espera, em vez de depender da configuração do computador.
Não dá erro: dá uma contagem errada. Sem o lower(), O e o contam separado (o quadro da versão 3); sem a pontuação certa, café! e café também (a conferência da versão 2). Sempre que uma contagem parecer baixa demais, olhe a lista de palavras antes de contar.
Desafio extra: perguntar por uma palavra
A Ana quer digitar uma palavra e saber quantas vezes ela aparece. Com o Counter fica fácil, porque ele responde 0 para uma palavra que não está no texto, em vez de dar KeyError como um dicionário comum:
from collections import Counter
PONTUACAO = '.,;:!?"—'
def ler_palavras(nome_arquivo):
with open(nome_arquivo, encoding="utf-8") as arquivo:
texto = arquivo.read().lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
return texto.split()
contagem = Counter(ler_palavras("cronica.txt"))
procurada = input("Qual palavra? ").strip().lower()
print(f"{procurada}: {contagem[procurada]} vez(es)")
$ python3 contador.py Qual palavra? café café: 5 vez(es) $ python3 contador.py Qual palavra? Pão « com espaços e maiúscula pão: 9 vez(es) $ python3 contador.py Qual palavra? biscoito biscoito: 0 vez(es)
O .strip().lower() limpa o que foi digitado do mesmo jeito que o texto foi limpo. Sem ele, Pão não seria encontrado, porque todas as chaves estão em minúsculas.
Missão: a caixa vai digitar Pão, com P maiúsculo, e o programa responde Pão: 0 vez(es). Limpe o que foi digitado para que a resposta seja pão: 9 vez(es).
from collections import Counter
PONTUACAO = '.,;:!?"—'
def ler_palavras(nome_arquivo):
with open(nome_arquivo, encoding="utf-8") as arquivo:
texto = arquivo.read().lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
return texto.split()
contagem = Counter(ler_palavras("cronica.txt"))
procurada = input("Qual palavra? ")
print(f"{procurada}: {contagem[procurada]} vez(es)")
A Padaria Pão Quente abre às seis. Antes do sol, o pão já está no forno, e o cheiro de pão quente chega à rua inteira. Dona Rosa é a primeira cliente: pede dez pães, um café e "o de sempre". O café da padaria é forte; o pão, macio. Depois vem o Seu Joaquim. Ele pede café, pão de queijo e conversa. Conversa sobre o tempo, sobre o time, sobre o preço do café! — O pão subiu? — pergunta ele. — O pão não, Seu Joaquim. Subiu o café — responde a Ana, rindo. Às dez, o bolo de milho sai do forno. Quem passa na rua sente o cheiro e entra. O bolo acaba antes do almoço; o pão, antes do jantar. No fim do dia, a Ana fecha o caixa, conta o pão que sobrou e anota tudo no caderno... Amanhã, às seis, o forno acende de novo.
from collections import Counter
PONTUACAO = '.,;:!?"—'
def ler_palavras(nome_arquivo):
with open(nome_arquivo, encoding="utf-8") as arquivo:
texto = arquivo.read().lower()
for sinal in PONTUACAO:
texto = texto.replace(sinal, " ")
return texto.split()
contagem = Counter(ler_palavras("cronica.txt"))
procurada = input("Qual palavra? ").strip().lower()
print(f"{procurada}: {contagem[procurada]} vez(es)")
Referência rápida do projeto
| Escrita | Para que serviu |
|---|---|
with open(nome, encoding="utf-8") as arquivo: + arquivo.read() | Ler o arquivo inteiro como um texto só |
texto.lower() | Tudo em minúsculas, para Pão e pão contarem juntos |
texto.replace(sinal, " ") | Desgrudar a pontuação das palavras |
texto.split() | Separar as palavras (em qualquer espaço em branco) |
contagem.get(p, 0) + 1 | Contar num dicionário sem KeyError |
sorted(d.items(), key=lambda par: par[1], reverse=True) | Ordenar os pares pelo número de vezes |
Counter(lista) / .most_common(10) | Contar numa linha / as 10 mais usadas |
[p for p in palavras if p not in IGNORAR] | Tirar as palavras comuns |
f"{palavra:<10} {vezes:>3} {'#' * vezes}" | Colunas alinhadas e a barra |
| Sintoma | Causa provável |
|---|---|
FileNotFoundError: … 'cronica.txt' | Nome errado, ou o programa rodou de outra pasta |
KeyError: 'o' | contagem[p] += 1 sem a chave existir: use get ou Counter |
UnicodeDecodeError: 'utf-8' codec can't decode byte … | O arquivo não está em UTF-8: tente encoding="latin-1" |
O e o contados separado | Faltou o lower() |
café! e café contados separado | Faltou um sinal na lista de pontuação |
Uma "palavra" vazia ('') na contagem | split(" ") no lugar de split() |
O pódio é o, de, e | Faltou ignorar as palavras comuns |
Exercícios
1. Separar as palavras
Complete o método que corta o texto nos espaços:
2. Contar sem KeyError
Complete o valor reserva do get:
3. As cinco mais
Complete para pegar as 5 palavras mais usadas de um Counter:
4. Por que a pontuação é trocada por um espaço, e não apagada?
5. Sem o lower(), a crônica deu 88 palavras diferentes; com ele, 77. Por quê?
6. Com contagem = Counter(palavras), o que dá contagem["biscoito"], se "biscoito" não aparece no texto?
Resumo
- Leia o arquivo com
with open(…, encoding="utf-8")eread(); separe as palavras comsplit(), sem argumento. - Antes de contar, limpe:
lower()para as maiúsculas,replace(por espaço) para a pontuação. Confira o resultado: palavra com sinal, ou vazia, é sinal de limpeza incompleta. - Um dicionário conta com
contagem.get(p, 0) + 1; ordene os pares comsorted(…, key=lambda par: par[1], reverse=True). - O
Counterfaz tudo isso numa linha, tem omost_common(n)e devolve 0 para o que não apareceu. - Palavras comuns (
o,de,e) enchem o pódio: guarde-as num conjunto e filtre com uma list comprehension. FileNotFoundErroré nome ou pasta errada;UnicodeDecodeErroré arquivo em outra codificação;KeyErroré chave que ainda não existe.