Aula 2.5 — Python para aplicações com IA¶
Nesta aula, você irá praticar as construções de Python necessárias para acompanhar o Lab 3 com mais segurança.
Usaremos um catálogo de livros e não faremos chamadas para modelos de IA ou serviços externos.
Objetivos¶
- manipular listas e dicionários;
- usar decisões e repetições;
- escrever funções com parâmetros e retorno;
- criar uma classe simples;
- validar dados com Pydantic;
- converter dados entre Python e JSON;
- entender o fluxo de uma solicitação de ferramenta.
Como usar este notebook¶
Em cada etapa:
- Leia o código com atenção.
- Preveja o resultado antes de executar.
- Execute a célula e confira a saída.
- Altere algum valor e execute novamente.
Nas seções Sua vez, tente resolver antes de abrir a solução.
1. Variáveis e tipos básicos¶
Uma variável associa um nome a um valor. O tipo do valor determina quais operações podem ser realizadas.
titulo = "Eu, Robô"
ano = 1950
disponivel = True
data_devolucao = None
print(titulo, type(titulo))
print(ano, type(ano))
print(disponivel, type(disponivel))
print(data_devolucao, type(data_devolucao))
Eu, Robô <class 'str'> 1950 <class 'int'> True <class 'bool'> None <class 'NoneType'>
Preveja¶
Antes de executar a próxima célula, responda:
- Qual será o valor de
mensagem? - O valor de
anoserá alterado?
mensagem = titulo + " foi publicado em " + str(ano) + "."
print(mensagem)
print("Ano continua sendo:", ano)
Eu, Robô foi publicado em 1950. Ano continua sendo: 1950
Sua vez¶
Altere os valores abaixo para representar outro livro. Depois, execute a célula.
meu_titulo = "O Hobbit"
meu_ano = 1937
meu_disponivel = False
print("Título:", meu_titulo)
print("Ano:", meu_ano)
print("Disponível:", meu_disponivel)
Título: O Hobbit Ano: 1937 Disponível: False
2. Listas e dicionários¶
Uma lista armazena vários itens em ordem. Um dicionário organiza valores usando chaves.
categorias = ["ficcao", "tecnologia", "historia", "outro"]
livro = {
"id": 1,
"titulo": "Eu, Robô",
"autor": "Isaac Asimov",
"categoria": "ficcao",
"disponivel": True,
}
print("Primeira categoria:", categorias[0])
print("Título:", livro["titulo"])
print("Disponível:", livro["disponivel"])
Primeira categoria: ficcao Título: Eu, Robô Disponível: True
[] e .get()¶
livro["titulo"] exige que a chave exista. livro.get("editora") devolve None quando a chave não existe, ou um valor padrão informado por nós.
print(livro["titulo"])
print(livro.get("editora"))
print(livro.get("editora", "Editora não informada"))
Eu, Robô None Editora não informada
Uma lista de dicionários¶
Nosso catálogo será uma lista. Cada item da lista será um dicionário que representa um livro.
CATALOGO = [
{
"id": 1,
"titulo": "Eu, Robô",
"autor": "Isaac Asimov",
"categoria": "ficcao",
"disponivel": True,
},
{
"id": 2,
"titulo": "Entendendo Algoritmos",
"autor": "Aditya Bhargava",
"categoria": "tecnologia",
"disponivel": False,
},
{
"id": 3,
"titulo": "Sapiens",
"autor": "Yuval Noah Harari",
"categoria": "historia",
"disponivel": True,
},
]
print("Quantidade de livros:", len(CATALOGO))
print("Segundo livro:", CATALOGO[1]["titulo"])
Quantidade de livros: 3 Segundo livro: Entendendo Algoritmos
Sua vez¶
Sem executar código ainda, escreva em um papel ou comentário:
- Como acessar o autor do primeiro livro?
- Como verificar a disponibilidade do terceiro livro?
- Como alterar a disponibilidade do segundo livro para
True?
# Solução
print(CATALOGO[0]["autor"])
print(CATALOGO[2]["disponivel"])
CATALOGO[1]["disponivel"] = True
print(CATALOGO[1])
Isaac Asimov
True
{'id': 2, 'titulo': 'Entendendo Algoritmos', 'autor': 'Aditya Bhargava', 'categoria': 'tecnologia', 'disponivel': True}
3. Decisões com if, elif e else¶
Uma aplicação precisa tomar decisões. A indentação indica quais linhas pertencem a cada condição.
livro_exemplo = CATALOGO[0]
if livro_exemplo["disponivel"]:
print("O livro pode ser emprestado.")
else:
print("O livro não está disponível.")
O livro pode ser emprestado.
Valide uma categoria¶
O operador in verifica se um valor pertence a uma coleção.
categoria_informada = "tecnologia"
if categoria_informada in categorias:
print("Categoria válida.")
else:
print("Categoria inválida.")
Categoria válida.
Use mais de um caminho¶
elif permite testar outra condição quando a primeira é falsa.
quantidade_disponivel = 2
if quantidade_disponivel == 0:
print("Nenhum livro disponível.")
elif quantidade_disponivel == 1:
print("Um livro disponível.")
else:
print("Mais de um livro disponível.")
Mais de um livro disponível.
4. Repetições com for¶
O for executa o mesmo bloco para cada item de uma coleção.
for item in CATALOGO:
print(item["id"], "-", item["titulo"])
1 - Eu, Robô 2 - Entendendo Algoritmos 3 - Sapiens
Busque um livro¶
Começamos com encontrado = None. Quando o identificador for localizado, guardamos o livro nessa variável e usamos break para encerrar o laço.
id_procurado = 3
encontrado = None
for item in CATALOGO:
if item["id"] == id_procurado:
encontrado = item
break
if encontrado is None:
print("Livro não encontrado.")
else:
print("Encontrado:", encontrado["titulo"])
Encontrado: Sapiens
Sua vez¶
Escreva um for que mostre somente os livros disponíveis. Tente antes de executar a solução.
# Escreva sua tentativa aqui.
# Solução possível
for item in CATALOGO:
if item["disponivel"]:
print(item["titulo"])
Eu, Robô Entendendo Algoritmos Sapiens
5. Funções¶
Uma função reúne uma responsabilidade sob um nome. Ela pode receber parâmetros e devolver um valor com return.
print() não é return¶
print()mostra algo na tela.returnentrega um valor para quem chamou a função.
def mostrar_titulo(titulo: str):
print("Título:", titulo)
def obter_titulo(livro: dict) -> str:
return livro["titulo"]
retorno_do_print = mostrar_titulo("Sapiens")
retorno_da_funcao = obter_titulo(CATALOGO[0])
print("Retorno de mostrar_titulo:", retorno_do_print)
print("Retorno de obter_titulo:", retorno_da_funcao)
Título: Sapiens Retorno de mostrar_titulo: None Retorno de obter_titulo: Eu, Robô
Escopo local¶
Uma variável criada dentro da função pertence à execução daquela função. Ela pode usar o mesmo nome de uma variável externa sem substituir automaticamente o valor externo.
mensagem = "fora da função"
def criar_mensagem(titulo: str) -> str:
mensagem = "Livro selecionado: " + titulo
return mensagem
resultado = criar_mensagem("Eu, Robô")
print(resultado)
print(mensagem)
Livro selecionado: Eu, Robô fora da função
Função de consulta¶
Agora transformamos a busca anterior em uma função reutilizável.
def consultar_livro(id_livro: int) -> dict:
for item in CATALOGO:
if item["id"] == id_livro:
return item
return {"erro": "livro não encontrado", "id_livro": id_livro}
print(consultar_livro(1))
print(consultar_livro(999))
{'id': 1, 'titulo': 'Eu, Robô', 'autor': 'Isaac Asimov', 'categoria': 'ficcao', 'disponivel': True}
{'erro': 'livro não encontrado', 'id_livro': 999}
Sua vez: cadastro¶
Antes de olhar a solução, tente escrever uma função que:
- receba título, autor e categoria;
- rejeite título ou autor com menos de dois caracteres;
- rejeite uma categoria desconhecida;
- crie um identificador;
- adicione o livro ao catálogo;
- devolva o livro criado.
# Escreva sua tentativa aqui.
#
# def cadastrar_livro(titulo, autor, categoria):
# ...
Solução possível¶
A implementação abaixo usa passos explícitos. Leia cada validação antes de executar.
def cadastrar_livro(titulo: str, autor: str, categoria: str) -> dict:
titulo_limpo = titulo.strip()
autor_limpo = autor.strip()
if len(titulo_limpo) < 2:
return {"erro": "título inválido"}
if len(autor_limpo) < 2:
return {"erro": "autor inválido"}
if categoria not in categorias:
return {"erro": "categoria inválida"}
maior_id = 0
for item in CATALOGO:
if item["id"] > maior_id:
maior_id = item["id"]
novo_livro = {
"id": maior_id + 1,
"titulo": titulo_limpo,
"autor": autor_limpo,
"categoria": categoria,
"disponivel": True,
}
CATALOGO.append(novo_livro)
return novo_livro
Teste entradas válidas e inválidas¶
Observe que uma entrada inválida devolve um erro e não altera o catálogo.
quantidade_antes = len(CATALOGO)
cadastro_valido = cadastrar_livro("Duna", "Frank Herbert", "ficcao")
cadastro_sem_titulo = cadastrar_livro(" ", "Autor Teste", "ficcao")
cadastro_categoria_invalida = cadastrar_livro(
"Livro Teste", "Autor Teste", "aventura"
)
print("Cadastro válido:", cadastro_valido)
print("Título inválido:", cadastro_sem_titulo)
print("Categoria inválida:", cadastro_categoria_invalida)
print("Livros adicionados:", len(CATALOGO) - quantidade_antes)
Cadastro válido: {'id': 4, 'titulo': 'Duna', 'autor': 'Frank Herbert', 'categoria': 'ficcao', 'disponivel': True}
Título inválido: {'erro': 'título inválido'}
Categoria inválida: {'erro': 'categoria inválida'}
Livros adicionados: 1
Liste apenas os disponíveis¶
Criamos uma nova lista e adicionamos somente os itens que atendem à condição.
def listar_disponiveis() -> list[dict]:
disponiveis = []
for item in CATALOGO:
if item["disponivel"]:
disponiveis.append(item)
return disponiveis
for item in listar_disponiveis():
print(item["titulo"])
Eu, Robô Entendendo Algoritmos Sapiens Duna
Checks das funções¶
assert verifica uma condição. Se ela for falsa, o Python interrompe a execução com AssertionError.
assert consultar_livro(1)["titulo"] == "Eu, Robô"
assert consultar_livro(999)["erro"] == "livro não encontrado"
assert cadastro_valido["titulo"] == "Duna"
assert cadastro_categoria_invalida["erro"] == "categoria inválida"
for item in listar_disponiveis():
assert item["disponivel"] is True
print("Checks das funções: OK")
Checks das funções: OK
6. Orientação a objetos essencial¶
Uma classe descreve quais dados e comportamentos seus objetos terão. Cada objeto criado a partir dela é uma instância.
__init__ recebe os valores iniciais. self representa o próprio objeto.
class Livro:
def __init__(self, id_livro: int, titulo: str, autor: str, categoria: str, disponivel: bool = True):
self.id_livro = id_livro
self.titulo = titulo
self.autor = autor
self.categoria = categoria
self.disponivel = disponivel
def resumo(self) -> str:
if self.disponivel:
status = "disponível"
else:
status = "indisponível"
return f"{self.titulo}, de {self.autor} — {status}"
Crie objetos¶
Cada chamada a Livro(...) cria um objeto diferente, com seus próprios atributos.
livro_objeto_1 = Livro(10, "Neuromancer", "William Gibson", "ficcao")
livro_objeto_2 = Livro(11, "Código Limpo", "Robert C. Martin", "tecnologia", False )
print(livro_objeto_1.titulo)
print(livro_objeto_1.resumo())
print(livro_objeto_2.resumo())
Neuromancer Neuromancer, de William Gibson — disponível Código Limpo, de Robert C. Martin — indisponível
Sua vez¶
Crie um terceiro objeto Livro, mostre um atributo e chame o método resumo().
meu_livro = Livro(12, "1984", "George Orwell", "ficcao", True)
print(meu_livro.autor)
print(meu_livro.resumo())
George Orwell 1984, de George Orwell — disponível
O que é herança?¶
Uma classe pode aproveitar comportamentos fornecidos por outra classe. A sintaxe class Filha(Pai) indica essa relação.
Não estudaremos herança em profundidade. Precisamos apenas reconhecer essa sintaxe porque os modelos Pydantic herdam de BaseModel.
class ItemCatalogo:
def identificar(self) -> str:
return "Sou um item do catálogo"
class Revista(ItemCatalogo):
pass
revista = Revista()
print(revista.identificar())
Sou um item do catálogo
7. Validação com Pydantic¶
Pydantic é a única dependência externa desta aula. A instalação abaixo não utiliza uma API de IA; ela apenas disponibiliza a biblioteca no ambiente do notebook.
%pip install -q "pydantic>=2.0,<3"
WARNING: Cache entry deserialization failed, entry ignored [notice] A new release of pip is available: 25.2 -> 26.2.1 [notice] To update, run: pip install --upgrade pip Note: you may need to restart the kernel to use updated packages.
Defina um modelo¶
LivroValidado herda recursos de BaseModel. Os type hints definem os tipos esperados e Field acrescenta restrições.
from typing import Literal
from pydantic import BaseModel, Field, ValidationError
class LivroValidado(BaseModel):
id_livro: int = Field(gt=0)
titulo: str = Field(min_length=2)
autor: str = Field(min_length=2)
categoria: Literal["ficcao", "tecnologia", "historia", "outro"]
disponivel: bool = True
Validação bem-sucedida¶
Quando os dados atendem às regras, recebemos um objeto. Seus campos podem ser acessados como atributos.
livro_validado = LivroValidado(
id_livro=20,
titulo="Fundação",
autor="Isaac Asimov",
categoria="ficcao",
disponivel=False
)
print(livro_validado)
print("Título:", livro_validado.titulo)
print("Disponível:", livro_validado.disponivel)
id_livro=20 titulo='Fundação' autor='Isaac Asimov' categoria='ficcao' disponivel=False Título: Fundação Disponível: False
model_dump()¶
O objeto validado pode voltar a ser um dicionário. Esse método aparecerá novamente em aplicações que precisam enviar dados para outros componentes.
livro_como_dicionario = livro_validado.model_dump()
print(livro_como_dicionario)
print(type(livro_como_dicionario))
{'id_livro': 20, 'titulo': 'Fundação', 'autor': 'Isaac Asimov', 'categoria': 'ficcao', 'disponivel': False}
<class 'dict'>
Trate um ValidationError¶
Dados inválidos geram uma exceção. try/except permite que a aplicação trate esse problema em vez de encerrar inesperadamente.
try:
livro_invalido = LivroValidado(
id_livro=0,
titulo="X",
autor="",
categoria="aventura",
)
print(livro_invalido)
except ValidationError as erro:
print("Os dados não passaram pela validação.")
print(erro)
Os dados não passaram pela validação.
4 validation errors for LivroValidado
id_livro
Input should be greater than 0 [type=greater_than, input_value=0, input_type=int]
For further information visit https://errors.pydantic.dev/2.13/v/greater_than
titulo
String should have at least 2 characters [type=string_too_short, input_value='X', input_type=str]
For further information visit https://errors.pydantic.dev/2.13/v/string_too_short
autor
String should have at least 2 characters [type=string_too_short, input_value='', input_type=str]
For further information visit https://errors.pydantic.dev/2.13/v/string_too_short
categoria
Input should be 'ficcao', 'tecnologia', 'historia' or 'outro' [type=literal_error, input_value='aventura', input_type=str]
For further information visit https://errors.pydantic.dev/2.13/v/literal_error
Inspecione os erros¶
erro.errors() devolve uma lista de dicionários. Assim, a aplicação pode identificar o campo e a mensagem de cada erro.
try:
LivroValidado(
id_livro=-5,
titulo="A",
autor="B",
categoria="desconhecida",
)
except ValidationError as erro:
erros = erro.errors()
for item_erro in erros:
print("Campo:", item_erro["loc"])
print("Mensagem:", item_erro["msg"])
print()
Campo: ('id_livro',)
Mensagem: Input should be greater than 0
Campo: ('titulo',)
Mensagem: String should have at least 2 characters
Campo: ('autor',)
Mensagem: String should have at least 2 characters
Campo: ('categoria',)
Mensagem: Input should be 'ficcao', 'tecnologia', 'historia' or 'outro'
Validação manual e Pydantic¶
Na função cadastrar_livro, escrevemos cada regra com if. No modelo Pydantic, os campos e restrições formam um contrato reutilizável.
Pydantic reduz parte do código repetitivo, mas a aplicação ainda precisa definir regras de negócio, como verificar duplicidade ou disponibilidade.
8. Python e JSON¶
JSON é um formato textual usado para transportar dados. O módulo json faz parte da biblioteca padrão do Python.
import json
dados_python = {
"titulo": "Fundação",
"disponivel": True,
"data_devolucao": None,
"tags": ["ficcao", "classico"],
}
print(dados_python)
print(type(dados_python))
{'titulo': 'Fundação', 'disponivel': True, 'data_devolucao': None, 'tags': ['ficcao', 'classico']}
<class 'dict'>
Python → JSON com dumps()¶
O resultado é uma string. Observe true e null, que pertencem à sintaxe JSON.
texto_json = json.dumps(dados_python, ensure_ascii=False, indent=2)
print(texto_json)
print(type(texto_json))
{
"titulo": "Fundação",
"disponivel": true,
"data_devolucao": null,
"tags": [
"ficcao",
"classico"
]
}
<class 'str'>
JSON → Python com loads()¶
Ao ler a string, recuperamos estruturas Python.
dados_recuperados = json.loads(texto_json)
print(dados_recuperados)
print(type(dados_recuperados))
print("Título:", dados_recuperados["titulo"])
{'titulo': 'Fundação', 'disponivel': True, 'data_devolucao': None, 'tags': ['ficcao', 'classico']}
<class 'dict'>
Título: Fundação
Check da ida e volta¶
Depois da conversão Python → JSON → Python, os dados devem continuar equivalentes.
assert dados_recuperados == dados_python
print("Conversão Python → JSON → Python: OK")
Conversão Python → JSON → Python: OK
Leia uma solicitação estruturada¶
A string abaixo poderia ter sido recebida de outro componente da aplicação.
solicitacao_json = """
{
"nome": "consultar_livro",
"argumentos": {
"id_livro": 1
}
}
"""
solicitacao_python = json.loads(solicitacao_json)
print("Nome:", solicitacao_python["nome"])
print("Argumentos:", solicitacao_python["argumentos"])
Nome: consultar_livro
Argumentos: {'id_livro': 1}
Sua vez: escreva uma solicitação¶
Altere a célula para representar uma chamada a cadastrar_livro. Lembre-se de usar true, false e null apenas em JSON; em um dicionário Python, use True, False e None.
nova_solicitacao = {
"nome": "cadastrar_livro",
"argumentos": {
"titulo": "Python Fluente",
"autor": "Luciano Ramalho",
"categoria": "tecnologia",
},
}
nova_solicitacao_json = json.dumps(
nova_solicitacao,
ensure_ascii=False,
indent=2,
)
print(nova_solicitacao_json)
{
"nome": "cadastrar_livro",
"argumentos": {
"titulo": "Python Fluente",
"autor": "Luciano Ramalho",
"categoria": "tecnologia"
}
}
9. Miniaplicação: execute uma solicitação de ferramenta¶
Primeiro, definimos modelos Pydantic específicos para os argumentos de cada operação.
class ArgumentosConsulta(BaseModel):
id_livro: int = Field(gt=0)
class ArgumentosCadastro(BaseModel):
titulo: str = Field(min_length=2)
autor: str = Field(min_length=2)
categoria: Literal["ficcao", "tecnologia", "historia", "outro"]
Controle quais funções podem ser executadas¶
A função abaixo usa if/elif. Somente três nomes são reconhecidos. Qualquer outro nome será recusado.
As chamadas usam argumentos explícitos para deixar o fluxo visível.
- model_validate: é um metodo do Pydantic que valida os argumentos recebidos. Ele devolve um objeto com os campos validados ou gera uma exceção.
def executar_solicitacao(solicitacao: dict) -> dict:
nome = solicitacao.get("nome")
argumentos = solicitacao.get("argumentos", {})
if nome == "consultar_livro":
try:
dados = ArgumentosConsulta.model_validate(argumentos)
except ValidationError as erro:
return {
"sucesso": False,
"erro": "argumentos inválidos",
"detalhes": str(erro),
}
resultado = consultar_livro(dados.id_livro)
if "erro" in resultado:
return {"sucesso": False, "resultado": resultado}
return {"sucesso": True, "resultado": resultado}
elif nome == "cadastrar_livro":
try:
dados = ArgumentosCadastro.model_validate(argumentos)
except ValidationError as erro:
return {
"sucesso": False,
"erro": "argumentos inválidos",
"detalhes": str(erro),
}
resultado = cadastrar_livro(
dados.titulo,
dados.autor,
dados.categoria,
)
if "erro" in resultado:
return {"sucesso": False, "resultado": resultado}
return {"sucesso": True, "resultado": resultado}
elif nome == "listar_disponiveis":
resultado = listar_disponiveis()
return {"sucesso": True, "resultado": resultado}
else:
return {
"sucesso": False,
"erro": "ferramenta não autorizada",
"nome": nome,
}
Teste uma ferramenta conhecida¶
solicitacao_consulta = {
"nome": "consultar_livro",
"argumentos": {"id_livro": 2},
}
resultado_consulta = executar_solicitacao(solicitacao_consulta)
print(resultado_consulta)
{'sucesso': True, 'resultado': {'id': 2, 'titulo': 'Entendendo Algoritmos', 'autor': 'Aditya Bhargava', 'categoria': 'tecnologia', 'disponivel': True}}
Teste argumentos inválidos¶
O identificador deve ser maior que zero. A função de consulta não será executada quando a validação falhar.
solicitacao_invalida = {
"nome": "consultar_livro",
"argumentos": {"id_livro": 0},
}
resultado_invalido = executar_solicitacao(solicitacao_invalida)
print(resultado_invalido)
{'sucesso': False, 'erro': 'argumentos inválidos', 'detalhes': '1 validation error for ArgumentosConsulta\nid_livro\n Input should be greater than 0 [type=greater_than, input_value=0, input_type=int]\n For further information visit https://errors.pydantic.dev/2.13/v/greater_than'}
Teste uma ferramenta não autorizada¶
Não existe um caminho de execução para apagar_catalogo.
solicitacao_proibida = {
"nome": "apagar_catalogo",
"argumentos": {},
}
resultado_proibido = executar_solicitacao(solicitacao_proibida)
print(resultado_proibido)
{'sucesso': False, 'erro': 'ferramenta não autorizada', 'nome': 'apagar_catalogo'}
Execute um cadastro e converta o resultado para JSON¶
resultado_cadastro = executar_solicitacao(nova_solicitacao)
resultado_cadastro_json = json.dumps(
resultado_cadastro,
ensure_ascii=False,
indent=2,
)
print(resultado_cadastro_json)
{
"sucesso": true,
"resultado": {
"id": 5,
"titulo": "Python Fluente",
"autor": "Luciano Ramalho",
"categoria": "tecnologia",
"disponivel": true
}
}