Fundamentos
Tabelas-verdade em Python: conferir condições com and, or e not
Monte tabelas-verdade em Python para testar condições com and, or e not, conferir se duas expressões são equivalentes e achar o caso que ficou de fora.
Por Prof. Rodrigo Faustino · Atualizado em · 13 min de leitura
Métricas deste artigo
- Palavras
- 2.430
- Caracteres
- 12.892
- Tempo de leitura
- ~13 min
Uma tabela-verdade lista todas as combinações possíveis de True e False para as variáveis de uma expressão e mostra, ao lado de cada combinação, o resultado da expressão. Com uma variável há duas linhas, com duas há quatro, com três há oito: a conta é 2 elevado ao número de variáveis. Em Python, dá para imprimir uma tabela dessas com poucas linhas de código, e ela serve para três coisas práticas: conferir uma condição antes de usá-la, descobrir se duas expressões que parecem diferentes dizem a mesma coisa e achar o caso que ninguém previu.
O artigo anterior apresentou os operadores and, or e not. Este mostra como confiar neles. Uma condição escrita errada não dá erro, dá o comportamento errado na hora errada, e a tabela é o jeito mais barato de pegar isso antes de o programa chegar perto de uma placa, de um motor ou de um aluno.
O problema: duas condições que parecem a mesma
Um alarme deve disparar quando a porta ou a janela estiver aberta. Na hora de escrever a condição para o caso contrário, o "não dispara", alguém pensa assim: "não dispara quando a porta não está aberta e a janela também não". Outra pessoa, com pressa, escreve: "não dispara quando a porta não está aberta ou a janela não está aberta". As duas frases soam parecidas, e em código ficam assim:
nao_dispara = not (porta or janela)
outra = (not porta) or (not janela)Elas dão o mesmo resultado? A intuição fica dividida, e é exatamente para isso que existe a tabela. Antes de montá-la, vale ver como ela é construída.
Da ementa ao semestre planejado
- Plano de ensino e calendário montados sozinhos
- Diário de aulas pronto para colar no SIGA
- Slides e listas de exercícios com gabarito
Versão Community grátis · Windows
O que é uma tabela-verdade
Cada variável vira uma coluna, e cada linha é uma combinação de valores. A forma de não esquecer nenhuma é contar em binário, com False no lugar do 0 e True no lugar do 1: a última coluna alterna a cada linha, a do meio a cada duas linhas, a primeira a cada quatro.
Cada variável a mais dobra o número de linhas. Duas variáveis dão 4, três dão 8, quatro dão 16, cinco dão 32 e dez dão 1024. Isso explica por que a tabela é uma ferramenta de condições pequenas: com três ou quatro variáveis ela cabe na tela, e com dez já é um trabalho para a máquina.
Para duas variáveis, a tabela dos operadores and e or pode ser escrita linha por linha:
print(2 ** 1, 2 ** 2, 2 ** 3, 2 ** 4, 2 ** 5, 2 ** 10)
print("a b and or")
print(False, False, False and False, False or False)
print(False, True, False and True, False or True)
print(True, False, True and False, True or False)
print(True, True, True and True, True or True)A primeira linha confirma a conta das linhas: 2 4 8 16 32 1024. As quatro seguintes são a tabela em si, e cada uma tem as duas variáveis, o resultado do and e o do or. Só a última linha tem True nas duas colunas, e só a primeira tem False nas duas. Quem escreve a tabela assim, à mão, aprende a ler cada linha, mas o método não escala: com três variáveis seriam oito linhas e com quatro, dezesseis.
Montando a tabela com for aninhado
O for é assunto do módulo de repetição, mas ele resolve este problema tão bem que vale um adiantamento. Em for a in (False, True):, a variável a assume um valor de cada vez, primeiro False e depois True, e o bloco indentado roda uma vez para cada valor. Colocando um for dentro de outro, todas as combinações aparecem:
print(f"{'a':<6}{'b':<6}{'and':<6}or")
for a in (False, True):
for b in (False, True):
print(f"{str(a):<6}{str(b):<6}{str(a and b):<6}{a or b}")A saída é a mesma tabela, agora alinhada em colunas. O :<6 dentro da f-string reserva seis caracteres e alinha o texto à esquerda. O str() em volta de cada valor não é enfeite. Um booleano formatado direto, como em f"{a:<6}", aparece como 1 ou 0 em vez de True ou False, tanto no Python do computador como no simulador. Convertido para texto antes, ele aparece com o nome certo.

