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 executa do mesmo jeito toda vez: de cima para baixo, uma linha de cada vez. Isso funciona para scripts simples, mas programas reais precisam tomar decisões e repetir trabalho. Um quiz precisa checar se a resposta está certa. Um jogo precisa continuar rodando até o jogador vencer ou perder. Este capítulo mostra como fazer seu programa se ramificar e repetir.

Controle de fluxo molda o caminho que seu programa toma. Condições (if/elif/else) escolhem entre ramificações; loops (while, for) repetem um bloco. O for do Python percorre itens um de cada vez em vez de contar um índice, por isso funciona limpar com tantas coisas. Este capítulo cobre quando usar cada ferramenta e os erros que cada uma propicia.

Python oferece três ferramentas de controle de fluxo: if/elif/else para ramificar, e while e for para repetir. Um loop for percorre um iterável (qualquer coisa que você possa passar por um item de cada vez, como uma lista ou arquivo) e para sozinho quando os itens acabam, então você raramente gerencia um índice na mão. Um loop while continua enquanto sua condição permanecer verdadeira. break e continue tocam apenas o loop mais próximo ao seu redor, e a cláusula else do loop (coberta depois) roda apenas quando um loop termina sem um break. O resto deste capítulo é sobre escolher o certo e evitar os modos de falha que cada um traz.

Comparações

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

Operadores de comparação retornam um bool, e Python permite encadeá-los: 0 < x < 10 significa 0 < x and x < 10, coisa que a maioria das outras linguagens lê da esquerda para direita e erra. Strings se comparam caractere por caractere em ordem de dicionário, então maiúsculas vêm antes de minúsculas. Use == para comparar valores; você quase nunca quer is, que faz uma pergunta diferente (coberta no Deep Dive).

Comparações retornam um bool, e dois detalhes importam em código de verdade. Primeiro, Python suporta comparações encadeadas: a < b < c lê como a < b and b < c, e o valor do meio b é avaliado só uma vez, o que vale a pena saber se for uma chamada cara. Segundo, == verifica se dois valores são iguais, enquanto is verifica identidade (se dois nomes apontam exatamente para o mesmo objeto na memória). Não são intercambiáveis: compare valores com ==, e guarde is para None, True e False, onde você realmente quer aquele único objeto. Usar is em strings ou números leva a bugs que passam em entradas pequenas e quebram em grandes.

python
5 > 3     # True
5 < 3     # False
5 == 5    # True   (note: dois iguais; = é atribuição, == é comparação)
5 != 3    # True   ("não é igual a")
5 >= 5    # True   ("maior ou igual a")
5 <= 4    # False  ("menor ou igual a")

A distinção entre = e == confunde quase todo mundo no começo. Atribuição (=) armazena um valor; comparação (==) verifica se dois valores são iguais.

Você também pode comparar strings. Python as compara alfabeticamente, caractere por caractere, coisa que o capítulo Strings cobre em mais detalhe:

python
"apple" == "apple"   # True
"apple" < "banana"   # True  (a vem antes de b)
"apple" == "Apple"   # False (sensível a maiúsculas)
JunoComparações= armazena um valor, == verifica se dois valores são iguais. Eu ainda desacelero e confiro bem esse quando estou cansado, então você está em boa companhia se isso te confundir. Comparações devolvem True ou False, e funcionam em strings também, alfabeticamente.
JunoComparações Comparações retornam um bool. Formas encadeadas como 0 < x < 10 leem como 0 < x and x < 10, coisa que a maioria das outras linguagens erra. A comparação de strings é por ordem Unicode, então ordena sensível a maiúsculas.
JunoComparações Use == para valor, is para identidade, e guarde is para None, True e False. A armadilha é is em strings ou ints: passa em entradas pequenas e quebra em grandes. Comparações encadeadas avaliam o termo do meio uma vez, útil quando é caro.

Combinando condições

and, or e not combinam comparações. and exige que ambos os lados sejam verdadeiros. or exige que pelo menos um lado seja. not inverte o resultado. Esses permitem expressar condições do mundo real como "pontuação está passando E usuário está ativo".

