
Passo 1 de 7
Separe resultados de mensagens de erro
Use stdout para o resultado da operação e stderr para erros e diagnósticos, mesmo que ambos apareçam no mesmo terminal.
Trilha de aprendizado · Nível 15 · Tutorial 5
Estabelecer um contrato de sucesso e falha para a CLI, separando resultados, mensagens ao usuário e informações de diagnóstico.
Separe resultados de mensagens de erro
Use stdout para o resultado da operação e stderr para erros e diagnósticos, mesmo que ambos apareçam no mesmo terminal. 2 min
Defina o contrato de códigos de saída
Use códigos de saída para que pessoas e outros programas interpretem o resultado de uma execução de forma coerente. 2 min
Respeite as saídas do argparse
Preveja o comportamento padrão do argparse para ajuda, argumentos inválidos e chamadas válidas. 2 min
Concentre o encerramento no ponto de entrada
Organize a CLI para que main retorne códigos e o ponto de entrada encerre o processo. 2 min
Traduza falhas conhecidas em orientações úteis
Converta falhas previstas da operação em mensagens claras para o usuário, mantendo stderr e o código de saída contratados. 3 min
Preserve o diagnóstico de falhas inesperadas
Diferencie defeitos inesperados de falhas previstas e preserve evidências úteis para investigação. 2 min
Aplique e confira o contrato completo
Execute uma CLI autocontida em seis cenários e compare canais, mensagens, diagnóstico e código de saída. 3 min

Passo 1 de 7
Use stdout para o resultado da operação e stderr para erros e diagnósticos, mesmo que ambos apareçam no mesmo terminal.
Uma CLI tem dois canais de texto importantes. Use stdout para o resultado que a pessoa ou outro programa quer consumir. Use stderr para mensagens de erro e diagnósticos.
Separar os canais permite, por exemplo, guardar somente o resultado em um arquivo sem misturar avisos ou explicações técnicas.
A tela do terminal pode mostrar ambos os textos juntos, mas eles continuam em canais separados.

Mesmo terminal visível não significa o mesmo canal de saída.
Por padrão, print() escreve em stdout. Para enviar uma mensagem a stderr, importe sys e passe file=sys.stderr.
O resultado abaixo permanece limpo em stdout; a mensagem sobre o problema segue separadamente para stderr.
import sys
print("Registro encontrado: Ana")
print("Aviso: o cadastro está desatualizado.", file=sys.stderr)Relacione cada exemplo ao canal mais adequado.
Toque em um item e depois no par correspondente.

Passo 2 de 7
Use códigos de saída para que pessoas e outros programas interpretem o resultado de uma execução de forma coerente.
Uma CLI comunica o desfecho por dois meios complementares:
Por convenção, 0 significa sucesso. Qualquer valor diferente de zero indica falha. O destino da mensagem e o código são decisões independentes: imprimir em stderr não altera automaticamente o código de saída.
A mensagem pode seguir para stdout ou stderr; o status é um sinal separado enviado ao término da execução.

Uma execução pode escrever em stderr e, ainda assim, informar status 0 se o programa não definir um status de falha.
Neste exemplo, a aplicação adota este contrato:
| Código | Categoria |
|---:|---|
| 0 | sucesso ou solicitação de ajuda |
| 2 | uso incorreto dos argumentos |
| 3 | falha esperada da operação, como configuração inválida ou registro ausente |
| 1 | defeito inesperado que precisa de investigação |
Os valores não zero não são uma classificação universal do Python. Eles fazem parte do contrato desta aplicação; documente-os e aplique-os de modo consistente.
Exemplo
Considere estas duas execuções conceituais:
stderr: Erro: registro "A-17" não encontrado.
status: 0stderr: Erro: registro "A-17" não encontrado. Verifique o identificador e tente novamente.
status: 3A segunda é coerente: a pessoa recebe orientação em stderr, e a automação recebe o sinal de falha esperada (3). A primeira mostra um erro, mas comunica sucesso incorretamente.
A CLI recebeu argumentos válidos, mas o identificador solicitado não existe. Qual código ela deve sinalizar segundo o contrato deste tutorial?
Uma execução produz o seguinte resultado:
stderr: Configuração inválida: informe uma porta entre 1 e 65535.
status: 0Qual é o problema?