Com três variáveis, basta mais um for dentro dos outros. A ordem das linhas segue a mesma contagem binária da figura.
Conferindo se duas expressões são equivalentes
Voltando ao alarme. Duas expressões são equivalentes quando dão o mesmo resultado em todas as linhas da tabela. Em vez de olhar linha por linha, o programa pode comparar as colunas com == e imprimir uma resposta por linha:
for porta in (False, True):
for janela in (False, True):
nao_dispara = not (porta or janela)
certa = (not porta) and (not janela)
errada = (not porta) or (not janela)
print(porta, janela, nao_dispara == certa, nao_dispara == errada)A saída tem quatro linhas: False False True True, False True True False, True False True False e True True True True. A primeira coluna de comparação, nao_dispara == certa, é verdadeira nas quatro linhas: as duas expressões são equivalentes. A segunda, nao_dispara == errada, falha nas linhas em que só um dos lados está aberto, que são justamente as que importam: com a porta aberta e a janela fechada, a versão errada diria que o alarme não dispara, e ele deveria disparar.
Esse resultado é uma das leis de De Morgan, que vale a pena guardar de cabeça: negar um or é negar cada parte e trocar por and, e negar um and é negar cada parte e trocar por or.
for a in (False, True):
for b in (False, True):
print(a, b, not (a and b), (not a) or (not b), not (a or b), (not a) and (not b))As quatro colunas finais da saída mostram o que a figura mostra: as duas primeiras são iguais em todas as linhas (True True, True True, True True e False False), e as duas últimas também (True True na primeira linha e False False nas outras três). O erro mais comum é aplicar só metade da regra, negar as partes e esquecer de trocar o operador. A tabela denuncia isso de imediato.
Outras equivalências úteis
Há equivalências que ajudam a simplificar condições longas. A absorção diz que a or (a and b) é o mesmo que a, e que a and (a or b) também. Se a é verdadeiro, a primeira expressão já é verdadeira, e se a é falso, o and interno é falso e o resultado depende só de a. A tabela confirma, e mostra de quebra o "ou exclusivo", o "ou um ou outro" da fala do dia a dia, que o Python escreve como a != b:
for a in (False, True):
for b in (False, True):
print(a, b, (a or (a and b)) == a, (a and (a or b)) == a, a != b)A saída é False False True True False, False True True True True, True False True True True e True True True True False. As duas colunas do meio são sempre True, e a última é a do "ou exclusivo": verdadeira quando os dois valores são diferentes. O or do Python é inclusivo, ou seja, True or True dá True. Quando a ideia é "um ou o outro, mas não os dois", o != resolve. O operador ^ também existe, mas trabalha com os bits de números inteiros e pode devolver 1 e 0 em vez de True e False, o que o torna uma escolha pior para condições.
A IA que lembra da sua vida e mora no seu computador
- Memória de longo prazo com a fonte de cada resposta
- Funciona offline: seus dados ficam no seu PC
- Lê PDFs, slides, fotos e gravações de reunião
Plano Essencial grátis para sempre
Uma tabela de verdade para um caso real
Uma bomba de irrigação ligada a uma ESP32 deve funcionar quando o solo estiver seco, o tanque tiver água e não estiver chovendo. São três perguntas, e a regra é seco and agua and not chuva. Como são três variáveis, a tabela tem oito linhas.
print(f"{'seco':<6}{'agua':<6}{'chuva':<6}irrigar")
for seco in (False, True):
for agua in (False, True):
for chuva in (False, True):
irrigar = seco and agua and not chuva
print(f"{str(seco):<6}{str(agua):<6}{str(chuva):<6}{irrigar}")
total = 0
for seco in (False, True):
for agua in (False, True):
for chuva in (False, True):
for manual in (False, True):
irrigar = (seco and agua and not chuva) or manual
total += irrigar
print(total, 2 ** 4)A tabela impressa tem uma única linha com True na última coluna, a sétima, em que o solo está seco, há água e não chove. Isso é o que se espera da regra, e é bom conferir, porque a tabela é o único lugar em que o programador vê as sete combinações em que a bomba fica desligada. Uma delas, solo seco e tanque vazio, é a que protege a bomba de trabalhar a seco. Outra, solo seco e chuva, evita um desperdício de água.
O segundo trecho acrescenta uma variável, manual, que liga a bomba à força. Agora são 16 linhas, e a conta com total += irrigar conta quantas dão True: o booleano se soma como 1 e 0. A saída é 9 16. Das 16 combinações, 8 têm manual verdadeiro e ligam a bomba de qualquer jeito, e mais 1 liga pela regra original, o que dá 9. É um jeito de conferir a lógica sem montar nada: se o número de linhas verdadeiras não bater com o que você esperava, a condição está errada.
O que muda na ESP32
As operações lógicas se comportam igual no MicroPython e no Python do computador. O que muda é de onde vêm os valores. Numa placa, quase nenhuma variável nasce como booleano: o pin.value() devolve o número 0 ou 1, e as comparações com sensores (temperatura > 30) é que produzem True e False. Para a tabela, cada comparação vira uma variável, e o que importa é testar a lógica sem precisar ligar nada.
O caso do botão com resistor de pull-up merece uma tabela só dele. Com o pull-up, o pino lê 1 quando o botão está solto e 0 quando está apertado, o contrário do que a intuição pede:
for valor in (0, 1):
print(valor, valor == 0, not valor, bool(valor), valor == True)A saída é 0 True True False False e 1 False False True True. Quando o pino vale 0, valor == 0 e not valor são ambos verdadeiros, e é assim que se escreve "o botão está pressionado": pressionado = pino.value() == 0. As duas últimas colunas mostram que o 1 também se comporta como True numa comparação, e o 0 como False. O artigo sobre pull-up e pino flutuante explica por que a lógica fica invertida e como evitar leituras instáveis.
Em sala de aula: onde as tabelas costumam aparecer
Os pontos abaixo se repetem sempre que uma turma começa a escrever condições, e cada um se resolve olhando a tabela.
O "e" do português que vira "ou" no código
A regra "maiores de 65 anos e menores de 12 anos pagam meia" soa natural, e a tradução para o Python cria uma condição que nunca é verdadeira. Em português, o "e" junta dois grupos de pessoas. Em lógica, uma pessoa não pode ter mais de 65 e menos de 12 ao mesmo tempo, e quem junta grupos está dizendo or.
for idade in (8, 30, 70):
print(idade, idade >= 65 and idade < 12, idade >= 65 or idade < 12)
for x in (3, 5, 9):
print(x, x < 5 or x >= 5, x < 5 and x > 10)A primeira parte da saída é 8 False True, 30 False False e 70 False True. A coluna do and é falsa para todas as idades, e a do or dá o resultado certo: crianças de 8 e idosos de 70 pagam meia, adultos de 30 não. A segunda parte mostra o outro defeito clássico, a condição que nunca muda de valor. x < 5 or x >= 5 é verdadeira para qualquer x, e x < 5 and x > 10 é falsa para qualquer x. Os valores 3 True False, 5 True False e 9 True False confirmam. Uma condição assim nunca decide nada, e a tabela deixa isso evidente.
O "ou" que na conversa é exclusivo
"Você quer café ou chá?" quase nunca quer dizer "pode ser os dois". O or do Python é inclusivo e aceita os dois. Quem precisa do "um ou o outro" usa !=, como na seção acima, e confere na tabela que a linha em que os dois são verdadeiros fica falsa.
A tabela com linhas faltando
Com três variáveis, a tabela precisa de oito linhas, e é comum entregar seis, porque a pessoa montou as combinações de cabeça e pulou duas. A defesa é contar em binário, como na figura do início, e conferir a conta com 2 ** n.
O not distribuído pela metade
A armadilha da figura de De Morgan aparece em quase todo código escrito com pressa: nega-se cada termo, mas o and ou o or fica como estava. O programa roda, não dá erro, e a condição vale para metade dos casos. É o tipo de defeito que só aparece quando alguém testa justamente a combinação que a versão errada trata mal.
O caso em que nada vale
Para cada decisão com várias saídas, a pergunta que a tabela faz sem ser chamada é: e quando nenhuma das condições for verdadeira? O robô do artigo anterior, que desvia para a esquerda, para a direita ou para, só é seguro porque alguém desenhou a linha em que os dois lados estão bloqueados. Quem monta a tabela vê essa linha. Quem escreve só os casos "felizes" nem sabe que ela existe.
Exercício: o portão da garagem
Um portão abre quando alguém aperta o controle ou o sensor detecta um carro, mas nunca se houver um obstáculo na frente. Três pessoas escreveram a condição de jeitos diferentes:
- A:
(controle or carro) and not obstaculo - B:
(controle and not obstaculo) or (carro and not obstaculo) - C:
controle or carro and not obstaculo
Antes de rodar, aposte quais são equivalentes. Depois monte a tabela e conte as linhas em que a A e a C discordam.
dif = 0
for controle in (False, True):
for carro in (False, True):
for obstaculo in (False, True):
a = (controle or carro) and not obstaculo
b = (controle and not obstaculo) or (carro and not obstaculo)
c = controle or carro and not obstaculo
dif += a != c
print(controle, carro, obstaculo, a, b, c, a == b)
print(dif)A saída tem oito linhas e termina em 2. Na última coluna, a == b é True em todas as oito: a B é só a A com o not obstaculo distribuído, uma propriedade chamada distributiva. A C é outra história. Sem os parênteses, o and é resolvido antes, e a expressão vira controle or (carro and not obstaculo). O contador dif fecha em 2, nas linhas em que o controle está apertado e há obstáculo: a A mantém o portão fechado, e a C abre. É o tipo de erro que, numa garagem de verdade, prende um carro ou uma pessoa. Repare também no dif += a != c: o resultado da comparação, um booleano, é somado como 1 ou 0, e o contador conta as discordâncias.
Perguntas frequentes
Quantas linhas tem uma tabela-verdade?
2 elevado ao número de variáveis. Com 3 variáveis são 8 linhas e com 4 são 16. Cada variável nova dobra a tabela.
Preciso montar a tabela na mão?
Para duas variáveis, à mão é rápido. A partir de três, vale deixar o Python gerar com for aninhados, como nos exemplos acima, porque o computador não esquece linhas.
Posso usar 0 e 1 no lugar de False e True?
Nas operações, sim: o zero conta como falso e qualquer outro número como verdadeiro. Mas o and e o or devolvem um dos operandos, e a tabela vai mostrar números onde você esperava True e False. Prefira os booleanos.
Por que o str() dentro da f-string?
Porque um booleano formatado com largura, como f"{a:<6}", aparece como 1 ou 0. Convertido com str(a), aparece como True ou False.
O Python tem um operador xor?
Não com esse nome. Para dois booleanos, a != b faz o "ou exclusivo". O ^ é um operador bit a bit e pode devolver número em vez de booleano.
Para continuar
A trilha Lógica com Python tem desafios que colocam essas condições para funcionar. O Maior de idade e o Intervalo de 10 a 20 cobram comparações e faixas, o Aprovação na escola junta duas condições com and, e o Nível de acesso pede uma regra com vários casos, em que montar a tabela antes ajuda muito. Se alguma comparação ou operador ficou solto, o artigo sobre operadores relacionais e lógicos é o ponto de partida desta série.
O próximo assunto leva essas condições para o programa: a estrutura if e else, que escolhe um caminho de acordo com o valor de uma expressão como as que acabamos de testar.