Skip to content
This page has been auto-translated and may contain errors.View in English

Controle de fluxo

Todo programa que você escreveu até agora é executado da mesma forma todas as vezes: de cima para baixo, uma linha por vez. Isso funciona para scripts simples, mas programas reais precisam tomar decisões e repetir tarefas. Um quiz precisa verificar se a resposta está correta. Um jogo precisa continuar rodando até o jogador vencer ou perder. Este capítulo aborda como fazer seu programa ramificar e repetir.

O controle de fluxo molda o caminho de execução do seu programa. Condições (if/elif/else) selecionam entre ramos. Laços (while, for) repetem blocos. O laço for do Python é um protocolo de iterador, não um contador baseado em índice. Compreender tanto a sintaxe quanto os modelos subjacentes torna seu código mais limpo.

As primitivas de controle de fluxo do Python são if/elif/else, while e for. for invoca __iter__ no iterável e chama next() até StopIteration. while avalia um objeto de condição via __bool__ ou __len__. break e continue afetam o laço delimitador mais próximo; o loop-else executa apenas quando o laço se esgota sem um break.

Comparações

Antes de poder tomar uma decisão, você precisa comparar coisas. Operadores de comparação retornam True ou False. O mais importante de acertar logo cedo: = atribui um valor, == verifica se dois valores são iguais. Confundir os dois é um dos erros mais comuns para iniciantes.

Operadores de comparação chamam os métodos dunder correspondentes (__eq__, __lt__, etc.) e retornam um bool. O Python suporta comparações encadeadas: 0 < x < 10 é avaliado como 0 < x and x < 10, não da esquerda para a direita como na maioria das linguagens. A comparação de strings é lexicográfica, baseada em pontos de código Unicode.

Operadores de comparação chamam métodos de comparação rica: __eq__, __ne__, __lt__, __le__, __gt__, __ge__. O Python suporta comparações encadeadas com curto-circuito: a < b < c torna-se a < b and b < c, mas b é avaliado apenas uma vez. is verifica a identidade do objeto (igualdade de id()), não igualdade de valor; use == para comparações de valor e is apenas para None, True, False.

python
5 > 3     # True
5 < 3     # False
5 == 5    # True   (observação: igual duplo; = é atribuição, == é comparação)
5 != 3    # True   ("diferente de")
5 >= 5    # True   ("maior ou igual a")
5 <= 4    # False  ("menor ou igual a")

A distinção entre = e == confunde quase todo mundo no início. A atribuição (=) armazena um valor; a comparação (==) verifica se dois valores são iguais.

Você também pode comparar strings. O Python as compara alfabeticamente:

python
"apple" == "apple"   # True
"apple" < "banana"   # True  (a vem antes de b)
"apple" == "Apple"   # False (sensível a maiúsculas/minúsculas)

Combinando condições

and, or e not combinam comparações. and exige que ambos os lados sejam verdadeiros. or exige pelo menos um dos lados. not inverte o resultado. Eles permitem expressar condições do mundo real como "nota está aprovada E usuário está ativo".

and e or fazem curto-circuito: and para no primeiro operando falsy, or para no primeiro operando truthy. Eles retornam o valor real do operando, não apenas True ou False. not chama __bool__ no operando e retorna o resultado invertido.

and avalia da esquerda para a direita e retorna o primeiro operando falsy, ou o último operando se todos forem truthy. or retorna o primeiro operando truthy, ou o último se todos forem falsy. Esse comportamento de curto-circuito é uma garantia: o lado direito não é avaliado se o lado esquerdo determinar o resultado. not x é equivalente a True if not bool(x) else False, mas o Python otimiza isso.

python
age   = 25
score = 88

age >= 18 and score >= 80    # True  (ambos devem ser verdadeiros)
age < 18 or score >= 80      # True  (pelo menos um deve ser verdadeiro)
not age >= 18                # False (inverte o resultado)

and exige ambos os lados. or exige pelo menos um lado. not inverte.

Truthy e falsy

Todo valor em Python tem uma interpretação booleana, mesmo que não seja True ou False. Strings vazias, zero, listas vazias e None se comportam como False em uma condição. Todo o resto se comporta como True. Isso significa que if results: verifica se uma lista não está vazia sem precisar escrever if len(results) > 0:.