and e or fazem curto-circuito: and para no primeiro operando falsy, or para no primeiro truthy. Eles retornam esse operando atual, não sempre um True ou False simples, é por isso que name or "guest" é uma forma limpa de fornecer um fallback. not sempre te dá um bool simples.

and e or não retornam True ou False, retornam um dos operandos. and retorna o primeiro operando falsy, ou o último se todos forem truthy; or retorna o primeiro operando truthy, ou o último se todos forem falsy. O lado direito não é avaliado uma vez que o esquerdo resolveu o resultado, e essa garantia de curto-circuito (Python para assim que a resposta fica conhecida) é algo que você constrói em cima: user and user.nameuser.name apenas quando user existe, então nunca erra em um valor faltando. O lado oposto é um modo de falha real: se o lado direito tem um efeito colateral que você estava contando, como uma função que loga ou escreve, ela silenciosamente nunca roda quando o lado esquerdo faz curto-circuito.

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.

JunoCombinando condiçõesand quer ambos os lados verdadeiros, or quer pelo menos um, not inverte a resposta. Isso cobre a maioria das condições reais que você vai escrever, como "pontuação está passando and o usuário está ativo". Python para de checar no momento em que sabe o resultado.
JunoCombinando condiçõesand para no primeiro valor falsy, or no primeiro truthy, e ambos retornam esse operando atual, não sempre um True/False simples. Esse curto-circuito é por que x and x.method() fica seguro quando x está vazio.
JunoCombinando condiçõesand/or retornam um operando, não um bool, e o lado direito é pulado uma vez que o esquerdo decide o resultado. Lean nele para guardas como user and user.name. Fique de olho no outro lado: um efeito colateral no lado direito silenciosamente nunca acontece quando faz curto-circuito.

Truthy e falsy

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

Os valores falsy do Python são False, 0, 0.0, "", [], (), {}, set() e None. Tudo mais é truthy, então um container vazio é falsy e um não-vazio é truthy. É por isso que if results: é o jeito idiomático de checar se está vazio: escreva no lugar de if len(results) > 0:.

Quando você coloca um objeto em uma condição, Python pede uma resposta sim-ou-não dele chamando seu método __bool__ (um dunder, um dos métodos com barra dupla que Python chama por trás dos bastidores). Se um tipo não define __bool__, Python volta para __len__, então qualquer coisa com comprimento zero conta como falsy. Os valores falsy padrão são False, 0, 0.0, "", containers vazios ([], (), {}, set()) e None; tudo mais é truthy. A armadilha para ficar de olho: if results: é falso para uma lista vazia e também para None, então se você especificamente precisa distinguir "vazio" de "não configurado ainda", teste if results is None: em vez de contar com truthiness.

python
# Tudo isso se comporta como False em uma condição:
False, 0, 0.0, "", [], {}, (), None

# Tudo mais se comporta como True

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

JunoTruthy e falsy Coisas vazias e zero contam como False em uma condição: 0, "", [] e None. Tudo mais conta como True. É por isso que if results: lê tão limpo como "se há algo na lista", nenhum len() necessário.
JunoTruthy e falsy Falsy é False, zero, strings vazias, containers vazios e None; tudo mais é truthy. Então uma lista vazia é falsy e uma cheia é truthy. É a razão inteira de if my_list: funcionar como uma verificação de vazio, nenhum len() necessário.
JunoTruthy e falsy Condições chamam __bool__, depois __len__ se isso estiver faltando, então suas próprias classes podem escolher sua truthiness. A pegadinha: if results: não consegue diferenciar uma lista vazia de None. Quando essa distinção importa, use is None no lugar.

if / elif / else

A declaração if roda um bloco de código apenas quando sua condição é True. elif adiciona mais condições para checar se a primeira foi falsa. else captura tudo que não correspondeu a nenhuma condição. Python usa indentação, não chaves, para definir o que pertence dentro de cada bloco.

if/elif/else avalia condições de cima para baixo e roda o primeiro bloco que bate. Python usa indentação (4 espaços por convenção) para definir escopo de bloco; indentação inconsistente é um SyntaxError. Apenas um ramo roda: uma vez que uma condição bate, todos os elif e else subsequentes são pulados.

