Python Projeto: Notas da Turma

A professora da escola do bairro guarda as notas da turma numa planilha e, todo bimestre, faz as contas à mão: a média de cada aluno, quem passou, quem vai para a recuperação. A Ana se ofereceu para automatizar isso. A planilha é exportada como CSV (um arquivo de texto com os valores separados por vírgulas, aula Arquivos CSV), e o programa vai ler esse arquivo, calcular tudo, mostrar um relatório arrumado e gravar o resultado num CSV novo, pronto para abrir na planilha. Quatro versões, uma novidade por vez.

Neste projeto você vai usar

O que vamos construir

A pasta do projeto tem o arquivo de notas que a professora exportou:

nome,turma,nota1,nota2,nota3
Bia,A,8.0,9.5,7.0
Caio,B,6.0,5.5,5.0
Duda,A,9.0,10.0,9.5
Enzo,B,7.0,7.0,7.0
Gabi,A,4.5,6.0,5.5
Hugo,B,10.0,8.5,9.0

A primeira linha é o cabeçalho: o nome de cada coluna. Cada linha seguinte é um aluno, com a turma e as três notas do bimestre. A regra da escola: média 7 ou mais, aprovado; abaixo de 7, recuperação. Esta é a versão final rodando no computador da Ana:

$ python3 notas.py
Aluno    Turma   Média  Situação
------------------------------------
Duda       A      9.50  aprovado
Hugo       B      9.17  aprovado
Bia        A      8.17  aprovado
Enzo       B      7.00  aprovado
Caio       B      5.50  recuperação
Gabi       A      5.33  recuperação
------------------------------------
Média da turma: 7.44
Aprovados: 4 de 6
Gravei resultado.csv
$ cat resultado.csv
nome,turma,media,situacao
Duda,A,9.50,aprovado
Hugo,B,9.17,aprovado
Bia,A,8.17,aprovado
Enzo,B,7.00,aprovado
Caio,B,5.50,recuperação
Gabi,A,5.33,recuperação

(O cat mostra o conteúdo de um arquivo de texto no terminal.) As quatro versões:

  1. Ler o CSV com csv.DictReader.
  2. Média e situação, em funções.
  3. O relatório, alinhado e ordenado pela média.
  4. Gravar o resultado.csv com csv.DictWriter.

Antes de começar

Crie a pasta do projeto, entre nela e salve lá o notas.csv de cima (num editor de texto, ou exportando da planilha como "CSV separado por vírgulas"). O programa vai se chamar notas.py, na mesma pasta:

$ mkdir -p projetos/notas
$ cd projetos/notas

As caixas Experimente deste projeto têm duas abas: o programa (main.py) e o notas.csv, que fica na mesma pasta do programa. Você pode mexer nas notas da aba do CSV e rodar de novo para ver o resultado mudar.


Versão 1 · Ler o CSV

import csv

with open("notas.csv", newline="", encoding="utf-8") as arquivo:
    alunos = list(csv.DictReader(arquivo))

print(f"{len(alunos)} alunos lidos")
print(alunos[0])
for aluno in alunos:
    print(aluno["nome"], aluno["turma"])
import csv
O módulo da biblioteca padrão que entende arquivos CSV: separa as colunas, lida com aspas e vírgulas dentro dos valores.
open("notas.csv", newline="", encoding="utf-8")
Abre o arquivo para leitura. O newline="" é a recomendação da documentação para arquivos CSV (o módulo csv cuida das quebras de linha sozinho), e o encoding="utf-8" garante que os acentos sejam lidos certo. A aula Arquivos CSV explica os dois.
csv.DictReader(arquivo)
Lê cada linha como um dicionário, usando o cabeçalho como chaves. O cabeçalho em si não vira aluno.
list(…)
Junta todos os dicionários numa lista, para usar depois que o with fechar o arquivo.
aluno["nome"]
Cada valor se pega pelo nome da coluna, e não pela posição. Se a professora mudar a ordem das colunas, o programa continua certo.
$ python3 notas.py
6 alunos lidos
{'nome': 'Bia', 'turma': 'A', 'nota1': '8.0', 'nota2': '9.5', 'nota3': '7.0'}
Bia A
Caio B
Duda A
Enzo B
Gabi A
Hugo B