Regras de truthiness do Python: valores falsy são False, 0, 0.0, "", [], (), {}, set() e None. Todo o resto é truthy. As condições chamam __bool__ no objeto, recorrendo a __len__ se __bool__ não estiver definido. Um objeto com __len__ de comprimento zero é falsy.

O teste de verdade chama __bool__. Se __bool__ não estiver definido, o Python recorre a __len__: um objeto com __len__ == 0 é falsy. Classes personalizadas controlam a truthiness implementando um desses métodos. Os valores falsy padrão são: False, 0, 0.0, 0j, "", b"", [], (), {}, set(), frozenset(), None e qualquer objeto cujo __bool__ retorne False ou __len__ retorne 0.

python
# Todos estes se comportam como False em uma condição:
False, 0, 0.0, "", [], {}, (), None

# Todo o resto se comporta como True

Isso significa que if results: é uma forma natural de dizer "se a lista não está vazia", e if name: verifica se uma string tem algum conteúdo.

if / elif / else

A instrução if executa um bloco de código apenas quando sua condição é True. elif adiciona mais condições para verificar caso a primeira tenha sido falsa. else captura tudo que não correspondeu a nenhuma condição. O Python usa indentação, não chaves, para definir o que pertence a cada bloco.

if/elif/else avalia as condições de cima para baixo e executa o primeiro bloco correspondente. O Python usa indentação (4 espaços por convenção) para definir o escopo do bloco; indentação inconsistente gera um SyntaxError. Apenas um ramo é executado: assim que uma condição corresponde, todos os elif e else subsequentes são ignorados.

O Python usa indentação como delimitadores de bloco, aplicada pelo parser. O interpretador gera bytecodes SETUP_BLOCK para cada ramo; exatamente um ramo é executado. elif é açúcar sintático: evita o aninhamento que uma cadeia de instruções if simples criaria. Cada condição é avaliada de forma preguiçosa, somente se todas as condições anteriores forem falsy.

python
score = 87

if score >= 90:
    print("A grade")
elif score >= 80:
    print("B grade")
elif score >= 70:
    print("C grade")
else:
    print("Below C")

As regras:

  • if é obrigatório e sempre vem primeiro
  • elif (abreviação de "else if") é opcional e você pode ter quantos precisar
  • else é opcional, lida com tudo que não correspondeu e vem por último
  • O Python usa indentação (4 espaços) para marcar o que pertence a cada bloco; não há chaves

A indentação não é opcional nem cosmética. O Python a usa para definir estrutura. Indentação inconsistente é um erro de sintaxe.

Condições em uma linha

Para atribuições simples de sim/não, o Python tem uma forma compacta em uma linha chamada expressão ternária: valor_se_verdadeiro if condição else valor_se_falso. Use-a apenas quando a lógica é genuinamente simples e se lê como uma frase.

A expressão condicional (operador ternário) avalia para um de dois valores com base em uma condição. É uma expressão, não uma instrução, então pode aparecer em qualquer lugar onde um valor é esperado: dentro de uma f-string, como argumento de função, em uma atribuição. Use-a para casos simples de sim/não; para qualquer coisa envolvendo elif, escreva a versão completa.

A expressão condicional x if condition else y é uma única expressão que avalia a condição e retorna x ou y sem executar o outro ramo. Ela é mapeada para uma combinação de bytecodes POP_JUMP_IF_FALSE. Diferente dos blocos if/else, ela não pode abranger várias instruções e não pode conter elif; para ramificações complexas, blocos if/elif/else completos são mais claros.

python
label = "pass" if score >= 50 else "fail"

Esta é uma expressão ternária; lê-se como uma frase. Use-a quando a lógica é genuinamente simples. Para qualquer coisa envolvendo elif, escreva a versão completa.

Laços while

Um laço while repete seu bloco enquanto sua condição é True. Use-o quando você não sabe de antemão quantas vezes o laço deve rodar, por exemplo, esperando por entrada válida ou tentando novamente até um trabalho ter sucesso.