Python verifica as condições de cima para baixo e roda exatamente um ramo: o primeiro cuja condição é truthy, ou o else se nenhuma bater. Cada condição é avaliada preguiçosamente, significando que Python só a testa se toda condição acima foi falsa, então ordem é parte da lógica. Coloque o teste mais barato ou mais provável primeiro, e ordene intervalos sobrepostos para que o limite mais apertado venha antes do mais frouxo, ou um ramo posterior fica inalcançável. Indentação é a estrutura de bloco aqui, não uma escolha de estilo, então misturar tabs e espaços é um TabError em vez de uma mudança silenciosa de significado.

python
score = 87

if score >= 90:
    print("Nota A")
elif score >= 80:
    print("Nota B")
elif score >= 70:
    print("Nota C")
else:
    print("Abaixo de 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
  • Python usa indentação (4 espaços) para marcar o que pertence dentro de cada bloco; não há chaves

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

Junoif / elif / elseif roda seu bloco quando a condição é verdadeira, elif verifica mais casos, else captura o resto. Apenas o primeiro que bate roda. A coisa que me pegou cedo: indentação é o que diz ao Python onde cada bloco começa e termina, então mantenha consistente.
Junoif / elif / else Condições são verificadas de cima para baixo e apenas o primeiro que bate roda, então ordem importa: coloque intervalos mais apertados antes dos mais frouxos. Indentação (4 espaços) marca o bloco, e misturá-la é um SyntaxError, não um bug silencioso.
Junoif / elif / else Condições avaliam preguiçosamente, de cima para baixo, então coloque o teste mais barato ou mais provável primeiro e ordene limites sobrepostos apertado-antes-frouxo ou um ramo fica morto. Indentação é estrutura, não estilo, e tabs-vs-espaços é um TabError em vez de uma mudança silenciosa.

Condições em uma linha

Para atribuições simples sim/não, Python tem uma forma compacta de uma linha chamada expressão ternária: value_if_true if condition else value_if_false. Use apenas quando a lógica for pequena e ler como uma sentença.

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

A expressão condicional x if condition else y avalia a condição e retorna um lado sem rodar o outro, então é seguro usar onde um ramo iria errar de outro jeito. Como é uma expressão, encaixa em qualquer lugar um valor vai: uma f-string, um argumento de função, um padrão. A disciplina é saber quando parar. Não pode manter um elif e encadeá-la (a if p else b if q else c) lê pior que o bloco que substitui, então uma vez que você tem mais de uma decisão, use o if/elif/else completo. O leitor que mantém isso depois vai te agradecer.

python
label = "passou" if score >= 50 else "falhou"

Essa é uma expressão ternária; lê como uma sentença. Use quando a lógica for pequena. Para qualquer coisa envolvendo elif, escreva a versão completa.

JunoCondições em uma linhavalue_if_true if condition else value_if_false escolhe um de dois valores em uma linha. É lindo para escolhas arrumadas e pequenas como "passou" if score >= 50 else "falhou". No momento que a lógica cresce além disso, volta para um bloco if normal, vai ler melhor.
JunoCondições em uma linha O ternário é uma expressão, então encaixa em qualquer lugar um valor faz: uma f-string, um argumento, uma atribuição. Use para escolhas simples de dois caminhos. Qualquer coisa com um elif nela pertence em um bloco completo.
JunoCondições em uma linhax if cond else y roda apenas o lado que escolhe, então é seguro onde um ramo iria levantar, e encaixa em qualquer slot de expressão. Não as encadeie: ternários empilhados leem pior que o bloco que substitui, então passado uma decisão, escreva por completo.

Loops while

Um loop while repete seu bloco enquanto sua condição é True. Use quando você não sabe com antecedência quantas vezes o loop deve rodar, por exemplo esperando entrada válida ou tentando novamente até um trabalho conseguir.

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

while retesta sua condição antes de cada passagem, e o corpo é o que muda essa condição, então o modo de falha é estrutural: esqueça de se mover na direção da saída (decrementar o contador, consumir a fila) e você consegue um loop que nunca termina e um processo que trava. A forma idiomática quando a saída tem que ser decidida no meio ou fim do corpo é while True com um break interior, que mantém a condição onde ela realmente acontece em vez de forçá-la para o cabeçalho. Para qualquer coisa andando uma coleção ou rodando um número conhecido de vezes, um loop for é mais limpo e remove a chance de um off-by-one no contador.

python
lives = 3

while lives > 0:
    print(f"Vidas restantes: {lives}")
    lives -= 1

print("Game over")

while é melhor quando você não sabe com antecedência quantas vezes o loop vai rodar. Quando você sabe, ou quando está iterando uma coleção, for é mais limpo.

JunoLoops whilewhile continua rodando seu bloco enquanto a condição fica verdadeira. Use quando você não sabe quantas rodadas vai precisar, como esperando entrada válida. Certifique-se de que algo dentro do loop se move na direção da saída, ou vai rodar para sempre, um erro que eu cometi mais de uma vez.
JunoLoops whilewhile verifica sua condição antes de cada passagem e o corpo é o que a muda. Use quando a saída depende de algo acontecendo dentro do loop. Se você está contando ou andando uma coleção, for é a escolha mais limpa.
JunoLoops while O corpo tem que se mover na direção da saída ou você consegue uma travagem, então é a primeira coisa a checar. while True com um break interior é a forma limpa quando a saída cai no meio do corpo. Para uma contagem conhecida ou uma coleção, prefira for e pule o risco de off-by-one.

break e continue

break sai do loop imediatamente, não importa quantas iterações restam. continue pula o resto da iteração atual e volta para a verificação de condição. Ambas apenas afetam o loop mais próximo que elas estão.

break encerra o loop mais próximo, transferindo controle para a primeira declaração depois dele. continue pula o resto do corpo do loop atual e recomeça da verificação de condição (ou a próxima iteração num loop for). Ambas apenas afetam o loop mais próximo.

break deixa o loop de uma vez e pula sua cláusula else se tiver uma; continue vai direto de volta ao cabeçalho do loop para a próxima passagem. Ambas ligam ao loop mais próximo, e Python não tem break etiquetado, então elas não conseguem alcançar um loop exterior de dentro de um interior. Quando você precisa escapar vários níveis de uma vez, o movimento limpo é elevar os loops aninhados para uma função e return, que lê melhor que ficar passando uma variável flag através de ambos. Uma flag funciona, mas cada if found: extra que você adiciona para evitar a etiqueta perdida é um lugar onde uma edição futura pode errar.

break sai do loop imediatamente:

python
target = 5
num = 0

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

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 loop.

continue pula o resto da iteração atual e volta para a verificação de 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 imprimem: 1, 3, 5, 7, 9
Junobreak e continuebreak salta para fora do loop na hora, continue pula o resto dessa rodada e volta a checar a condição. Ambas apenas tocam o loop que elas estão diretamente dentro. while True: com um break lê estranho no começo, mas é um jeito normal de fazer um loop até algo acontecer.
Junobreak e continuebreak deixa o loop mais próximo, continue reinicia dele da condição. Nenhuma alcança um loop exterior. Para sair de loops aninhados, coloque uma flag ou puxe o loop interior para uma função e return.
Junobreak e continue Ambas ligam ao loop mais próximo, e não há break etiquetado, então escapar vários níveis significa uma flag ou, mais limpo, uma função com return. break também pula o else do loop, que é exatamente do que o padrão de busca depende.

Loops for

Um loop for passa por uma sequência um item de cada vez: uma lista, uma string, um intervalo de números. A variável que você nomeia depois de for recebe cada item por vez. Você não gerencia um contador ou verifica o comprimento você mesmo.

for pede a coisa que você dá um item de cada vez e para quando os itens acabam, em vez de contar através de posições de índice. É por isso que funciona com muito mais que listas: strings, dicionários, intervalos e objetos de arquivo todos fazem loop do mesmo jeito, sem verificação de comprimento ou contador do seu lado. Use for ao invés de while sempre que estiver andando uma coleção.

for não conta índices, pede a coisa que você dá um iterador (um objeto que entrega um item de cada vez e lembra onde parou) e puxa itens até acabarem. O payoff prático é que for funciona com muito mais que listas: strings, dicionários, intervalos, objetos de arquivo e fontes preguiçosas como geradores (sequências computadas sob demanda, nunca todas mantidas em memória) todos fazem loop do mesmo jeito. Esse último grupo é onde a pegadinha mora: um iterador preguiçoso é de uso único, então uma vez que um loop for drena um gerador ou um arquivo, fazer loop novamente não te dá nada. Se você precisar dos itens duas vezes, materialize-os numa lista primeiro ou abra a fonte novamente.

python
players = ["Alice", "Bob", "Charlie"]

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

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

JunoLoops forfor item in sequence anda cada item por vez, e você nunca toca um contador ou um comprimento você mesmo. Funciona em listas, strings, intervalos, muitas coisas. Vindo de outras linguagens, não gerenciar o índice pareceu estranho para mim no começo, depois pareceu um presente.
JunoLoops forfor puxa itens um de cada vez até acabarem, então não fica amarrado a sequências indexadas. Listas, strings, dicts, intervalos, arquivos: mesmo loop para todos. Sem bookkeeping de índice, sem verificações de comprimento.
JunoLoops forfor dirige qualquer iterador, então listas, arquivos e geradores todos fazem loop igualmente. A pegadinha é os preguiçosos: um iterador de gerador ou arquivo é de uso único, drenado após uma passagem. Precisa dos itens duas vezes? Puxe-os para uma lista primeiro ou reabra a fonte.

range()

range() gera uma sequência de números para você fazer loop. range(5) te dá 0, 1, 2, 3, 4. Você pode controlar o começo, fim e tamanho do passo. Use quando você precisar de um loop rodar um número específico de vezes.

range(start, stop, step) produz inteiros de start até (mas não incluindo) stop, pisando por step. É uma sequência preguiçosa: não cria uma lista, gera números sob demanda. Isso faz range(10_000_000) ser eficiente em memória. Todos os três formatos aceitam argumentos negativos para contagem reversa.

Um range nunca constrói a lista de números, apenas armazena o começo, fim e passo e trabalha cada valor enquanto vai. Isso faz range(10_000_000) custar a mesma memória que range(10), então quando você só precisa iterar, faça loop do range diretamente em vez de envolvê-lo em list() e pagar por dez milhões de inteiros armazenados. Ainda se comporta como uma sequência onde conta: você pode indexar, dividir, pedir len() e testar pertencimento com in, tudo barato. Chamar list(range(n)) só vale a pena quando você realmente precisa de uma verdadeira lista reutilizável para manter.

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 para baixo)

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

