2.41. Depuração¶
A maioria dos scripts que falham na câmara falha de uma de três formas: lançam uma exceção, produzem o valor errado ou ficam suspensos. Cada situação tem um conjunto diferente de ferramentas.
2.41.1. Ler um traceback¶
Quando um script lança uma exceção e nada a trata, o REPL ou o IDE imprime um traceback – um registo da cadeia de chamadas desde o script mais externo até à linha que lançou a exceção.
Um traceback lê-se de baixo para cima:
A linha inferior nomeia a classe da exceção e a sua mensagem (
ValueError: invalid literal for int()...).Cada bloco
File "...", line N, in <name>acima é um fotograma – uma chamada mais profunda à medida que se sobe.O fotograma mais acima é onde o script começou; o fotograma mais abaixo é onde o erro ocorreu.
Leia primeiro a parte inferior para perceber o que correu mal, e depois percorra para cima para ver como o script chegou lá. Os números de linha apontam para localizações exatas no código fonte do script.
2.41.2. Depuração por impressão¶
A forma mais rápida de perceber o que um script está a fazer é imprimir os valores suspeitos. Três funções integradas tornam as impressões mais úteis:
repr()– devolve a representação para o programador de um valor.print(repr(value))distingue"5"de5eNonede"None", o que um simplesprint()não consegue.type()– devolve a classe de um valor.print(type(value))é a forma de descobrir se a variável que «devia ser um int» é secretamente uma string.len()– o comprimento de uma sequência ou coleção. Uma fração surpreendentemente grande dos erros são problemas de desvio de um ou de incompatibilidade de tamanhos.
print("got:", repr(value), "type:", type(value), "len:", len(value))
Coloque uma impressão dentro de cada ramo que lhe interessa – ambos os braços de um if, cada bloco except, o corpo de um ciclo que suspeita de executar zero vezes. O custo é uma linha de saída; o valor é descobrir se o caminho de código que pensa estar a executar é o que está realmente a executar.
2.41.3. Explorar um objeto¶
Duas funções integradas respondem à questão «o que posso fazer com isto»:
dir()– devolve uma lista de todos os nomes definidos num objeto: métodos, atributos, dunders, tudo.help()– imprime a docstring (e no CPython, a assinatura) de uma função, método ou classe.
Use-as em conjunto: dir encontra o nome, help explica o que faz.
2.41.3.1. Encontrar um nome com dir¶
>>> dir([1, 2, 3])
['__add__', '__class__', '__contains__', '__delitem__',
'__eq__', '__ge__', ..., 'append', 'clear', 'copy',
'count', 'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']
O primeiro bloco da lista são os métodos dunder, herdados de todos os objetos; os nomes que vale a pena procurar encontram-se geralmente depois deles. dir funciona com qualquer coisa – uma classe, uma instância, um módulo, um tipo integrado:
>>> import json
>>> dir(json)
['__name__', 'dump', 'dumps', 'load', 'loads']
Esta segunda forma é a maneira de descobrir quais os nomes de nível superior que um módulo realmente expõe sem sair do REPL.
2.41.3.2. Consultar com help¶
Assim que dir encontrar um candidato, help descreve-o:
>>> help(str.split)
split(sep=None, maxsplit=-1)
Return a list of the words in the string, ...
No MicroPython, help é mais simples do que no CPython – por vezes apenas a assinatura, por vezes uma docstring de uma linha, por vezes nada para funções integradas em C. Ainda assim é um lembrete rápido quando o tooltip do IDE não está à mão.
2.41.4. Quando algo fica suspenso¶
Um script que não retorna é mais difícil de diagnosticar do que um que lança uma exceção. Causas comuns:
Um ciclo
whilecuja condição nunca se torna falsa. Adicione uma impressão da variável do ciclo em cada iteração; se o valor não mudar, o corpo do ciclo tem um erro.Uma chamada bloqueante à espera de entrada que nunca chega – uma leitura de uma fila vazia, um sleep sem fim. Delimite a chamada com impressões para ver em que linha o script está bloqueado.
Uma recursão infinita. O traceback quando finalmente disparar (com
RecursionError) normalmente aponta diretamente para ela.
A recuperação mais eficaz para um script suspenso é o botão stop do IDE, que envia um KeyboardInterrupt ao script via USB. A interrupção aparece como um traceback na linha atualmente em execução – muitas vezes é exatamente essa a linha que não está a retornar.
Nota
Se uma suspensão resiste a todos os diagnósticos – o script parece correto, o traceback da interrupção aponta para um integrado ou para código de firmware em vez do seu script, ou o mesmo código funcionava numa versão de firmware anterior – a causa pode ser um erro de firmware e não de script. Reduza o script ao menor reprodutor que ainda fique suspenso e abra um relatório no fórum OpenMV. Inclua a versão do firmware, a placa onde foi executado e o script reduzido.
2.41.5. Remover os diagnósticos antes de distribuir¶
As impressões estratégicas durante o desenvolvimento são ótimas; uma centena de chamadas a print deixadas num script de produção enchem a saída e consomem heap que o trabalho real poderia estar a usar. Quando um erro estiver corrigido, remova as impressões (ou coloque-as por detrás de um indicador de depuração que pode desativar).
Para diagnósticos que devem permanecer no caminho de código a longo prazo, substitua print() pelo módulo logging. Este associa um nível a cada mensagem (debug, info, warning, error) e permite que uma única definição silencie as mais silenciosas em produção:
import logging
log = logging.getLogger("main")
log.info("starting up")
log.debug("loaded config: %s", config)
log.warning("falling back to defaults")
Definir o nível do logger para logging.WARNING faz com que as chamadas info e debug não custem praticamente nada (a string da mensagem nunca é construída), sem ter de comentar linhas. Isto torna o logging a ferramenta certa para diagnósticos permanentes; um print simples é suficiente para os temporários.