Olhe bem o dicionário da Bia: 'nota1': '8.0', com aspas. Tudo o que vem de um CSV é texto, inclusive as notas. Para fazer conta, a versão 2 vai precisar converter com float(). Esquecer isso é o erro mais comum deste projeto (veja Quando dá errado).

Missão: em vez de só o nome, faça o programa mostrar uma frase por aluno, como Caio está na turma B, usando as colunas nome e turma.

import csv

with open("notas.csv", newline="", encoding="utf-8") as arquivo:
    alunos = list(csv.DictReader(arquivo))

for aluno in alunos:
    print(aluno["nome"])
nome,turma,nota1,nota2,nota3
Bia,A,8.0,9.5,7.0
Caio,B,6.0,5.5,5.0
Duda,A,9.0,10.0,9.5
Enzo,B,7.0,7.0,7.0
Gabi,A,4.5,6.0,5.5
Hugo,B,10.0,8.5,9.0
import csv

with open("notas.csv", newline="", encoding="utf-8") as arquivo:
    alunos = list(csv.DictReader(arquivo))

for aluno in alunos:
    print(f"{aluno['nome']} está na turma {aluno['turma']}")

Versão 2 · Média e situação

Agora as contas. Cada tarefa vira uma função com um nome claro: uma lê o arquivo e já converte as notas, outra calcula a média, outra decide a situação.

import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    if m >= 7:
        return "aprovado"
    return "recuperação"


for aluno in ler_alunos("notas.csv"):
    m = media(aluno["notas"])
    print(f"{aluno['nome']}: {m:.2f} {situacao(m)}")
def ler_alunos(caminho):
Lê o CSV e devolve uma lista de alunos já "limpos": cada um é um dicionário com nome, turma e notas, uma lista de números.
float(linha["nota1"])
Converte o texto '8.0' no número 8.0. A conversão é feita uma vez, na leitura; o resto do programa já recebe números.
sum(notas) / len(notas)
A média: a soma das notas dividida pela quantidade (aula Funções Embutidas). Se um dia houver quatro notas, a conta continua certa.
if m >= 7: return "aprovado"
A regra da escola. Repare no >=: com 7,00 exato, o aluno passa.
{m:.2f}
A média com duas casas: 8.1666… aparece como 8.17. O valor guardado não muda; só o jeito de mostrar.
$ python3 notas.py
Bia: 8.17 aprovado
Caio: 5.50 recuperação
Duda: 9.50 aprovado
Enzo: 7.00 aprovado                « 7,00 exato: passou, por causa do >=
Gabi: 5.33 recuperação
Hugo: 9.17 aprovado

Missão: a função situacao ainda devolve "?" para todo mundo. Escreva a regra: "aprovado" se a média for 7 ou mais; senão, "recuperação". Atenção ao Enzo, que tem 7,00 exato: ele deve aparecer como Enzo: 7.00 aprovado.

import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    return "?"  # troque: "aprovado" se m for 7 ou mais; senão, "recuperação"


for aluno in ler_alunos("notas.csv"):
    m = media(aluno["notas"])
    print(f"{aluno['nome']}: {m:.2f} {situacao(m)}")
nome,turma,nota1,nota2,nota3
Bia,A,8.0,9.5,7.0
Caio,B,6.0,5.5,5.0
Duda,A,9.0,10.0,9.5
Enzo,B,7.0,7.0,7.0
Gabi,A,4.5,6.0,5.5
Hugo,B,10.0,8.5,9.0
import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    if m >= 7:
        return "aprovado"
    return "recuperação"


for aluno in ler_alunos("notas.csv"):
    m = media(aluno["notas"])
    print(f"{aluno['nome']}: {m:.2f} {situacao(m)}")

Versão 3 · O relatório

A professora quer imprimir uma tabela: os alunos do melhor para o pior, com as colunas alinhadas, e um resumo da turma no fim. A média e a situação passam a ser guardadas em cada aluno, para o relatório e a ordenação usarem.

import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    if m >= 7:
        return "aprovado"
    return "recuperação"