Passo 3 de 7
Preveja o comportamento padrão do argparse para ajuda, argumentos inválidos e chamadas válidas.
Depois de receber os argumentos, o argparse pode concluir a execução antes de sua operação começar.
--help: mostra a ajuda em stdout e encerra com código 0.Portanto, pedir ajuda é um caminho bem-sucedido de consulta da interface, não uma falha.
Compare o canal, o código e a continuidade de cada chamada.

A operação só é alcançada quando os argumentos passam pela validação do parser.
Salve este exemplo como consulta.py para observar os três caminhos.
import argparse
def criar_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Consulta um registro.")
parser.add_argument("registro_id", type=int)
return parser
def consultar(registro_id: int) -> None:
print(f"Registro consultado: {registro_id}")
def main(argv: list[str] | None = None) -> None:
argumentos = criar_parser().parse_args(argv)
consultar(argumentos.registro_id)
if __name__ == "__main__":
main()
Exemplo
python consulta.py --help
→ ajuda em stdout; código 0; consultar() não é chamada
python consulta.py abc
→ uso e erro em stderr; código 2; consultar() não é chamada
python consulta.py 42
→ "Registro consultado: 42" em stdout; consultar() é chamadaA redação e a formatação da ajuda e do erro podem variar. O contrato importante aqui é canal, código e se a operação foi iniciada.
Dica
Um argumento rejeitado pelo parser é um erro de uso da CLI. Uma falha descoberta depois de consultar(...) começar pertence à operação e terá seu próprio tratamento nos próximos passos.
Na chamada python consulta.py --help, o argparse deve escrever a ajuda em stdout, encerrar com código 0 e não chamar consultar.
Para python consulta.py abc, qual previsão respeita o comportamento padrão do argparse?

Passo 4 de 7
Organize a CLI para que main retorne códigos e o ponto de entrada encerre o processo.
Faça main receber argv opcional e retornar um inteiro quando seu fluxo termina normalmente. Esse inteiro representa o resultado da execução conforme o contrato da CLI.
Porém, return 0 apenas devolve um valor para quem chamou main; ele não encerra o processo com código zero por conta própria.
A separação permite testar e reutilizar main sem que ela finalize o processo durante a chamada.

main retorna o código; a fronteira externa transforma esse retorno no encerramento do processo.
O bloco protegido executa somente quando este arquivo é iniciado diretamente.
import argparse
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("nome")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
print(f"Olá, {args.nome}!")
return 0
if __name__ == "__main__":
raise SystemExit(main())Dica
raise SystemExit(main()) fica no ponto de entrada. Assim, as funções que executam regras da aplicação retornam resultados ou sinalizam suas próprias falhas, sem decidir quando o processo inteiro deve terminar.
Ao interpretar argv, o argparse pode levantar SystemExit: por exemplo, para ajuda (--help) ou argumentos inválidos. Nesse caso, main não chega ao seu return.
SystemExit deriva de BaseException, e não de Exception. Portanto, except Exception não intercepta esse encerramento — e isso é desejável.
Atenção
Não use except: nem except BaseException: em volta de parse_args() ou de toda a CLI. Essas capturas podem engolir o SystemExit do argparse e impedir os códigos e comportamentos esperados de ajuda e erro de uso.
No ponto de entrada, use raise ______(main()) para encerrar o processo com o inteiro retornado por main.
Se uma seção posterior da CLI precisar tratar exceções comuns da aplicação, qual opção não captura o SystemExit que o argparse pode levantar?

Passo 5 de 7
Converta falhas previstas da operação em mensagens claras para o usuário, mantendo stderr e o código de saída contratados.
A CLI é a fronteira que conhece tanto a configuração quanto o caso de uso. É nela que uma falha prevista pelo contrato pode ser convertida em uma orientação para quem executou o comando.
Capture tipos específicos, como ConfiguracaoInvalida e RegistroNaoEncontrado. Não use except ValueError apenas porque uma falha poderia envolver um valor: um ValueError pode ter outra origem e não é automaticamente um erro de uso da CLI.
Para cada falha esperada deste exemplo: escreva a mensagem em stderr, não imprima resultado em stdout e retorne 3.