Junorange()range(5) te dá 0, 1, 2, 3, 4, e você pode configurar um começo, fim e passo. Use quando você quer um loop rodar um número configurado de vezes. Não constrói uma lista, te entrega um número por rodada.
Junorange()range(start, stop, step) conta de start até mas não incluindo stop. É uma sequência preguiçosa, então faz números sob demanda em vez de construir uma lista, que é por que range(10_000_000) custa quase nada. Passos negativos contam para baixo.
Junorange() Um range armazena apenas início, fim e passo, então é memória fixa não importa quão grande. Faça loop dele diretamente em vez de list(range(n)) a menos que você realmente precise de uma lista reutilizável verdadeira. Ainda indexa, divide, e suporta in e len() barato.

enumerate()

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

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

enumerate envolve qualquer iterável e entrega de volta pares (count, value), que o cabeçalho for divide em dois nomes através de unpacking (Python atribuindo cada parte de uma tupla ao seu próprio nome em um passo). É preguiçoso e não adiciona nada além do contador, então custa igual ao loop simples. A razão de sempre usar no lugar de um index = 0; index += 1 manual é confiabilidade: a versão manual convida o bug onde você esquece o incremento ou o faz no lugar errado, e enumerate remove toda essa classe de erro. Passe start=1 quando você está numerando saída para pessoas, que contam de um, não zero.