def relatorio(alunos):
    print(f"{'Aluno':<8}{'Turma':^7}{'Média':>7}  Situação")
    print("-" * 36)
    for aluno in alunos:
        print(f"{aluno['nome']:<8}{aluno['turma']:^7}{aluno['media']:>7.2f}  {aluno['situacao']}")
    print("-" * 36)
    aprovados = [a for a in alunos if a["situacao"] == "aprovado"]
    media_turma = sum(a["media"] for a in alunos) / len(alunos)
    print(f"Média da turma: {media_turma:.2f}")
    print(f"Aprovados: {len(aprovados)} de {len(alunos)}")


alunos = ler_alunos("notas.csv")
for aluno in alunos:
    aluno["media"] = media(aluno["notas"])
    aluno["situacao"] = situacao(aluno["media"])

alunos.sort(key=lambda a: a["media"], reverse=True)
relatorio(alunos)

As partes novas, uma por uma

aluno["media"] = media(aluno["notas"])
Acrescenta duas chaves em cada dicionário de aluno, media e situacao, calculadas uma vez só.
alunos.sort(key=lambda a: a["media"], reverse=True)
Ordena a lista pela média. O key diz o que comparar em cada aluno (uma lambda, função de uma linha, aula lambda), e o reverse=True põe a maior primeiro (aula Ordenando Listas).
{'Aluno':<8}
O texto Aluno alinhado à esquerda num espaço de 8 caracteres. O que sobra vira espaço em branco.
{'Turma':^7}
Centralizado em 7 caracteres.
{aluno['media']:>7.2f}
O número alinhado à direita em 7 caracteres, com duas casas. Números alinhados à direita deixam as vírgulas uma embaixo da outra.
"-" * 36
Repete o traço 36 vezes: a linha da tabela (aula Juntar e Repetir Textos).
[a for a in alunos if a["situacao"] == "aprovado"]
Uma lista só com os aprovados (aula List Comprehension); o len dela conta quantos são.
sum(a["media"] for a in alunos)
Soma as médias de todos os alunos, sem criar uma lista no meio.
$ python3 notas.py
Aluno    Turma   Média  Situação
------------------------------------
Duda       A      9.50  aprovado
Hugo       B      9.17  aprovado
Bia        A      8.17  aprovado
Enzo       B      7.00  aprovado
Caio       B      5.50  recuperação
Gabi       A      5.33  recuperação
------------------------------------
Média da turma: 7.44
Aprovados: 4 de 6

As colunas ficaram alinhadas porque cada campo ocupa sempre o mesmo número de caracteres, seja qual for o nome. (Num nome com mais de 8 letras, o alinhamento quebraria: o f-string nunca corta um texto, só completa os que são menores.)

Missão: o relatório está saindo na ordem do arquivo. Na linha marcada, ordene a lista alunos da maior média para a menor, para a Duda aparecer primeiro e a Gabi por último.

import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    if m >= 7:
        return "aprovado"
    return "recuperação"


def relatorio(alunos):
    print(f"{'Aluno':<8}{'Turma':^7}{'Média':>7}  Situação")
    print("-" * 36)
    for aluno in alunos:
        print(f"{aluno['nome']:<8}{aluno['turma']:^7}{aluno['media']:>7.2f}  {aluno['situacao']}")
    print("-" * 36)
    aprovados = [a for a in alunos if a["situacao"] == "aprovado"]
    media_turma = sum(a["media"] for a in alunos) / len(alunos)
    print(f"Média da turma: {media_turma:.2f}")
    print(f"Aprovados: {len(aprovados)} de {len(alunos)}")


alunos = ler_alunos("notas.csv")
for aluno in alunos:
    aluno["media"] = media(aluno["notas"])
    aluno["situacao"] = situacao(aluno["media"])

# ordene aqui os alunos, da maior média para a menor
relatorio(alunos)
nome,turma,nota1,nota2,nota3
Bia,A,8.0,9.5,7.0
Caio,B,6.0,5.5,5.0
Duda,A,9.0,10.0,9.5
Enzo,B,7.0,7.0,7.0
Gabi,A,4.5,6.0,5.5
Hugo,B,10.0,8.5,9.0
import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    if m >= 7:
        return "aprovado"
    return "recuperação"