while avalia sua condição antes de cada iteração e executa o bloco apenas quando a condição é truthy. Use-o para laços em que a condição de saída depende de algo que muda dentro do laço. Quando o número de iterações é conhecido ou você está iterando uma coleção, for geralmente é mais limpo.

while chama __bool__ (ou __len__) na expressão de condição antes de cada iteração. O corpo pode modificar a condição. while True com um break interno é o padrão idiomático de "laço-até" quando a condição de saída precisa ser avaliada no meio ou no fim do corpo. Laços infinitos sem um break são uma fonte comum de travamentos.

python
lives = 3

while lives > 0:
    print(f"Lives remaining: {lives}")
    lives -= 1

print("Game over")

while é melhor quando você não sabe de antemão quantas vezes o laço será executado. Quando você sabe, ou quando está iterando sobre uma coleção, for é mais limpo.

break e continue

break sai do laço imediatamente, não importa quantas iterações restem. continue pula o restante da iteração atual e volta para a verificação da condição. Ambos afetam apenas o laço mais interno em que estão.

break encerra o laço delimitador mais próximo, transferindo o controle para a primeira instrução após ele. continue pula o restante do corpo do laço atual e reinicia a partir da verificação da condição (ou da próxima iteração em um laço for). Ambos afetam apenas o laço delimitador mais interno.

break emite um bytecode BREAK_LOOP que sai do bloco de código do laço e pula qualquer cláusula else associada. continue emite CONTINUE_LOOP (ou JUMP_ABSOLUTE dependendo do contexto), retomando a partir do cabeçalho do laço. Ambos têm escopo no laço delimitador mais próximo; não há break rotulado em Python. Para sair de laços aninhados, use uma flag booleana ou reestruture em uma função com return.

break sai do laço imediatamente:

python
target = 5
num    = 0

while True:
    num += 1
    if num == target:
        print(f"Found {target}")
        break   # para o laço

while True: com um break é um padrão válido e comum quando a condição de saída é complexa ou precisa acontecer no final do corpo do laço.

continue pula o restante da iteração atual e volta para a verificação da condição:

python
num = 0

while num < 10:
    num += 1
    if num % 2 == 0:
        continue    # pula números pares
    print(num)      # apenas números ímpares são impressos: 1, 3, 5, 7, 9

Laços for

Um laço for percorre uma sequência um item por vez: uma lista, uma string, um intervalo de números. A variável que você nomeia após for recebe cada item por sua vez. Você não gerencia um contador nem verifica o comprimento.

for invoca iter() no iterável para obter um iterador, depois chama next() nele até StopIteration. Isso significa que for funciona em qualquer coisa que implemente o protocolo de iterador: listas, strings, dicionários, arquivos, intervalos e objetos personalizados. Não está limitado a sequências indexadas.

for target in iterable chama iter(iterable) para obter um objeto iterador, depois chama repetidamente next(iterator) e vincula o resultado a target até StopIteration ser lançado. O laço for captura StopIteration internamente. Qualquer objeto que implemente __iter__ (ou ambos __iter__ e __next__) é iterável. Isso inclui objetos preguiçosos como geradores e objetos de arquivo.

python
players = ["Ana", "Bruno", "Carlos"]

for player in players:
    print(f"Hello, {player}!")

Laços for também funcionam em strings (iterando caractere por caractere) e em qualquer outro tipo de sequência.

range()

range() gera uma sequência de números para você iterar. range(5) fornece 0, 1, 2, 3, 4. Você pode controlar o início, o fim e o tamanho do passo. Use-o quando precisar que um laço execute um número específico de vezes.

range(start, stop, step) produz inteiros de start até (mas não incluindo) stop, com passo de step. É uma sequência preguiçosa: não cria uma lista, gera números sob demanda. Isso torna range(10_000_000) eficiente em memória. Todas as três formas aceitam argumentos negativos para contagem reversa.

range é um tipo, não uma função. range(n) cria um objeto range que calcula pertencimento e indexação em O(1) sem materializar a sequência. Suporta len(), in, fatiamento e iteração reversa. Internamente armazena apenas start, stop e step. Prefira-o em vez de list(range(n)) quando você só precisa iterar.