python
players = ["Alice", "Bob", "Charlie"]

for i, player in enumerate(players):
    print(f"{i + 1}. {player}")
# 1. Alice
# 2. Bob
# 3. Charlie

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

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

python
for i, player in enumerate(players, start=1):
    print(f"{i}. {player}")    # começa em 1
Junoenumerate()enumerate() te entrega a posição e o valor juntos, então nenhum contador separado para manter em sincronia. A parte i, player pega ambos de uma vez. Passe start=1 quando você está numerando uma lista para alguém ler.
Junoenumerate()enumerate(iterable, start=0) devolve pares (index, value) que desempacotam direto no cabeçalho for. Prefira a uma variável contador, é mais limpo e você não consegue esquecer de incrementá-lo. start apenas desloca o número exibido, não o índice real.
Junoenumerate() Preguiçoso, grátis além do contador, e deleta o inteiro "esqueci de incrementar" bug que um índice manual convida, então use por padrão. Cada item é uma tupla, que é por que o for i, v unpacking funciona. start=1 para numeração humanamente legível.

Loops aninhados

Você pode colocar um loop dentro de outro loop. O loop interior roda completamente para cada iteração única do loop exterior. Esse é o jeito de processar grids, combinações ou qualquer dado com dois níveis de estrutura.

Loops aninhados têm uma contagem de iteração O(m × n) para comprimento exterior m e comprimento interior n. break e continue dentro de um loop aninhado apenas afetam o loop mais próximo. Para sair de múltiplos níveis, use uma variável flag ou reestruture para uma função.