def relatorio(alunos):
    print(f"{'Aluno':<8}{'Turma':^7}{'Média':>7}  Situação")
    print("-" * 36)
    for aluno in alunos:
        print(f"{aluno['nome']:<8}{aluno['turma']:^7}{aluno['media']:>7.2f}  {aluno['situacao']}")
    print("-" * 36)
    aprovados = [a for a in alunos if a["situacao"] == "aprovado"]
    media_turma = sum(a["media"] for a in alunos) / len(alunos)
    print(f"Média da turma: {media_turma:.2f}")
    print(f"Aprovados: {len(aprovados)} de {len(alunos)}")


alunos = ler_alunos("notas.csv")
for aluno in alunos:
    aluno["media"] = media(aluno["notas"])
    aluno["situacao"] = situacao(aluno["media"])

alunos.sort(key=lambda a: a["media"], reverse=True)
relatorio(alunos)

Dá para fazer com alunos.sort(…), que ordena a própria lista, ou com alunos = sorted(alunos, …), que cria uma lista nova ordenada. Os dois aceitam key e reverse.


Versão 4 · Gravar o resultado.csv

Por fim, o resultado vai para um arquivo que a professora abre na planilha. Uma função nova, gravar_resultado, usa o csv.DictWriter, o caminho de volta do DictReader:

import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    if m >= 7:
        return "aprovado"
    return "recuperação"


def relatorio(alunos):
    print(f"{'Aluno':<8}{'Turma':^7}{'Média':>7}  Situação")
    print("-" * 36)
    for aluno in alunos:
        print(f"{aluno['nome']:<8}{aluno['turma']:^7}{aluno['media']:>7.2f}  {aluno['situacao']}")
    print("-" * 36)
    aprovados = [a for a in alunos if a["situacao"] == "aprovado"]
    media_turma = sum(a["media"] for a in alunos) / len(alunos)
    print(f"Média da turma: {media_turma:.2f}")
    print(f"Aprovados: {len(aprovados)} de {len(alunos)}")


def gravar_resultado(alunos, caminho):
    with open(caminho, "w", newline="", encoding="utf-8") as arquivo:
        escritor = csv.DictWriter(arquivo, fieldnames=["nome", "turma", "media", "situacao"])
        escritor.writeheader()
        for aluno in alunos:
            escritor.writerow({
                "nome": aluno["nome"],
                "turma": aluno["turma"],
                "media": f"{aluno['media']:.2f}",
                "situacao": aluno["situacao"],
            })


alunos = ler_alunos("notas.csv")
for aluno in alunos:
    aluno["media"] = media(aluno["notas"])
    aluno["situacao"] = situacao(aluno["media"])

alunos.sort(key=lambda a: a["media"], reverse=True)
relatorio(alunos)
gravar_resultado(alunos, "resultado.csv")
print("Gravei resultado.csv")
open(caminho, "w", newline="", encoding="utf-8")
Abre para escrever ("w", de write). Se o arquivo não existe, é criado; se existe, é apagado e reescrito (aula Escrevendo Arquivos).
csv.DictWriter(arquivo, fieldnames=[…])
Um escritor que recebe dicionários. O fieldnames diz quais colunas, e em que ordem.
escritor.writeheader()
Escreve a linha do cabeçalho, com os nomes das colunas.
escritor.writerow({…})
Escreve uma linha. As chaves do dicionário têm de ser exatamente os fieldnames.
f"{aluno['media']:.2f}"
A média vai para o arquivo como texto com duas casas, 9.50, e não 9.5 nem 9.166666666666666.
"notas" não está na lista
O dicionário de cada aluno tem a chave notas, mas ela não vai para o arquivo: o writerow recebe um dicionário novo, só com as quatro colunas.
$ python3 notas.py
Aluno    Turma   Média  Situação
------------------------------------
Duda       A      9.50  aprovado
Hugo       B      9.17  aprovado
Bia        A      8.17  aprovado
Enzo       B      7.00  aprovado
Caio       B      5.50  recuperação
Gabi       A      5.33  recuperação
------------------------------------
Média da turma: 7.44
Aprovados: 4 de 6
Gravei resultado.csv
$ cat resultado.csv
nome,turma,media,situacao
Duda,A,9.50,aprovado
Hugo,B,9.17,aprovado
Bia,A,8.17,aprovado
Enzo,B,7.00,aprovado
Caio,B,5.50,recuperação
Gabi,A,5.33,recuperação
O fim de linha do CSV

