9. Combinando Condições com Lógica Booleana
No Capítulo 7, aprendemos sobre valores booleanos e condições simples usando operadores de comparação. No Capítulo 8, usamos essas condições para tomar decisões com instruções if. Mas programas do mundo real muitas vezes precisam verificar várias condições ao mesmo tempo. Devemos conceder acesso se o usuário tem a senha correta e está logado? Devemos mostrar um aviso se a temperatura está quente demais ou fria demais? Devemos prosseguir se o arquivo não está vazio?
O Python fornece três operadores lógicos (logical operators) que permitem combinar e modificar valores booleanos: and, or e not. Esses operadores são os blocos de construção para expressar uma lógica de tomada de decisão complexa nos seus programas.
9.1) Operadores Lógicos and, or e not
Os três operadores lógicos trabalham com valores booleanos (ou valores que podem ser tratados como booleanos) para produzir novos resultados booleanos.
9.1.1) O Operador and
O operador and retorna True apenas quando ambos os operandos são verdadeiros. Se qualquer operando for falso, a expressão inteira é falsa.
# Ambas as condições devem ser verdadeiras
age = 25
has_license = True
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: True
# Se qualquer condição for falsa, o resultado é False
age = 18
can_rent_car = age >= 21 and has_license
print(can_rent_car) # Output: FalsePense no and como um porteiro rigoroso: todas as condições precisam passar para que a verificação geral seja bem-sucedida.
Tabela Verdade para and:
| Operando da Esquerda | Operando da Direita | Resultado |
|---|---|---|
True | True | True |
True | False | False |
False | True | False |
False | False | False |
9.1.2) O Operador or
O operador or retorna True quando pelo menos um operando é verdadeiro. Ele só retorna False quando ambos os operandos são falsos.
# Pelo menos uma condição deve ser verdadeira
is_weekend = True
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: True
# Ambas as condições falsas
is_weekend = False
is_holiday = False
can_sleep_in = is_weekend or is_holiday
print(can_sleep_in) # Output: FalsePense no or como um porteiro flexível: você só precisa satisfazer uma condição para passar.
Tabela Verdade para or:
| Operando da Esquerda | Operando da Direita | Resultado |
|---|---|---|
True | True | True |
True | False | True |
False | True | True |
False | False | False |
Aqui vai um exemplo prático para um sistema de elegibilidade para desconto:
# Cliente ganha desconto se for estudante OU idoso
age = 68
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: True
# Outro cliente
age = 30
is_student = False
gets_discount = is_student or age >= 65
print(f"Eligible for discount: {gets_discount}") # Output: Eligible for discount: FalseO primeiro cliente se qualifica porque atende a um dos critérios (idoso), mesmo não sendo estudante.
9.1.3) O Operador not
O operador not é um operador unário (unary operator) (ele trabalha em um único operando) que inverte um valor booleano. Ele transforma True em False e False em True.
is_raining = False
is_sunny = not is_raining
print(is_sunny) # Output: True
is_raining = True
is_sunny = not is_raining
print(is_sunny) # Output: FalseTabela Verdade para not:
| Operando | Resultado |
|---|---|
True | False |
False | True |
O operador not é particularmente útil quando você quer verificar o oposto de uma condição:
# Verificar se um arquivo NÃO está vazio
file_size = 0
is_empty = file_size == 0
is_not_empty = not is_empty
print(f"File has content: {is_not_empty}") # Output: File has content: False
# Verificar se o usuário NÃO está logado
is_logged_in = False
needs_login_prompt = not is_logged_in
print(f"Show login prompt: {needs_login_prompt}") # Output: Show login prompt: True9.1.4) Combinando Vários Operadores Lógicos
Você pode combinar vários operadores lógicos em uma única expressão para construir condições mais sofisticadas:
# Loja online: frete grátis se o pedido for acima de $50 OU o cliente for membro premium
# E os itens estiverem em estoque
order_total = 45.00
is_premium = True
in_stock = True
gets_free_shipping = (order_total >= 50 or is_premium) and in_stock
print(f"Free shipping: {gets_free_shipping}") # Output: Free shipping: TrueVamos acompanhar essa avaliação:
order_total >= 50é avaliado comoFalse(45.00 não é >= 50)is_premiuméTrueFalse or Trueé avaliado comoTruein_stockéTrueTrue and Trueé avaliado comoTrue
Aqui vai outro exemplo com controle de acesso:
# Usuário pode acessar o painel de admin se ele for admin
# E (estiver na rede interna OU usando VPN)
is_admin = True
on_internal_network = False
using_vpn = True
can_access_admin = is_admin and (on_internal_network or using_vpn)
print(f"Can access admin panel: {can_access_admin}") # Output: Can access admin panel: TrueRepare nos parênteses em torno de (on_internal_network or using_vpn). Eles são importantes porque controlam a ordem de avaliação, assim como parênteses em expressões aritméticas.
9.2) Precedência de Operadores em Expressões Booleanas (Ordem de Not, And, Or)
Quando você combina vários operadores lógicos sem parênteses, o Python segue regras específicas de precedência para determinar a ordem de avaliação. Entender essas regras ajuda você a escrever condições corretas e evitar bugs sutis.
9.2.1) A Hierarquia de Precedência
O Python avalia os operadores lógicos nesta ordem (da maior para a menor precedência):
not(maior precedência)and(precedência intermediária)or(menor precedência)
Isso significa que not é avaliado primeiro, depois and e, por fim, or.
# Sem parênteses, a precedência determina a ordem
result = True or False and False
print(result) # Output: True
# Como o Python avalia isso:
# Passo 1: False and False → False (and tem precedência maior que or)
# Passo 2: True or False → TrueVamos ver isso passo a passo com um exemplo mais detalhado:
is_weekend = False
is_holiday = True
has_work = True
# Expressão: not has_work or is_weekend and is_holiday
free_time = not has_work or is_weekend and is_holiday
# Ordem de avaliação:
# Passo 1: not has_work → not True → False
# Passo 2: is_weekend and is_holiday → False and True → False
# Passo 3: False or False → False
print(f"Has free time: {free_time}") # Output: Has free time: False9.2.2) Usando Parênteses para Clareza
Mesmo quando você entende as regras de precedência, usar parênteses deixa seu código mais claro e evita erros. Os parênteses sobrescrevem a precedência padrão e deixam suas intenções explícitas.
# Ambíguo sem parênteses
result = True or False and False
print(result) # Output: True
# Claro com parênteses - o que a gente realmente quis dizer?
result = (True or False) and False
print(result) # Output: False
result = True or (False and False)
print(result) # Output: TrueEssas duas expressões produzem resultados diferentes! Os parênteses mudam completamente o significado.
9.2.3) Operadores de Comparação e Operadores Lógicos Juntos
Operadores de comparação (como <, >, ==, !=) têm maior precedência do que operadores lógicos. Isso significa que as comparações são avaliadas antes das operações lógicas.
age = 25
income = 50000
# Não são necessários parênteses ao redor das comparações
eligible = age >= 18 and income >= 30000
print(f"Eligible for loan: {eligible}") # Output: Eligible for loan: True
# O Python avalia como:
# Passo 1: age >= 18 → True
# Passo 2: income >= 30000 → True
# Passo 3: True and True → True9.3) Avaliação de Curto-Circuito
O Python usa avaliação de curto-circuito (short-circuit evaluation) ao avaliar expressões booleanas com and e or. Isso significa que o Python para de avaliar assim que ele já sabe o resultado final, possivelmente pulando a avaliação de operandos posteriores. Esse comportamento é tanto uma otimização de performance quanto uma técnica útil de programação.
9.3.1) Como o and Faz Curto-Circuito
Com o operador and, se o operando da esquerda é False, o Python sabe que a expressão inteira deve ser False (porque ambos os operandos precisam ser verdadeiros para and retornar True). Portanto, o Python não avalia o operando da direita.
# Demonstração simples
x = 5
result = x < 3 and x > 10
print(result) # Output: False
# Avaliação do Python:
# Passo 1: x < 3 → 5 < 3 → False
# Passo 2: Como o lado esquerdo é False, não avalie x > 10
# Passo 3: Retorne FalseAqui vai um exemplo prático mostrando por que a avaliação de curto-circuito importa:
# Verificando se um número é divisível - evitando divisão por zero
numerator = 100
denominator = 0
# Isto é seguro por causa da avaliação de curto-circuito
# Se denominator for 0, a divisão nunca acontece
is_divisible = denominator != 0 and numerator % denominator == 0
print(f"Is divisible: {is_divisible}") # Output: Is divisible: False
# Sem avaliação de curto-circuito, isso causaria um erro:
# denominator = 0
# result = numerator % denominator # ZeroDivisionError!A expressão denominator != 0 é avaliada como False, então o Python nunca avalia numerator % denominator, o que causaria um erro de divisão por zero.
Vamos ver outro exemplo com operações de string:
# Verificando propriedades de strings com segurança
text = ""
# Verifique se text não está vazio E se o primeiro caractere é maiúsculo
# Seguro porque, se text estiver vazio, nunca tentamos acessar text[0]
has_uppercase_start = len(text) > 0 and text[0].isupper()
print(f"Starts with uppercase: {has_uppercase_start}") # Output: Starts with uppercase: False
# Se tentássemos isso sem a verificação de tamanho:
# text = ""
# result = text[0].isupper() # IndexError: string index out of range9.3.2) Como o or Faz Curto-Circuito
Com o operador or, se o operando da esquerda é True, o Python sabe que a expressão inteira deve ser True (porque pelo menos um operando verdadeiro é suficiente). Portanto, o Python não avalia o operando da direita.
# Demonstração simples
x = 15
result = x > 10 or x < 5
print(result) # Output: True
# Avaliação do Python:
# Passo 1: x > 10 → 15 > 10 → True
# Passo 2: Como o lado esquerdo é True, não avalie x < 5
# Passo 3: Retorne True9.3.3) Aplicações Práticas da Avaliação de Curto-Circuito
Evitando Erros:
# Acessando elementos de uma lista com segurança
numbers = [1, 2, 3]
index = 5
# Verifique se index é válido antes de acessar
is_valid = index < len(numbers) and numbers[index] > 0
print(f"Valid and positive: {is_valid}") # Output: Valid and positive: False
# Sem curto-circuito, isso quebraria:
# is_valid = numbers[index] > 0 # IndexError!Verificando Várias Condições com Eficiência:
# Validação de formulário - pare no primeiro erro
email = "user@example.com"
password = "pass"
age = 25
# Verifique cada requisito na ordem de maior probabilidade de falhar
valid_form = (
len(email) > 0 and # Verificação rápida
"@" in email and # Verificação rápida
len(password) >= 8 and # Verificação rápida
age >= 18 # Verificação rápida
)
print(f"Form valid: {valid_form}") # Output: Form valid: False
# Para na verificação do tamanho da senha, não avalia age9.4) O Que os Operadores and e or Retornam com Operandos Não Booleanos, e Armadilhas Comuns em Expressões Booleanas
Até agora, vimos and, or e not funcionando com valores booleanos. Mas os operadores lógicos do Python têm um comportamento interessante: eles podem trabalhar com quaisquer valores, não apenas True e False. Entender esse comportamento ajuda você a escrever código mais conciso e evitar erros comuns.
9.4.1) Entendendo Truthiness e Falsiness (Revisão)
Como aprendemos no Capítulo 7, o Python trata muitos valores não booleanos como "truthy" ou "falsy" em contextos booleanos:
Valores falsy (tratados como False):
FalseNone0(zero de qualquer tipo numérico)""(string vazia)[](lista vazia){}(dicionário vazio)()(tupla vazia)
Valores truthy (tratados como True):
True- Qualquer número diferente de zero
- Qualquer string não vazia
- Qualquer coleção não vazia
# Demonstrando truthiness
if "hello":
print("Strings não vazias são truthy") # Output: Non-empty strings are truthy
if 0:
print("Isso não vai imprimir") # Zero é falsy
else:
print("Zero é falsy") # Output: Zero is falsy
if [1, 2, 3]:
print("Listas não vazias são truthy") # Output: Non-empty lists are truthy9.4.2) O Que and Realmente Retorna
O operador and nem sempre retorna True ou False. Em vez disso, ele retorna um dos seus operandos:
- Se o operando da esquerda é falsy,
andretorna o operando da esquerda (sem avaliar o da direita) - Se o operando da esquerda é truthy,
andretorna o operando da direita
# and retorna o primeiro valor falsy, ou o último valor se todos forem truthy
result = 5 and 10
print(result) # Output: 10
result = 0 and 10
print(result) # Output: 0
result = "hello" and "world"
print(result) # Output: world
result = "" and "world"
print(result) # Output: (empty string)
result = None and "world"
print(result) # Output: NoneVamos acompanhar esses exemplos:
# Exemplo 1: Ambos truthy
result = 5 and 10
# Passo 1: 5 é truthy, então avalie o lado direito
# Passo 2: Retorne o valor do lado direito: 10
print(result) # Output: 10
# Exemplo 2: Lado esquerdo é falsy
result = 0 and 10
# Passo 1: 0 é falsy, então retorne-o imediatamente
# Passo 2: Não avalie o lado direito
print(result) # Output: 0
# Exemplo 3: Ambas strings truthy
result = "hello" and "world"
# Passo 1: "hello" é truthy, então avalie o lado direito
# Passo 2: Retorne o valor do lado direito: "world"
print(result) # Output: world9.4.3) O Que or Realmente Retorna
Da mesma forma, o operador or retorna um dos seus operandos:
- Se o operando da esquerda é truthy,
orretorna o operando da esquerda (sem avaliar o da direita) - Se o operando da esquerda é falsy,
orretorna o operando da direita
# or retorna o primeiro valor truthy, ou o último valor se todos forem falsy
result = 5 or 10
print(result) # Output: 5
result = 0 or 10
print(result) # Output: 10
result = "" or "default"
print(result) # Output: default
result = "hello" or "world"
print(result) # Output: hello
result = None or 0
print(result) # Output: 0Vamos acompanhar esses exemplos:
# Exemplo 1: Lado esquerdo é truthy
result = 5 or 10
# Passo 1: 5 é truthy, então retorne-o imediatamente
# Passo 2: Não avalie o lado direito
print(result) # Output: 5
# Exemplo 2: Lado esquerdo é falsy
result = 0 or 10
# Passo 1: 0 é falsy, então avalie o lado direito
# Passo 2: Retorne o valor do lado direito: 10
print(result) # Output: 10
# Exemplo 3: Ambos falsy
result = None or 0
# Passo 1: None é falsy, então avalie o lado direito
# Passo 2: Retorne o valor do lado direito: 0 (mesmo que ele também seja falsy)
print(result) # Output: 09.4.4) Usos Práticos de or para Valores Padrão
Um padrão comum é usar or para fornecer valores padrão:
# Preferências do usuário com padrões
user_theme = "" # O usuário não configurou um tema
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: light
user_theme = "dark"
theme = user_theme or "light"
print(f"Theme: {theme}") # Output: Theme: dark
# Valores de configuração
max_retries = None # Não configurado
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 3
max_retries = 5
retries = max_retries or 3
print(f"Retries: {retries}") # Output: Retries: 5Esse padrão funciona porque, se o lado esquerdo for falsy (string vazia, None, 0, etc.), or retorna o lado direito (o valor padrão).
9.4.12) Resumo do Que os Operadores Retornam
Aqui está um resumo completo do que cada operador lógico retorna:
Operador and:
- Retorna o primeiro operando falsy
- Se todos os operandos forem truthy, retorna o último operando
- Usa avaliação de curto-circuito (para no primeiro valor falsy)
Operador or:
- Retorna o primeiro operando truthy
- Se todos os operandos forem falsy, retorna o último operando
- Usa avaliação de curto-circuito (para no primeiro valor truthy)
Operador not:
- Sempre retorna um booleano (
TrueouFalse) notconverte o operando para booleano e então nega ele
# Demonstrando os três operadores
print(5 and 10) # Output: 10 (both truthy, return last)
print(0 and 10) # Output: 0 (first falsy, return it)
print(5 or 10) # Output: 5 (first truthy, return it)
print(0 or 10) # Output: 10 (first falsy, evaluate second)
print(not 5) # Output: False (5 is truthy, not returns boolean)
print(not 0) # Output: True (0 is falsy, not returns boolean)
print(not "") # Output: True (empty string is falsy)
print(not "hello") # Output: False (non-empty string is truthy)Entender esses comportamentos ajuda você a escrever código mais conciso e Pythonic, mas sempre priorize clareza. Se usar esses recursos deixar seu código mais difícil de entender, é melhor ser explícito.
Neste capítulo, exploramos como combinar condições simples em lógica booleana complexa usando os operadores and, or e not do Python. Aprendemos sobre a precedência de operadores, avaliação de curto-circuito e o comportamento surpreendente de operadores lógicos com valores não booleanos. Também examinamos armadilhas comuns e melhores práticas para escrever expressões booleanas claras e corretas.
Essas ferramentas permitem que você expresse uma lógica sofisticada de tomada de decisão nos seus programas. Combinadas com as instruções if do Capítulo 8, agora você consegue lidar com praticamente qualquer lógica condicional que seus programas precisarem. No próximo capítulo, vamos explorar expressões condicionais, que fornecem uma forma compacta de escolher entre dois valores com base em uma condição.