Loops aninhados multiplicam: um loop exterior de comprimento m ao redor de um interior de comprimento n roda o corpo m vezes n vezes, então o custo cresce rápido e um par de loops sobre entradas grandes é o culpado usual por trás de um script lento. Antes de alcançar um terceiro nível, pergunte se a estrutura de dados é a verdadeira correção (uma busca de dicionário pode colapsar um loop de busca interior). Quando você só precisa de toda combinação de duas sequências (um produto Cartesiano, cada pareamento de uma com a outra), itertools.product da biblioteca padrão lê melhor que o aninhamento. E lembre-se break sai apenas do loop interior: para deixar ambos, eleve-os para uma função e return.

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

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

break e continue dentro de um loop aninhado apenas afetam o loop mais próximo.

JunoLoops aninhados Um loop dentro de um loop roda o loop inteiro interior para cada rodada única do exterior. É assim que você lida com grids e combinações. break e continue apenas agem no loop que ficam, o interior aqui.
JunoLoops aninhados O loop interior roda por completo a cada passo exterior, então a contagem é m vezes n, fique de olho em entradas grandes. break e continue apenas alcançam o loop mais próximo. Para escapar ambos, use uma flag ou puxe-os para uma função e return.
JunoLoops aninhados Custos multiplicam, então loops aninhados sobre entradas grandes são o lugar lento usual; às vezes uma busca de dict substitui o interior inteiramente. Para todo pareamento, itertools.product bate o aninhamento. break atinge apenas o loop interior, então uma função com return é o jeito limpo para fora de ambos.

Loop-else

Loops do Python podem ter uma cláusula else que roda apenas se o loop terminou sem bater um break. Não é comum, mas é o jeito mais limpo de escrever "procure uma lista, e se nada for encontrado, faça isso".

O else num loop for ou while executa se o loop completa normalmente (esgota o iterável ou a condição fica falsa) sem bater um break. É o padrão idiomático para "procure e reporte se não encontrado" sem precisar de uma variável found-flag separada.

Loop else roda apenas quando o loop termina sem um break, que é o oposto do que a maioria das pessoas lê (não é "else, o loop não rodou"). Esse nome é exatamente por que confunde leitores, então a regra em produção é usá-lo para uma coisa e uma coisa só: o padrão procura-e-não-encontrado, onde break marca um acerto e o else relata a falha. Substitui a flag found = False que você carregaria de outro jeito através do loop. Em qualquer outro lugar, a inteligência custa mais em reviewers confundidos que economiza, então um comentário no else vale a linha.

python
target = "Dave"
names = ["Alice", "Bob", "Charlie"]

for name in names:
    if name == target:
        print(f"Encontrado {target}")
        break
else:
    print(f"{target} não está na lista")   # roda porque break nunca disparou

Se break roda, o else é pulado. Se o loop esgota a sequência, else roda. É um padrão de nicho mas mais limpo que uma variável flag.