Por padrão, o módulo csv termina cada linha com \r\n (o fim de linha do Windows), como pede a especificação dos arquivos CSV. No terminal isso não aparece, e as planilhas leem sem problema. É por isso que o newline="" é importante também na gravação: sem ele, em alguns sistemas, cada linha ganharia uma quebra a mais.

Missão: a função gravar_resultado ainda está vazia, e o programa diz "Gravei resultado.csv" sem gravar nada (mentindo!). Escreva o corpo dela com csv.DictWriter: o cabeçalho nome,turma,media,situacao e uma linha por aluno, com a média em duas casas, como Caio,B,5.50,recuperação.

import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    if m >= 7:
        return "aprovado"
    return "recuperação"


def relatorio(alunos):
    print(f"{'Aluno':<8}{'Turma':^7}{'Média':>7}  Situação")
    print("-" * 36)
    for aluno in alunos:
        print(f"{aluno['nome']:<8}{aluno['turma']:^7}{aluno['media']:>7.2f}  {aluno['situacao']}")
    print("-" * 36)
    aprovados = [a for a in alunos if a["situacao"] == "aprovado"]
    media_turma = sum(a["media"] for a in alunos) / len(alunos)
    print(f"Média da turma: {media_turma:.2f}")
    print(f"Aprovados: {len(aprovados)} de {len(alunos)}")


def gravar_resultado(alunos, caminho):
    pass  # troque: grave nome, turma, media (com 2 casas) e situacao com csv.DictWriter


alunos = ler_alunos("notas.csv")
for aluno in alunos:
    aluno["media"] = media(aluno["notas"])
    aluno["situacao"] = situacao(aluno["media"])

alunos.sort(key=lambda a: a["media"], reverse=True)
relatorio(alunos)
gravar_resultado(alunos, "resultado.csv")
print("Gravei resultado.csv")
nome,turma,nota1,nota2,nota3
Bia,A,8.0,9.5,7.0
Caio,B,6.0,5.5,5.0
Duda,A,9.0,10.0,9.5
Enzo,B,7.0,7.0,7.0
Gabi,A,4.5,6.0,5.5
Hugo,B,10.0,8.5,9.0
import csv


def ler_alunos(caminho):
    alunos = []
    with open(caminho, newline="", encoding="utf-8") as arquivo:
        for linha in csv.DictReader(arquivo):
            notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
            alunos.append({"nome": linha["nome"], "turma": linha["turma"], "notas": notas})
    return alunos


def media(notas):
    return sum(notas) / len(notas)


def situacao(m):
    if m >= 7:
        return "aprovado"
    return "recuperação"


def relatorio(alunos):
    print(f"{'Aluno':<8}{'Turma':^7}{'Média':>7}  Situação")
    print("-" * 36)
    for aluno in alunos:
        print(f"{aluno['nome']:<8}{aluno['turma']:^7}{aluno['media']:>7.2f}  {aluno['situacao']}")
    print("-" * 36)
    aprovados = [a for a in alunos if a["situacao"] == "aprovado"]
    media_turma = sum(a["media"] for a in alunos) / len(alunos)
    print(f"Média da turma: {media_turma:.2f}")
    print(f"Aprovados: {len(aprovados)} de {len(alunos)}")


def gravar_resultado(alunos, caminho):
    with open(caminho, "w", newline="", encoding="utf-8") as arquivo:
        escritor = csv.DictWriter(arquivo, fieldnames=["nome", "turma", "media", "situacao"])
        escritor.writeheader()
        for aluno in alunos:
            escritor.writerow({
                "nome": aluno["nome"],
                "turma": aluno["turma"],
                "media": f"{aluno['media']:.2f}",
                "situacao": aluno["situacao"],
            })


alunos = ler_alunos("notas.csv")
for aluno in alunos:
    aluno["media"] = media(aluno["notas"])
    aluno["situacao"] = situacao(aluno["media"])

alunos.sort(key=lambda a: a["media"], reverse=True)
relatorio(alunos)
gravar_resultado(alunos, "resultado.csv")
print("Gravei resultado.csv")