python
for i in range(5):
    print(i)    # 0, 1, 2, 3, 4

range() tem três formas:

ChamadaO que produz
range(5)0, 1, 2, 3, 4
range(2, 6)2, 3, 4, 5
range(0, 10, 2)0, 2, 4, 6, 8 (passo de 2)
range(5, 0, -1)5, 4, 3, 2, 1 (contagem regressiva)

range() não cria uma lista. Ele produz números um de cada vez, o que é eficiente mesmo para intervalos muito grandes.

enumerate()

enumerate() fornece tanto o índice quanto o valor enquanto você itera, então você não precisa rastrear um contador separadamente. A parte i, player recebe automaticamente um par de valores em cada iteração.

enumerate(iterable, start=0) envolve qualquer iterador e produz tuplas (index, value). O parâmetro start desloca o contador mas não altera o índice subjacente. Prefira enumerate() em vez de gerenciar uma variável contadora; é mais limpo e menos propenso a erros.

enumerate envolve qualquer iterador em um objeto enumerate que produz pares (count, value). O argumento start define o valor inicial do contador. O desempacotamento no cabeçalho do for (for i, v in enumerate(...)) funciona porque cada item produzido é uma tupla. enumerate é O(1) por passo, sem alocação de memória extra além do contador.

python
players = ["Ana", "Bruno", "Carlos"]

for i, player in enumerate(players):
    print(f"{i + 1}. {player}")
# 1. Ana
# 2. Bruno
# 3. Carlos

A sintaxe i, player é chamada de desempacotamento. O Python divide o par (index, value) em dois nomes automaticamente.

Por padrão, enumerate() começa em 0. Passe um valor inicial para alterar isso:

python
for i, player in enumerate(players, start=1):
    print(f"{i}. {player}")    # começa em 1

Laços aninhados

Você pode colocar um laço dentro de outro laço. O laço interno é executado completamente para cada iteração do laço externo. É assim que você processa grades, combinações ou qualquer dado com dois níveis de estrutura.

Laços aninhados têm uma contagem de iteração O(m × n) para comprimento externo m e comprimento interno n. break e continue dentro de um laço aninhado afetam apenas o laço mais interno. Para sair de múltiplos níveis, use uma variável de flag ou reestruture em uma função.

Cada chamada de laço for cria um novo objeto iterador. Laços aninhados compõem seus iteradores independentemente. break sai apenas do laço mais interno; não há break rotulado em Python. A solução alternativa comum é uma variável de flag ou encapsular o laço interno em uma função e usar return. Para produtos cartesianos, itertools.product é mais legível do que laços for aninhados.

python
rows = [1, 2, 3]
cols = ["A", "B"]

for row in rows:
    for col in cols:
        print(f"{col}{row}", end=" ")
    print()   # nova linha após cada linha
# A1 B1
# A2 B2
# A3 B3

break e continue dentro de um laço aninhado afetam apenas o laço mais interno.

Loop-else

Laços Python podem ter uma cláusula else que é executada apenas se o laço terminar sem encontrar um break. Não é comumente usada, mas é a forma mais limpa de escrever "pesquise uma lista e, se nada for encontrado, faça isso".

O else em um laço for ou while é executado se o laço completar normalmente (esgota o iterável ou a condição se torna falsa) sem encontrar um break. É o padrão idiomático para "pesquisar e relatar se não encontrado" sem precisar de uma variável de flag separada.

O else de laço é controlado pelo bytecode BREAK_LOOP: se um break disparar, o código de configuração do bloco else não é executado. Se o laço se esgotar, o bloco else é executado. Isso é semanticamente diferente de um else simples em um if. O principal uso prático é o padrão de busca-com-break; fora disso, raramente é visto e pode confundir leitores não familiarizados com ele.

python
target = "Daniel"
names  = ["Ana", "Bruno", "Carlos"]

for name in names:
    if name == target:
        print(f"Found {target}")
        break
else:
    print(f"{target} not in list")   # executa porque break nunca foi disparado