JunoLoop-else Um loop pode ter um else que roda apenas quando o loop termina sem um break. Não é usado frequentemente, mas é o jeito mais arrumado de dizer "procure essa lista, e se nada corresponder, faça isso". O nome é um pouco enganoso, então um comentário breve ajuda.
JunoLoop-else O loop else dispara quando o loop termina normalmente, sem break. É o padrão arrumado "procure e reporte se não encontrado", sem flag necessária. Mantenha para esse um uso, já que o nome confunde pessoas.
JunoLoop-else Roda quando o loop termina sem um break, que é o inverso de como a palavra lê. Mantenha para o caso procura-e-não-encontrado, onde substitui uma flag found = False, e comente. Em qualquer outro lugar custa mais em reviewers confundidos que economiza.

Ordenação

sorted() retorna uma nova lista ordenada e deixa a original inalterada. .sort() ordena a lista no lugar e retorna None. O argumento key= permite você ordenar por algo diferente do valor bruto. Por exemplo, ordenar nomes insensitivamente a maiúsculas ou ordenar tuplas de jogador por sua pontuação.

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

A ordenação do Python é estável, significando itens que se comparam iguais mantêm sua ordem original, que é o que permite você ordenar em estágios: ordene por nome primeiro, depois por pontuação, e jogadores com a mesma pontuação ficam em ordem de nome. A função key= roda uma vez por elemento, não uma vez por comparação, então uma chave cara é computada n vezes, não n log n vezes, e você raramente precisa cachear você mesmo. .sort() retorna None de propósito, para te parar de escrever scores = scores.sort() e silenciosamente apagar sua lista, então a chamada no-lugar deve ficar em sua própria linha. Use sorted() por padrão e guarde .sort() para quando você realmente quer mutar a original.

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]

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

python
names = ["Charlie", "Alice", "Bob"]
sorted(names, key=str.lower)       # ordenação insensitiva a maiúsculas

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

lambda p: p[1] é uma função de uma linha: toma uma tupla de jogador e retorna a pontuação. Lambdas recebem tratamento completo no capítulo Lambdas e compreensões.

Para casos simples, use sorted(). Para listas onde você quer modificar no lugar, use .sort().

JunoOrdenaçãosorted() entrega de volta uma nova lista ordenada e deixa a original sozinha, .sort() rearranja a lista ela mesma e retorna None. Passe key= para ordenar por algo diferente do valor bruto, como nomes insensitivamente a maiúsculas. Quando em dúvida, use sorted(), é a segura.
JunoOrdenaçãosorted() nunca toca a original, .sort() muta no lugar e retorna None. Ambas pegam reverse=True e uma função key= aplicada a cada elemento antes de comparar. Padrão para sorted(); use .sort() apenas quando você quer mudar a lista.
JunoOrdenação Ordenação é estável, então ordene em estágios e empates mantêm sua ordem. key= roda uma vez por elemento, não por comparação, então não pré-cache. E .sort() retorna None de propósito: x = x.sort() apaga sua lista, então mantenha a chamada no-lugar em sua própria linha.

Na prática

Faça loop através de pontuações, acumule um total, conte notas que passaram 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"Média: {average:.1f}")
print(f"Passando: {passing}/{len(raw_scores)}")
print(f"Pontuação máxima: {sorted(raw_scores, reverse=True)[0]}")

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

python
files = [
    {"name": "relatorio_jan.csv", "size_mb": 12},
    {"name": "relatorio_fev.csv", "size_mb": 850},
    {"name": "relatorio_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"Pulando {f['name']} ({f['size_mb']} MB, muito grande)")
        skipped += 1
    else:
        print(f"Processando {f['name']}...")

print(f"\nPronto. {skipped} arquivo(s) pulado(s).")

Escaneie um log de requisição para erros, depois use um loop de retry que sai no sucesso ou quando o limite de tentativa é 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)} erro(s) no log de requisição:")
    for err in errors:
        print(f"  {err['method']} {err['path']} -> {err['status']}")
else:
    print("Todas as requisições tiveram sucesso")

attempts = 0
max_retries = 3
success = False

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

print("Conectado" if success else "Falhou após todas as tentativas")