Quando dá errado

Esquecer o float(): unsupported operand type(s) for +: 'int' and 'str'
import csv

with open("notas.csv", newline="", encoding="utf-8") as arquivo:
    for linha in csv.DictReader(arquivo):
        notas = [linha["nota1"], linha["nota2"], linha["nota3"]]
        print(linha["nome"], sum(notas) / 3)
$ python3 notas.py
Traceback (most recent call last):
  File "/home/ana/projetos/notas/notas.py", line 6, in <module>
    print(linha["nome"], sum(notas) / 3)
                         ~~~^^^^^^^
TypeError: unsupported operand type(s) for +: 'int' and 'str'

"Tipos de operando não suportados para +: int e str". O sum começa do número 0 e vai somando: 0 + '8.0', um número com um texto. As notas vieram do CSV como texto. Como consertar: converta cada nota com float() antes de somar, de preferência logo na leitura, como na versão 2.

Nome de coluna errado: KeyError
import csv

with open("notas.csv", newline="", encoding="utf-8") as arquivo:
    for linha in csv.DictReader(arquivo):
        print(linha["Nome"])
$ python3 notas.py
Traceback (most recent call last):
  File "/home/ana/projetos/notas/notas.py", line 5, in <module>
    print(linha["Nome"])
          ~~~~~^^^^^^^^
KeyError: 'Nome'

No cabeçalho do arquivo está nome, com minúscula, e as chaves do DictReader são exatamente o que está no cabeçalho. Como consertar: confira a primeira linha do CSV (um print(alunos[0]), como na versão 1, mostra as chaves de verdade) e use o mesmo nome. Se a planilha tiver espaços no cabeçalho (nota 1), a chave também terá.

Nota com vírgula: could not convert string to float: '5,5'

A professora digitou uma nota do Caio à moda brasileira, com vírgula. No CSV, como a vírgula separa as colunas, a planilha pôs aspas em volta do valor:

nome,turma,nota1,nota2,nota3
Bia,A,8.0,9.5,7.0
Caio,B,6.0,"5,5",5.0
…
$ python3 notas.py
Traceback (most recent call last):
  File "/home/ana/projetos/notas/notas.py", line 23, in <module>
    for aluno in ler_alunos("notas.csv"):
                 ~~~~~~~~~~^^^^^^^^^^^^^
  File "/home/ana/projetos/notas/notas.py", line 8, in ler_alunos
    notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
                                    ~~~~~^^^^^^^^^^^^^^^^
ValueError: could not convert string to float: '5,5'

O csv leu certo (tirou as aspas e entregou '5,5'), mas o float() só entende ponto como separador decimal. O rastro mostra o caminho: a linha 23 chamou o ler_alunos, e a linha 8, dentro dele, falhou; os ^^^ apontam o float(linha["nota2"]), a segunda nota. Como consertar: troque a vírgula antes de converter, float(linha["nota2"].replace(",", ".")), ou exporte a planilha com ponto. (Planilhas em português costumam exportar com ponto e vírgula entre as colunas e vírgula nos decimais; a aula Arquivos CSV mostra o delimiter=";".)

Nota em branco: could not convert string to float: ''

Se uma célula da planilha ficou vazia (Caio,B,6.0,,5.0), a mensagem é a mesma, com um texto vazio entre as aspas:

$ python3 notas.py
Traceback (most recent call last):
  File "/home/ana/projetos/notas/notas.py", line 23, in <module>
    for aluno in ler_alunos("notas.csv"):
                 ~~~~~~~~~~^^^^^^^^^^^^^
  File "/home/ana/projetos/notas/notas.py", line 8, in ler_alunos
    notas = [float(linha["nota1"]), float(linha["nota2"]), float(linha["nota3"])]
                                    ~~~~~^^^^^^^^^^^^^^^^
ValueError: could not convert string to float: ''

'' é o texto vazio: não há número nenhum para converter. Como consertar: decida a regra com a professora (nota em branco vale zero? o aluno fica de fora?) e escreva no programa, por exemplo com um try/except ValueError (aula try e except) que avisa qual aluno está com a nota faltando.