Se break executar, o else é ignorado. Se o laço esgotar a sequência, else é executado. É um padrão de nicho, mas mais limpo do que uma variável de flag.

Ordenação

sorted() retorna uma nova lista ordenada e deixa o original inalterado. .sort() ordena a lista no local e retorna None. O argumento key= permite ordenar por algo diferente do valor bruto. Por exemplo, ordenar nomes sem distinção entre maiúsculas e minúsculas ou ordenar tuplas de jogadores por sua pontuação.

sorted() é o padrão seguro: nunca modifica o original. .sort() modifica no local e retorna None. Ambos aceitam reverse=True para ordem decrescente. O argumento key= recebe uma função aplicada a cada elemento antes da comparação. Isso separa o critério de ordenação dos dados.

Ambos usam Timsort: O(n log n) no pior caso, O(n) em dados quase ordenados, estável. .sort() retorna None deliberadamente (separação comando-consulta). A função key= é chamada uma vez por elemento, não uma vez por comparação, então computações de chave caras não são repetidas. key=str.lower é uma referência de método não vinculado; key=lambda p: p[1] é uma função inline para acessar um campo específico.

python
scores = [87, 42, 96, 55, 71]

ranked = sorted(scores)           # [42, 55, 71, 87, 96] (nova lista)
scores.sort()                     # ordena a lista original, retorna None
scores.sort(reverse=True)         # [96, 87, 71, 55, 42]

Ambos aceitam um argumento key=: uma função aplicada a cada item antes da comparação:

python
names = ["Carlos", "Ana", "Bruno"]
sorted(names, key=str.lower)       # ordenação sem distinção entre maiúsculas/minúsculas

players = [("Ana", 87), ("Bruno", 96), ("Carlos", 55)]
sorted(players, key=lambda p: p[1])   # ordena por pontuação

O que é um lambda?

lambda p: p[1] é uma função de uma linha. Recebe uma tupla de jogador e retorna a pontuação. Funções lambda são abordadas no capítulo Lambda, comprehensions e zip.

Para casos simples, use sorted(). Para listas em que você quer modificar no local, use .sort().

Na prática

Itere pelas pontuações, acumule um total, conte as notas aprovadas e imprima um resumo:

python
raw_scores = [87, 42, 96, 55, 71, 63]

total   = 0
passing = 0

for score in raw_scores:
    total += score
    if score >= 60:
        passing += 1

average = total / len(raw_scores)
print(f"Average: {average:.1f}")
print(f"Passing: {passing}/{len(raw_scores)}")
print(f"Top score: {sorted(raw_scores, reverse=True)[0]}")

Processe uma lista de arquivos em ordem ordenada, pule aqueles que são muito grandes e informe quantos foram pulados:

python
files = [
    {"name": "report_jan.csv", "size_mb": 12},
    {"name": "report_feb.csv", "size_mb": 850},
    {"name": "report_mar.csv", "size_mb": 7},
]

MAX_SIZE = 100
skipped  = 0

for f in sorted(files, key=lambda x: x["name"]):
    if f["size_mb"] > MAX_SIZE:
        print(f"Skipping {f['name']} ({f['size_mb']} MB, too large)")
        skipped += 1
    else:
        print(f"Processing {f['name']}...")

print(f"\nDone. {skipped} file(s) skipped.")

Varra um log de requisições em busca de erros, depois use um laço de retentativa que sai em caso de sucesso ou quando o limite de tentativas é atingido:

python
requests = [
    {"method": "GET",  "path": "/users",  "status": 200},
    {"method": "POST", "path": "/users",  "status": 201},
    {"method": "GET",  "path": "/broken", "status": 500},
]

errors = []

for req in requests:
    if req["status"] >= 400:
        errors.append(req)

if errors:
    print(f"{len(errors)} error(s) in request log:")
    for err in errors:
        print(f"  {err['method']} {err['path']} -> {err['status']}")
else:
    print("All requests succeeded")

attempts    = 0
max_retries = 3
success     = False

while attempts < max_retries and not success:
    attempts += 1
    print(f"Attempt {attempts}...")
    success = attempts >= 2   # simula sucesso na segunda tentativa

print("Connected" if success else "Failed after all retries")