Projeto: Sistema de Gerenciamento de Biblioteca
Este é o projeto final da Fase 4. Ele reúne tudo das últimas lições — classes, herança, encapsulamento, métodos mágicos e operações de arquivo — para construir um sistema de gerenciamento de biblioteca realmente utilizável. Completar este projeto significa que você realmente dominou a programação orientada a objetos do Python.
Requisitos do Projeto
1. Gerenciamento de Livros (CRUD)
├── Adicionar livro (título, autor, ISBN, ano de publicação)
├── Deletar livro (por ISBN)
├── Modificar informações do livro
└── Buscar livros (busca difusa por título / busca por autor)
2. Gerenciamento de Empréstimos
├── Emprestar livro (registrar mutuário e data de empréstimo)
├── Devolver livro (verificar atraso)
└── Visualizar registros de empréstimo
3. Estatísticas de Dados
├── Total de livros
├── Contagem de disponíveis/emprestados
└── Contagem por autor
4. Persistência de Dados (arquivo CSV, carregamento automático na inicialização)
1. Código Completo
Exemplo: Sistema de Gerenciamento de Biblioteca
import csv
import os
from datetime import datetime, timedelta
from dataclasses import dataclass
# ====== Modelo de Dados ======
@dataclass
class Livro:
"""Classe de dados Livro"""
titulo: str
autor: str
isbn: str
ano: int
emprestado: bool = False
mutuario: str = ""
data_emprestimo: str = ""
def to_csv_row(self):
"""Converte para linha CSV"""
return [self.titulo, self.autor, self.isbn, str(self.ano),
str(self.emprestado), self.mutuario, self.data_emprestimo]
@staticmethod
def from_csv_row(row):
"""Cria objeto Livro a partir de linha CSV"""
return Livro(
titulo=row[0], autor=row[1], isbn=row[2],
ano=int(row[3]),
emprestado=row[4] == "True",
mutuario=row[5], data_emprestimo=row[6]
)
# ====== Camada de Lógica de Negócio ======
class Biblioteca:
"""Sistema de gerenciamento de biblioteca"""
DATA_FILE = "library_data.csv"
def __init__(self):
self.livros = []
self.load_data()
# ---- Persistência ----
def load_data(self):
"""Carrega dados do CSV"""
if not os.path.exists(self.DATA_FILE):
return
with open(self.DATA_FILE, "r", encoding="utf-8") as f:
reader = csv.reader(f)
next(reader, None) # Pular cabeçalho
for row in reader:
if row:
self.livros.append(Livro.from_csv_row(row))
print(f"Carregados {len(self.livros)} livros.")
def save_data(self):
"""Salva dados em CSV"""
with open(self.DATA_FILE, "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["Título", "Autor", "ISBN", "Ano",
"Emprestado", "Mutário", "Data Empréstimo"])
for book in self.livros:
writer.writerow(book.to_csv_row())
# ---- Gerenciamento de Livros ----
def adicionar_livro(self, titulo, autor, isbn, ano):
"""Adiciona um livro"""
for book in self.livros:
if book.isbn == isbn:
return False, "ISBN já existe!"
self.livros.append(Livro(titulo, autor, isbn, ano))
self.save_data()
return True, f"Adicionado '{titulo}'"
def deletar_livro(self, isbn):
"""Deleta um livro"""
for i, book in enumerate(self.livros):
if book.isbn == isbn:
removido = self.livros.pop(i)
self.save_data()
return True, f"Deletado '{removido.titulo}'"
return False, "Livro com este ISBN não encontrado."
def buscar_por_titulo(self, keyword):
"""Busca difusa por título"""
return [b for b in self.livros if keyword.lower() in b.titulo.lower()]
def buscar_por_autor(self, autor):
"""Busca por autor"""
return [b for b in self.livros if autor.lower() in b.autor.lower()]
def get_livro_por_isbn(self, isbn):
"""Encontra livro por ISBN"""
for book in self.livros:
if book.isbn == isbn:
return book
return None
# ---- Gerenciamento de Empréstimos ----
def emprestar_livro(self, isbn, mutuario):
"""Empresta um livro"""
book = self.get_livro_por_isbn(isbn)
if not book:
return False, "Livro não encontrado."
if book.emprestado:
return False, f"'{book.titulo}' já está emprestado para {book.mutuario}."
book.emprestado = True
book.mutuario = mutuario
book.data_emprestimo = datetime.now().strftime("%Y-%m-%d")
self.save_data()
return True, f"'{book.titulo}' foi emprestado para {mutuario}."
def devolver_livro(self, isbn):
"""Devolve um livro"""
book = self.get_livro_por_isbn(isbn)
if not book:
return False, "Livro não encontrado."
if not book.emprestado:
return False, f"'{book.titulo}' não está atualmente emprestado."
# Verifica atraso (período de empréstimo de 30 dias)
data_emp = datetime.strptime(book.data_emprestimo, "%Y-%m-%d")
dias_emprestado = (datetime.now() - data_emp).days
atrasado = dias_emprestado > 30
book.emprestado = False
book.mutuario = ""
book.data_emprestimo = ""
self.save_data()
if atrasado:
return True, f"'{book.titulo}' devolvido. Atrasado em {dias_emprestado - 30} dias."
return True, f"'{book.titulo}' devolvido. Emprestado por {dias_emprestado} dias."
# ---- Estatísticas ----
def get_stats(self):
"""Obtém estatísticas"""
total = len(self.livros)
emprestados = sum(1 for b in self.livros if b.emprestado)
disponiveis = total - emprestados
# Contagem por autor
autor_count = {}
for book in self.livros:
autor_count[book.autor] = autor_count.get(book.autor, 0) + 1
return {
"total": total,
"disponiveis": disponiveis,
"emprestados": emprestados,
"por_autor": dict(sorted(autor_count.items(),
key=lambda x: x[1], reverse=True))
}
# ====== Camada de Apresentação ======
def mostrar_menu():
print("\n" + "=" * 40)
print("Sistema de Gerenciamento de Biblioteca")
print("=" * 40)
print("1. Adicionar Livro")
print("2. Deletar Livro")
print("3. Buscar Livros")
print("4. Exibir Todos os Livros")
print("5. Emprestar Livro")
print("6. Devolver Livro")
print("7. Estatísticas")
print("8. Sair")
print("=" * 40)
def exibir_livros(livros, titulo="Resultados da Busca"):
"""Exibe uma lista de livros"""
if not livros:
print(f"\n{titulo}: Nenhum registro.")
return
print(f"\n--- {titulo} ---")
print(f"{'Título':<20}{'Autor':<12}{'ISBN':<15}{'Status':<8}")
print("-" * 55)
for b in livros:
status = "Emprestado" if b.emprestado else "Disponível"
print(f"{b.titulo:<20}{b.autor:<12}{b.isbn:<15}{status:<8}")
def main():
library = Biblioteca()
while True:
mostrar_menu()
choice = input("Selecione opção (1-8): ").strip()
if choice == "1":
titulo = input("Título: ").strip()
autor = input("Autor: ").strip()
isbn = input("ISBN: ").strip()
ano = input("Ano: ").strip()
success, msg = library.adicionar_livro(titulo, autor, isbn, int(ano))
print(f" {'OK' if success else 'Erro'}: {msg}")
elif choice == "2":
isbn = input("Digite o ISBN para deletar: ").strip()
success, msg = library.deletar_livro(isbn)
print(f" {'OK' if success else 'Erro'}: {msg}")
elif choice == "3":
print(" 1. Buscar por Título")
print(" 2. Buscar por Autor")
opt = input("Escolha: ").strip()
if opt == "1":
keyword = input("Digite palavra-chave do título: ").strip()
results = library.buscar_por_titulo(keyword)
elif opt == "2":
autor = input("Digite o nome do autor: ").strip()
results = library.buscar_por_autor(autor)
else:
print("Escolha inválida")
continue
exibir_livros(results)
elif choice == "4":
exibir_livros(library.livros, "Todos os Livros")
elif choice == "5":
isbn = input("Digite o ISBN para emprestar: ").strip()
mutuario = input("Nome do mutuário: ").strip()
success, msg = library.emprestar_livro(isbn, mutuario)
print(f" {'OK' if success else 'Erro'}: {msg}")
elif choice == "6":
isbn = input("Digite o ISBN para devolver: ").strip()
success, msg = library.devolver_livro(isbn)
print(f" {'OK' if success else 'Erro'}: {msg}")
elif choice == "7":
stats = library.get_stats()
print(f"\n--- Estatísticas ---")
print(f"Total de livros: {stats['total']}")
print(f"Disponíveis: {stats['disponiveis']} Emprestados: {stats['emprestados']}")
print(f"\nPor Autor:")
for autor, count in stats['por_autor'].items():
print(f" {autor}: {count} livros")
elif choice == "8":
print("Obrigado por usar! Até logo!")
break
else:
print("Escolha inválida. Digite 1-8.")
if __name__ == "__main__":
main()
2. Destaques de Design
Arquitetura em camadas: Modelo de dados (Livro), lógica de negócio (Biblioteca) e camada de apresentação (main) são separados, com responsabilidades claras para cada camada.
Persistência de dados: Armazenamento em arquivo CSV com carregamento automático na inicialização — dados sobrevivem a reinicializações do programa.
Lógica de empréstimo: Calcula automaticamente a duração do empréstimo; avisa sobre atraso se exceder 30 dias.
Simplificação com @dataclass: A classe Livro usa o decorador @dataclass, eliminando muito código boilerplate.
3. Direções de Extensão
| Recurso | Ideia de Implementação |
|---|---|
| Sistema de usuários | Adicione uma classe Usuário, rastreie histórico de empréstimos, limite máximo de empréstimos |
| Multas por atraso | Calcule multas em devolver_livro |
| Interface gráfica | Use tkinter ou um framework web para interface gráfica |
| Armazenamento em banco de dados | Substitua CSV por banco de dados SQLite |
| Busca avançada | Suporte busca combinada com múltiplas condições (autor + faixa de ano) |
❓ Perguntas Frequentes
P: Qual a diferença entre
@dataclasse escrever__init__manualmente? R:@dataclassgera automaticamente__init__,__repr__,__eq__e outros métodos, economizando código boilerplate. Funcionalmente idêntico, apenas mais conciso. Quando uma classe armazena principalmente dados sem lógica complexa, prefira@dataclass. P: Por que usar CSV em vez de JSON para armazenamento de dados? R: Dados de livros são "tabulares" (um livro por linha, campos fixos) — CSV se ajusta bem a esta estrutura e pode ser aberto diretamente no Excel. JSON é mais adequado para estruturas aninhadas (como notas de alunos com sub-dicionários). Ambos funcionam; escolha com base na estrutura dos dados. P: Usar strings como "Emprestado"/"Disponível" para status pode causar problemas? R: É suficiente para projetos pequenos. Uma abordagem mais rigorosa usaria umEnumpara definir estados, evitando erros de digitação em strings. No entanto, enums são um tópico avançado; strings funcionam bem aqui.
📖 Resumo
- O sistema de gerenciamento de biblioteca usa arquitetura em três camadas: Modelo de Dados -> Lógica de Negócio -> Camada de Apresentação
@dataclasssimplifica definições de classes de dados;to_csv_row()/from_csv_row()implementam serialização- Arquivos CSV fornecem persistência de dados;
load_data()lê na inicialização,save_data()salva após cada modificação - O módulo
datetimecalcula duração do empréstimo e detecta automaticamente atrasos - List comprehensions e o método
get()do dicionário são usados para estatísticas
📝 Atividades
-
Iniciante (Dificuldade ⭐): Adicione um campo
categoriaà classeLivro(Ficção, Ciência, História, etc.) e permita que o usuário selecione uma categoria ao adicionar um livro. -
Intermediário (Dificuldade ⭐⭐): Adicione um recurso de "histórico de empréstimos" — cada vez que um livro é devolvido, anexe o registro de empréstimo a um arquivo
history.csv. Formato do registro: título do livro, mutuário, data de empréstimo, data de devolução, status de atraso. -
Avançado (Dificuldade ⭐⭐⭐): Sem consultar o código acima, construa um "Sistema de Gerenciamento de Membros" do zero. Recursos incluem: adicionar membro (nome, telefone, pontos), resgatar pontos, visualizar lista de membros, ordenar por pontos, salvar dados em CSV. Organize seu código usando classes.