A exceção conhecida não atravessa a interface sem tradução: ela vira uma mensagem acionável em stderr e o retorno 3.
Dica
Inclua apenas o contexto necessário para agir: a chave de configuração inválida ou o identificador solicitado. Não despeje o conteúdo completo da configuração, credenciais, tokens ou detalhes internos que não ajudam na correção.
As classes representam falhas já previstas pelo domínio e pela configuração. A função main as traduz para a interface de linha de comando.
import sys
class ConfiguracaoInvalida(Exception):
def __init__(self, chave: str):
self.chave = chave
class RegistroNaoEncontrado(Exception):
def __init__(self, identificador: str):
self.identificador = identificador
def carregar_configuracao() -> dict[str, str]:
# Exemplo: a validação anterior detectou uma chave inválida.
raise ConfiguracaoInvalida("diretorio_dados")
def consultar_registro(identificador: str, config: dict[str, str]) -> str:
raise RegistroNaoEncontrado(identificador)
def main(argv: list[str] | None = None) -> int:
identificador = "A-104" # Viria dos argumentos já interpretados.
try:
config = carregar_configuracao()
resultado = consultar_registro(identificador, config)
except ConfiguracaoInvalida as erro:
print(
f"Erro de configuração: revise o valor de '{erro.chave}' e execute novamente.",
file=sys.stderr,
)
return 3
except RegistroNaoEncontrado as erro:
print(
f"Registro '{erro.identificador}' não foi encontrado. "
"Confirme o identificador e tente novamente.",
file=sys.stderr,
)
return 3
print(resultado)
return 0
if __name__ == "__main__":
raise SystemExit(main())Exemplo
Vaga: Erro.
Técnica demais: RegistroNaoEncontrado: A-104 em repositorio._indice[identificador]
Adequada: Registro 'A-104' não foi encontrado. Confirme o identificador e tente novamente.
A última explica o problema, preserva o contexto necessário e oferece uma próxima ação. Ela não expõe a implementação interna.
Atenção
Se uma dessas exceções for capturada, encerre aquele caminho com return 3. Não imprima uma confirmação de sucesso antes nem depois do tratamento da falha.
Um usuário solicitou o registro C-77, e o caso de uso levantou RegistroNaoEncontrado. Descreva onde você capturaria essa exceção e escreva a mensagem. Indique também o canal e o código de retorno. Evite detalhes internos e não use uma captura genérica de ValueError.
Escreva pelo menos 120 caracteres (0/120).

Passo 6 de 7
Diferencie defeitos inesperados de falhas previstas e preserve evidências úteis para investigação.
Uma falha prevista — como configuração inválida ou registro inexistente — já tem uma mensagem e retorno próprios. Um defeito inesperado é diferente: indica algo que precisa ser investigado.
Na fronteira da CLI, mantenha as capturas específicas primeiro. Depois delas, uma captura final de Exception pode informar a falha sem fingir que o problema foi causado pelo argumento do usuário.
O usuário precisa de uma orientação breve; quem investiga precisa do traceback original.

Para um defeito inesperado: stderr recebe a mensagem ao usuário e o diagnóstico identificado; o processo termina com código 1.
Coloque este bloco depois dos tratamentos das falhas esperadas.
import logging
import sys
def main(argv: list[str] | None = None) -> int:
try:
configuracao = carregar_configuracao(argv)
resultado = consultar_registro(configuracao)
except ConfiguracaoInvalida as erro:
print(f"Configuração inválida: {erro}. Revise a chave informada.", file=sys.stderr)
return 3
except RegistroNaoEncontrado as erro:
print(f"Registro não encontrado: {erro}. Confira o identificador.", file=sys.stderr)
return 3
except Exception:
logging.exception("Diagnóstico: defeito inesperado durante a consulta")
print("Não foi possível concluir a operação. Tente novamente mais tarde.", file=sys.stderr)
return 1
else:
print(resultado)
return 0
if __name__ == "__main__":
raise SystemExit(main())Dentro de um bloco except, logging.exception(...) registra a mensagem e o traceback da exceção ativa. Assim, a evidência da origem do defeito não se perde.
A mensagem de print(..., file=sys.stderr) é breve e acionável. Nenhum resultado de sucesso vai para stdout nesse caminho.
Atenção
Não inclua senhas, tokens, conteúdo completo de arquivos de configuração ou dados pessoais no texto do log. Também não afirme que houve recuperação, nem culpe o usuário, sem evidência.
A captura é de Exception, não de BaseException: isso preserva sinais de encerramento como SystemExit.
Após os tratamentos específicos de falhas previstas, qual bloco trata corretamente um defeito inesperado?