Rodar de outra pasta: FileNotFoundError
$ python3 projetos/notas/notas.py
Traceback (most recent call last):
  File "/home/ana/projetos/notas/notas.py", line 23, in <module>
    for aluno in ler_alunos("notas.csv"):
                 ~~~~~~~~~~^^^^^^^^^^^^^
  File "/home/ana/projetos/notas/notas.py", line 6, in ler_alunos
    with open(caminho, newline="", encoding="utf-8") as arquivo:
         ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'notas.csv'

A Ana estava na pasta pessoal (~) e rodou o programa pelo caminho. O programa e o CSV estão juntos em projetos/notas, mas o open("notas.csv") procura o arquivo na pasta onde o terminal está, e não na pasta do programa. Como consertar: entre na pasta do projeto antes (cd projetos/notas) e rode python3 notas.py. (A aula pathlib mostra como achar a pasta do próprio programa, para ele funcionar de qualquer lugar.)


Formas diferentes de guardar cada aluno

Neste projeto, cada aluno é um dicionário. Seria possível usar outras estruturas:

FormaExemploQuando usar
Dicionárioaluno["media"]O que o projeto usa: casa direto com o DictReader e o DictWriter, e as chaves têm nome
Lista ou tuplaaluno[3]Evite aqui: aluno[3] não diz nada a quem lê, e uma coluna nova bagunça as posições
Classe Alunoaluno.mediaQuando o aluno passa a ter comportamento (a média virando um @property, por exemplo). Veja as Dataclasses

Desafios extras

  • Reprovado: acrescente uma terceira situação, reprovado, para média abaixo de 5. Quem fica com a Gabi (5,33)?
  • Por turma: mostre a média de cada turma (A e B) e quantos aprovados cada uma teve. Um dicionário com a turma como chave ajuda (aula Percorrendo um Dicionário).
  • Vírgula decimal: faça o ler_alunos aceitar 5,5 e 5.5, e avisar (sem quebrar) quando uma nota estiver em branco.
  • O nome do arquivo na linha de comando: receba o nome do CSV em sys.argv (aula Os Módulos os e sys), para usar o mesmo programa com outras turmas.

Referência rápida do projeto

PeçaO que faz
open("notas.csv", newline="", encoding="utf-8")Abre o CSV para ler
list(csv.DictReader(arquivo))Lista de dicionários, um por linha; chaves = cabeçalho
float(linha["nota1"])Texto do CSV → número
sum(notas) / len(notas)Média
alunos.sort(key=lambda a: a["media"], reverse=True)Ordena da maior média para a menor
f"{nome:<8}{turma:^7}{media:>7.2f}"Colunas alinhadas: esquerda, centro, direita
open(caminho, "w", newline="", encoding="utf-8")Abre para gravar (apaga o que havia)
csv.DictWriter(arquivo, fieldnames=[…])Escritor de dicionários
writeheader() / writerow({…})Grava o cabeçalho / uma linha

Exercícios

1. O leitor de dicionários

Complete o nome da classe do módulo csv que lê cada linha como dicionário:

for linha in csv.(arquivo):

2. Texto em número

Complete para transformar a nota lida do CSV num número com casas decimais:

nota = (linha["nota1"])

3. Alinhado à direita

Complete o formato que mostra a média alinhada à direita em 7 caracteres, com duas casas:

f"{media:}"

4. A versão 1 mostrou 'nota1': '8.0', com aspas. O que isso quer dizer?

5. Por que o Enzo, com média 7,00, aparece como aprovado?

6. O que acontece com um resultado.csv que já existia quando o programa abre o arquivo com "w"?


Resumo

  • Versão 1: csv.DictReader lê cada linha como dicionário, com o cabeçalho como chaves. Tudo vem como texto.
  • Versão 2: float() na leitura, uma função para a média e outra para a situação (>= 7, aprovado).
  • Versão 3: sort(key=…, reverse=True) ordena pela média; :<, :^ e :> nas f-strings alinham as colunas.
  • Versão 4: csv.DictWriter com writeheader() e writerow() grava o resultado.csv; o "w" apaga o que havia.
  • Os erros típicos: nota sem float() (TypeError), coluna com outro nome (KeyError), vírgula decimal ou nota vazia (ValueError) e o programa rodado de outra pasta (FileNotFoundError).