Passo 7 de 7
Execute uma CLI autocontida em seis cenários e compare canais, mensagens, diagnóstico e código de saída.
Crie um arquivo chamado consulta.py em uma pasta vazia e cole o código abaixo. Ele contém dados, configuração simulada e um defeito controlado por --bug, para que você possa observar todo o contrato sem depender de um projeto anterior.
A fronteira da CLI está em main: ela imprime o resultado em stdout, traduz falhas previstas para stderr e retorna os códigos contratados. O bloco final é o único ponto que encerra o processo.
import argparse
import logging
import sys
class ConfiguracaoInvalida(Exception):
pass
class RegistroNaoEncontrado(Exception):
pass
REGISTROS = {
"ana": {"nome": "Ana", "cidade": "Recife"},
"bruno": {"nome": "Bruno", "cidade": "Curitiba"},
}
def carregar_configuracao(nome: str) -> None:
if nome == "invalida":
raise ConfiguracaoInvalida(
"a configuração solicitada não passou na validação"
)
def consultar_registro(identificador: str, provocar_bug: bool) -> dict[str, str]:
if provocar_bug:
raise RuntimeError("falha simulada no adaptador de dados")
try:
return REGISTROS[identificador]
except KeyError as erro:
raise RegistroNaoEncontrado(identificador) from erro
def construir_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Consulta um registro local.")
parser.add_argument("identificador", help="identificador do registro")
parser.add_argument(
"--config",
choices=["padrao", "invalida"],
default="padrao",
help="configuração simulada",
)
parser.add_argument(
"--bug",
action="store_true",
help="provoca um defeito inesperado para demonstração",
)
return parser
def main(argv: list[str] | None = None) -> int:
args = construir_parser().parse_args(argv)
try:
carregar_configuracao(args.config)
registro = consultar_registro(args.identificador, args.bug)
except ConfiguracaoInvalida:
print(
"Erro de configuração: revise a configuração solicitada e tente novamente.",
file=sys.stderr,
)
return 3
except RegistroNaoEncontrado as erro:
print(
f"Registro '{erro}' não foi encontrado. Confira o identificador e tente novamente.",
file=sys.stderr,
)
return 3
except Exception:
logging.exception("Diagnóstico técnico: defeito inesperado durante a consulta")
print(
"Ocorreu um erro inesperado. Tente novamente mais tarde.",
file=sys.stderr,
)
return 1
print(f"{registro['nome']} — {registro['cidade']}")
return 0
if __name__ == "__main__":
logging.basicConfig(level=logging.ERROR, format="%(levelname)s: %(message)s")
raise SystemExit(main())
Mesmo que stdout e stderr apareçam juntos no terminal, o redirecionamento abaixo torna a separação visível. O traceback do cenário inesperado é uma evidência técnica; ele não transforma a falha em sucesso.

Resultado, mensagem e código são evidências diferentes do mesmo contrato.
No terminal, entre na pasta que contém consulta.py. Em cada comando, stdout.txt e stderr.txt são substituídos. Consulte o código logo após executar Python: outro comando pode alterar o valor que o shell guarda.
No caso de --help, o argparse encerra com 0 e escreve em stdout. Sem o argumento obrigatório, ele escreve uso e erro em stderr e encerra com 2.
Execute os blocos um de cada vez. Depois de cada execução, os dois cat mostram os canais separados.
# 1. Sucesso: stdout tem o registro; stderr fica vazio; código 0
python consulta.py ana >stdout.txt 2>stderr.txt; codigo=$?
printf 'codigo=%s\n' "$codigo"; cat stdout.txt; cat stderr.txt
# 2. Ajuda: stdout tem a ajuda; stderr fica vazio; código 0
python consulta.py --help >stdout.txt 2>stderr.txt; codigo=$?
printf 'codigo=%s\n' "$codigo"; cat stdout.txt; cat stderr.txt
# 3. Argumento inválido: stderr tem uso e erro; código 2
python consulta.py >stdout.txt 2>stderr.txt; codigo=$?
printf 'codigo=%s\n' "$codigo"; cat stdout.txt; cat stderr.txt
# 4. Configuração inválida: stderr orienta a revisão; código 3
python consulta.py ana --config invalida >stdout.txt 2>stderr.txt; codigo=$?
printf 'codigo=%s\n' "$codigo"; cat stdout.txt; cat stderr.txt
# 5. Registro ausente: stderr orienta conferir o identificador; código 3
python consulta.py carla >stdout.txt 2>stderr.txt; codigo=$?
printf 'codigo=%s\n' "$codigo"; cat stdout.txt; cat stderr.txt
# 6. Defeito controlado: stderr tem diagnóstico e mensagem breve; código 1
python consulta.py ana --bug >stdout.txt 2>stderr.txt; codigo=$?
printf 'codigo=%s\n' "$codigo"; cat stdout.txt; cat stderr.txtExecute os blocos um de cada vez. $LASTEXITCODE é copiado imediatamente para $codigo.
# 1. Sucesso
python .\consulta.py ana 1> stdout.txt 2> stderr.txt; $codigo = $LASTEXITCODE
"codigo=$codigo"; Get-Content stdout.txt; Get-Content stderr.txt
# 2. Ajuda
python .\consulta.py --help 1> stdout.txt 2> stderr.txt; $codigo = $LASTEXITCODE
"codigo=$codigo"; Get-Content stdout.txt; Get-Content stderr.txt
# 3. Argumento inválido
python .\consulta.py 1> stdout.txt 2> stderr.txt; $codigo = $LASTEXITCODE
"codigo=$codigo"; Get-Content stdout.txt; Get-Content stderr.txt
# 4. Configuração inválida
python .\consulta.py ana --config invalida 1> stdout.txt 2> stderr.txt; $codigo = $LASTEXITCODE
"codigo=$codigo"; Get-Content stdout.txt; Get-Content stderr.txt
# 5. Registro ausente
python .\consulta.py carla 1> stdout.txt 2> stderr.txt; $codigo = $LASTEXITCODE
"codigo=$codigo"; Get-Content stdout.txt; Get-Content stderr.txt
# 6. Defeito controlado
python .\consulta.py ana --bug 1> stdout.txt 2> stderr.txt; $codigo = $LASTEXITCODE
"codigo=$codigo"; Get-Content stdout.txt; Get-Content stderr.txtExemplo
| Cenário | stdout | stderr | Código |
|---|---|---|---|
| ana | Ana — Recife | vazio | 0 |
| --help | ajuda do parser | vazio | 0 |
| sem identificador | vazio | uso e erro do parser | 2 |
| ana --config invalida | vazio | orientação para revisar a configuração | 3 |
| carla | vazio | orientação para conferir o identificador | 3 |
| ana --bug | vazio | ERROR: Diagnóstico técnico..., traceback e mensagem breve | 1 |
A redação exata da ajuda e do erro do argparse pode variar entre versões do Python. O essencial é o canal e o código. No último caso, procure tanto a mensagem ao usuário quanto o traceback registrado.
Após executar os seis cenários, relate os canais e códigos que você observou. Inclua especificamente: onde apareceu a ajuda, os códigos das três categorias de falha e o que apareceu em stderr com --bug.
Escreva pelo menos 180 caracteres (0/180).
Resumo
Você reuniu os comportamentos da CLI em evidências observáveis.
main retorna o código; raise SystemExit(main()) o entrega ao processo.Exception registra o traceback e ainda sinaliza falha.Parabéns! Você concluiu: Traduzir falhas em mensagens e códigos de saída
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks