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

Fluxo de controle

docs.scrimba.com

Todo programa que você escreveu até agora funciona do mesmo jeito cada vez: de cima para baixo, uma linha por vez. Isso funciona para scripts simples, mas programas reais precisam tomar decisões e repetir trabalho. Um quiz precisa verificar se a resposta está correta. Um jogo precisa continuar rodando até que o jogador vença ou perca. Este capítulo aborda como fazer seu programa se ramificar e repetir.

O fluxo de controle molda o caminho que seu programa segue. Condições (if/elif/else) escolhem entre ramificações; loops (while, for) repetem um bloco. O for do Python caminha pelos itens um de cada vez em vez de contar um índice, é por isso que ele faz loops sobre tantas coisas de forma limpa. Este capítulo aborda quando usar cada ferramenta e os erros que cada uma convida.

Python oferece três ferramentas de fluxo de controle: if/elif/else para ramificação, e while e for para repetição. Um loop for caminha por um iterável (qualquer coisa pela qual você possa passar um item de cada vez, como uma lista ou um arquivo) e para sozinho quando os itens acabam, então você raramente gerencia um índice manualmente. 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) é executada 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 você poder tomar uma decisão, você precisa comparar coisas. Operadores de comparação retornam True ou False. O mais importante para acertar cedo: = atribui um valor, == verifica se dois valores são iguais. Misturá-los é um dos erros mais comuns de iniciantes.

Operadores de comparação retornam um bool, e Python permite encadear: 0 < x < 10 significa 0 < x and x < 10, que a maioria das outras linguagens lê da esquerda para a direita e erra. Strings se comparam caractere por caractere em ordem de dicionário, então maiúsculas são classificadas 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 real. Primeiro, Python suporta comparações encadeadas: a < b < c é lido como a < b and b < c, e o valor do meio b é avaliado apenas uma vez, o que é importante saber se for uma chamada cara. Segundo, == verifica se dois valores são iguais, enquanto is verifica identidade (se dois nomes apontam para o mesmo objeto exato na memória). Eles não são intercambiáveis: compare valores com ==, e mantenha is para None, True e False, onde você realmente quer o objeto único. Recorrer a is em strings ou números leva a bugs que passam em entradas pequenas e falham em grandes.

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

A distinção = vs == confunde quase todos no início. Atribuição (=) armazena um valor; comparação (==) verifica se dois valores são iguais.

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

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 abrandei a marcha e verifiquei novamente quando estou cansado, então você está em boa companhia se isso o 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 são lidas como 0 < x and x < 10, que a maioria das outras linguagens erra. Comparação de strings é por ordem Unicode, então é classificada com distinção de maiúsculas e minúsculas.
JunoComparações Use == para valor, is para identidade, e mantenha is para None, True e False. A armadilha é is em strings ou ints: passa em entradas pequenas e falha 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 requer que ambos os lados sejam verdadeiros. or requer que pelo menos um lado seja verdadeiro. not inverte o resultado. Esses permitem expressar condições do mundo real como "score está passando AND usuário está ativo".

and e or fazem short-circuit: and para no primeiro operando falsy, or para no primeiro verdadeiro. Eles retornam esse operando real, nem sempre um True ou False simples, é por isso que name or "guest" é uma forma limpa de fornecer um fallback. not sempre devolve um bool simples.

and e or não retornam True ou False, eles retornam um dos operandos. and retorna o primeiro operando falsy, ou o último se todos forem verdadeiros; or retorna o primeiro operando verdadeiro, ou o último se todos forem falsy. O lado direito não é avaliado uma vez que o lado esquerdo resolve o resultado, e essa garantia de short-circuit (Python para assim que a resposta é conhecida) é algo em que você constrói: 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 registra ou escreve, ela silenciosamente nunca é executada quando o lado esquerdo faz short-circuit.

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 requer ambos os lados. or requer 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ê escreverá, como "score está passando and o usuário está ativo". Python para de verificar no momento em que sabe o resultado.
JunoCombinando condiçõesand para no primeiro valor falsy, or no primeiro verdadeiro, e ambos retornam esse operando real, nem sempre um True/False simples. Esse short-circuiting é 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 é ignorado uma vez que o esquerdo decide o resultado. Aproveite para guardas como user and user.name. Observe a outra borda: um efeito colateral no lado direito nunca é executado silenciosamente quando faz short-circuit.

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. 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 contêiner vazio é falsy e um não vazio é truthy. É por isso que if results: é a verificação de vazio idiomática: escreva isso em vez de if len(results) > 0:.

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

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

# Tudo mais se comporta como True

Isso significa 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.

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", sem len() necessário.
JunoTruthy e falsy Falsy é False, zero, strings vazias, contêineres vazios e None; tudo mais é truthy. Então uma lista vazia é falsy e uma cheia é truthy. É exatamente por isso que if my_list: funciona como uma verificação de vazio, sem len() necessário.
JunoTruthy e falsy Condições chamam __bool__, depois __len__ se isso estiver faltando, então suas próprias classes podem escolher seu truthiness. A pegadinha: if results: não pode distinguir uma lista vazia de None. Quando essa distinção importa, use is None em vez disso.

if / elif / else

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

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

Python verifica as condições de cima para baixo e executa exatamente uma ramificação: a primeira cuja condição é verdadeira, ou a else se nenhuma corresponder. Cada condição é avaliada preguiçosamente, significando que Python apenas a testa se cada condição acima dela foi falsa, então a ordem é parte da lógica. Coloque o teste mais barato ou mais provável primeiro, e ordene faixas sobrepostas para que o limite mais apertado venha antes do mais solto, ou uma ramificação posterior fica inacessível. A indentação é a estrutura de bloco aqui, não uma escolha de estilo, então misturar abas e espaços é um TabError em vez de uma mudança silenciosa de significado.

python
score = 87

if score >= 90:
    print("Grau A")
elif score >= 80:
    print("Grau B")
elif score >= 70:
    print("Grau 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, trata tudo que não correspondeu, e vem por último
  • 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 ou cosmética. Python a usa para definir estrutura. Indentação inconsistente é um erro de sintaxe.

Junoif / elif / elseif executa seu bloco quando a condição é verdadeira, elif verifica mais casos, else captura o resto. Apenas a primeira correspondência é executada. A coisa que me pegou no início: indentação é o que diz ao Python onde cada bloco começa e termina, então mantenha-a consistente.
Junoif / elif / else Condições são verificadas de cima para baixo e apenas a primeira correspondência é executada, então a ordem importa: coloque faixas mais estreitas antes de mais amplas. Indentação (4 espaços) marca o bloco, e misturar é um SyntaxError, não um bug silencioso.
Junoif / elif / else Condições são avaliadas preguiçosamente, de cima para baixo, então coloque o teste mais barato ou mais provável primeiro e ordene limites sobrepostos apertado-antes-solto ou uma ramificação morre. Indentação é estrutura, não estilo, e abas-vs-espaços é um TabError em vez de uma mudança silenciosa.

Condições de uma linha

Para atribuições simples de 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-a apenas quando a lógica é pequena e lê como uma sentença.

A expressão condicional (operador ternário) avalia 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 um 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 avalia a condição e retorna um lado sem executar o outro, então é seguro usar onde um ramo causaria um erro. Porque é uma expressão, ela se encaixa em qualquer lugar onde um valor vai: uma f-string, um argumento de função, um padrão. A disciplina é saber quando parar. Ela não pode conter 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 a completa if/elif/else. O leitor que mantém isso depois agradecerá a você.

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

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

JunoCondições de uma linhavalue_if_true if condition else value_if_false escolhe um de dois valores em uma única linha. É maravilhoso para escolhas bonitinhas como "passou" if score >= 50 else "reprovou". No momento em que a lógica cresce além disso, volte a um bloco `if` normal, lerá melhor.
JunoCondições de uma linha O ternário é uma expressão, então se encaixa em qualquer lugar onde um valor vai: uma f-string, um argumento, uma atribuição. Use-o para escolhas simples de duas vias. Qualquer coisa com um `elif` nela pertence a um bloco completo.
JunoCondições de uma linhax if cond else y executa apenas o lado que escolhe, então é seguro onde um ramo levantaria, e cai em qualquer slot de expressão. Não os encadeie: ternários empilhados leem pior que o bloco que substituem, então passado uma decisão, escreva por completo.

Loops while

Um loop while repete seu bloco enquanto sua condição é True. Use-o quando você não sabe antecipadamente quantas vezes o loop deve ser executado, por exemplo, esperando entrada válida ou tentando novamente até que um trabalho tenha sucesso.

while avalia sua condição antes de cada iteração e executa o bloco apenas quando a condição é verdadeira. Use-o 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 para a saída (diminuir o contador, consumir a fila) e você consegue um loop que nunca termina e um processo que congela. A forma idiomática quando a saída deve ser decidida no meio ou final do corpo é while True com um break interior, que mantém a condição onde ela realmente acontece em vez de forçá-la no cabeçalho. Para qualquer coisa caminhando por uma coleção ou executando 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("Fim de jogo")

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

JunoLoops whilewhile continua executando seu bloco enquanto a condição permanece verdadeira. Use quando você não sabe quantas rodadas você precisa, como esperando entrada válida. Certifique-se de que algo dentro do loop se move para a saída, ou ele será executado para sempre, um erro que cometi mais de uma vez.
JunoLoops whilewhile verifica sua condição antes de cada passagem e o corpo é o que a muda. Use-o quando a saída depende de algo que acontece dentro do loop. Se você está contando ou caminhando uma coleção, for é a escolha mais limpa.
JunoLoops while O corpo tem que se mover para a saída ou você consegue um congelamento, então é a primeira coisa a verificar. 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 ignore o risco 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 à verificação de condição. Ambos afetam apenas o loop mais interno em que estão.

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

break deixa o loop de uma vez e pula sua cláusula else se tiver uma; continue pula direto de volta ao cabeçalho do loop para a próxima passagem. Ambos se vinculam ao loop mais próximo, e Python não tem break rotulado, então eles não podem alcançar um loop externo de dentro de um interno. Quando você precisa escapar vários níveis de uma vez, o movimento limpo é levantar os loops aninhados em uma função e return, que lê melhor do que enroscar uma variável de bandeira em ambos. Uma bandeira funciona, mas cada if found: extra que você adiciona para evitar o rótulo faltante é 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 à 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 fora do loop agora, continue pula o resto desta rodada e volta à verificação de condição. Ambos apenas tocam no loop em que estão diretamente dentro. `while True:` com um `break` lê estranho no início, mas é uma forma normal de fazer loop até que algo aconteça.
Junobreak e continuebreak deixa o loop mais próximo, continue o reinicia a partir da condição. Nenhum deles alcança um loop externo. Para sair de loops aninhados, defina uma bandeira ou puxe o loop interno em uma função e `return`.
Junobreak e continue Ambos se vinculam ao loop mais próximo, e não há `break` rotulado, então escapar vários níveis significa uma bandeira ou, mais limpo, uma função com `return`. `break` também pula a `else` do loop, que é exatamente em que o padrão de busca confia.

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 após for recebe cada item por sua vez. Você não gerencia um contador ou verifica o comprimento você mesmo.

for pede à coisa que você dá um item de cada vez e para quando os itens acabam, em vez de contar posições de índice. É por isso que funciona em muito mais do que listas: strings, dicionários, ranges e objetos de arquivo todos fazem loop da mesma forma, sem verificação de comprimento ou contador do seu lado. Use for sobre while sempre que você estiver caminhando uma coleção.

for não conta índices, ele pede à coisa que você dá um iterador (um objeto que entrega um item de cada vez e lembra onde parou) e puxa itens até que acabem. O benefício prático é que for funciona em muito mais do que listas: strings, dicionários, ranges, objetos de arquivo, e lazy fontes como geradores (sequências computadas sob demanda, nunca todas mantidas na memória) fazem loop tudo do mesmo jeito. É neste último grupo onde vive a única pegadinha: um iterador lazy é de uso único, então uma vez que um loop for drena um gerador ou um arquivo, fazer loop novamente não dá nada. Se você precisa dos itens duas vezes, materialize-os em uma lista primeiro ou abra a fonte novamente.

python
players = ["Maria", "João", "Paulo"]

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 caminha por cada item por sua vez, e você nunca toca em um contador ou comprimento você mesmo. Funciona em listas, strings, ranges, muitas coisas. Vindo de outras linguagens, não gerenciar o índice me pareceu estranho no início, depois pareceu um presente.
JunoLoops forfor puxa itens um de cada vez até que acabem, então não fica preso a sequências indexadas. Listas, strings, dicts, ranges, arquivos: mesmo loop para todos eles. Sem bookkeeping de índice, sem verificações de comprimento.
JunoLoops forfor motoriza qualquer iterador, então listas, arquivos e geradores fazem loop como tudo. A pegadinha é os lazy: um iterador de gerador ou arquivo é de uso único, drenado após uma passagem. Precisa dos itens duas vezes? Puxe-os em uma lista primeiro ou reabra a fonte.

range()

range() gera uma sequência de números para você fazer loop. range(5)0, 1, 2, 3, 4. Você pode controlar início, fim e tamanho de passo. Use quando você precisa que um loop execute um número específico de vezes.

range(start, stop, step) produz inteiros de start até (mas não incluindo) stop, passando por step. É uma sequência lazy: não cria uma lista, gera números sob demanda. Isso torna range(10_000_000) 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, ele armazena apenas o início, parada e passo e descobre cada valor enquanto avança. Isso torna range(10_000_000) custo a mesma memória que range(10), então quando você apenas precisa iterar, faça loop no range diretamente em vez de envolvê-lo em list() e pagar por dez milhões de inteiros armazenados. Ainda assim se comporta como uma sequência onde conta: você pode indexá-lo, fatiar, pedir len() e testar associação com in, tudo barato. Chamar list(range(n)) é digno apenas quando você realmente precisa de uma lista real e reutilizável para segurar.

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 (contando para trás)

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

Junorange()range(5) dá `0, 1, 2, 3, 4`, e você pode definir um início, parada e passo. Use quando você quer um loop execute um número definido de vezes. Não constrói uma lista, dá um número por rodada.
Junorange()range(start, stop, step) conta de `start` até mas não incluindo `stop`. É uma sequência lazy, então faz números sob demanda em vez de construir uma lista, é por isso que `range(10_000_000)` custa quase nada. Passos negativos contam para trás.
Junorange() Um `range` armazena apenas start, stop e step, então é memória plana não importa quão grande. Faça loop diretamente em vez de `list(range(n))` a menos que você realmente precise de uma lista reutilizável. Ainda assim indexa, fatia, e suporta `in` e `len()` barato.

enumerate()

enumerate() 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 cede tuplas (index, value). O parâmetro start desloca o contador mas não muda o índice subjacente. Prefira enumerate() a gerenciar uma variável de contador; é mais limpo e menos propenso a erro.

enumerate envolve qualquer iterável e entrega 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). É lazy e adiciona nada além do contador, então custa o mesmo que o loop nu. A razão para sempre usar em vez de um index = 0; index += 1 gerenciado manualmente é confiabilidade: a versão manual convida o erro onde você esquece o incremento ou o bate 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 = ["Maria", "João", "Paulo"]

for i, player in enumerate(players):
    print(f"{i + 1}. {player}")
# 1. Maria
# 2. João
# 3. Paulo

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 de início para mudar:

python
for i, player in enumerate(players, start=1):
    print(f"{i}. {player}")    # começa em 1
Junoenumerate()enumerate() dá a posição e o valor juntos, então sem 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) cede pares `(index, value)` que desempacotan direto no cabeçalho `for`. Prefira-o a uma variável de contador, é mais limpo e você não pode esquecer de incrementá-lo. `start` apenas descoloca o número exibido, não o índice real.
Junoenumerate() Lazy, gratuito além do contador, e remove o bug todo "esqueci de incrementar" que um índice manual convida, então use-o por padrão. Cada item é uma tupla, é por isso que o unpacking `for i, v` funciona. `start=1` para numeração voltada para humanos.

Loops aninhados

Você pode colocar um loop dentro de outro loop. O loop interno executa completamente para cada iteração única do loop externo. É assim que você processa grades, 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 externo m e comprimento interno n. break e continue dentro de um loop aninhado afetam apenas o loop mais interno. Para sair de vários níveis, use uma variável de bandeira ou reestruture em uma função.

Loops aninhados multiplicam: um loop externo de comprimento m em torno de um loop interno de comprimento n executa o corpo m vezes n vezes, então o custo sobe rápido e um par de loops sobre entradas grandes é o culpado usual atrás de um script lento. Antes de alcançar um terceiro nível, pergunte se a estrutura de dados é o reparo real (uma busca de dicionário pode descer um loop de busca interno). Quando você apenas precisa de cada combinação de duas sequências (um produto cartesiano, cada emparelhamento de um com o outro), itertools.product da biblioteca padrão lê melhor que o aninhamento. E lembre-se break sai apenas do loop interno: para deixar ambos, levante-os em 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 após cada linha
# A1 B1
# A2 B2
# A3 B3

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

JunoLoops aninhados Um loop dentro de um loop executa o loop interno inteiro a cada rodada do loop externo. É assim que você trata grades e combinações. `break` e `continue` apenas agem no loop em que estão, aqui o loop interno.
JunoLoops aninhados O loop interno executa completo por passo externo, então a contagem é m vezes n, observe em entradas grandes. `break` e `continue` apenas alcançam o loop mais interno. Para escapar ambos, use uma bandeira ou puxe-os em uma função e `return`.
JunoLoops aninhados Os custos multiplicam, então loops aninhados sobre entradas grandes são o ponto lento usual; às vezes uma busca de dict substitui o internal completamente. Para cada emparelhamento, `itertools.product` bate o aninhamento. `break` atinge apenas o loop interno, então uma função com `return` é a forma limpa de sair de ambos.

Loop-else

Os loops Python podem ter uma cláusula else que é executada apenas se o loop terminou sem atingir um break. Não é comumente usado, mas é a forma mais limpa de escrever "buscar uma lista, e se nada foi encontrado, fazer isto".

A else em um loop for ou while é executada se o loop se completa normalmente (esgota o iterável ou condição se torna falsa) sem atingir um break. É o padrão idiomático para "buscar e relatar se não encontrado" sem precisar de uma variável de bandeira de encontrado separada.

Loop else é executado apenas quando o loop termina sem um break, que é o oposto do que a maioria das pessoas o lê (não é "else, o loop não foi executado"). Esse nome é exatamente por que confunde leitores, então a regra em produção é usá-lo para uma coisa e uma coisa apenas: o padrão de busca-e-não-encontrado, onde break marca uma vez e else relata a falha. Substitui a bandeira found = False que você carregaria pelo loop de outra forma. Em qualquer outro lugar, a esperteza custa mais em revisores confusos do que economiza, então um comentário no else vale a linha.

python
target = "Carlos"
names = ["Maria", "João", "Paulo"]

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

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

JunoLoop-else Um loop pode ter um `else` que executa apenas quando o loop termina sem um `break`. Não é usado frequentemente, mas é a forma mais clara de dizer "buscar esta lista, e se nada correspondeu, fazer isto". O nome é um pouco enganoso, então um breve comentário ajuda.
JunoLoop-else A `else` do loop dispara quando o loop termina normalmente, sem `break`. É o padrão arrumado "buscar e relatar se não encontrado", sem bandeira necessária. Mantenha-o nesse único uso, já que o nome desorienta as pessoas.
JunoLoop-else Executa quando o loop termina sem um `break`, que é o contrário de como a palavra se lê. Mantenha-o para o caso busca-e-não-encontrado, onde substitui uma bandeira `found = False`, e comente. Em qualquer outro lugar custa mais em revisores confusos do 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 ordenar por algo que não seja o valor bruto. Por exemplo, ordenar nomes sem distinção de maiúsculas e minúsculas ou ordenar tuplas de jogadores pela sua pontuação.

sorted() é a opção segura padrão: ela nunca modifica o original. .sort() modifica no lugar e retorna None. Ambas aceitam reverse=True para ordem descendente. O argumento key= leva 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 que itens que se comparam como iguais mantêm sua ordem original, que é o que permite ordenar em estágios: ordenar por nome primeiro, depois por pontuação, e jogadores com a mesma pontuação permanecem em ordem de nome. A função key= é executada 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 colocá-la em cache você mesmo. .sort() retorna None de propósito, para impedi-lo de escrever scores = scores.sort() e silenciosamente apagar sua lista, então a chamada no lugar deve estar em sua própria linha. Use sorted() por padrão e mantenha .sort() para quando você realmente quer mutar o 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 = ["Carlos", "Maria", "João"]
sorted(names, key=str.lower)       # ordenação sem distinção de maiúsculas

players = [("Maria", 87), ("João", 96), ("Paulo", 55)]
sorted(players, key=lambda p: p[1])   # ordenar por pontuação

lambda p: p[1] é uma função de uma linha: leva 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() devolve uma nova lista ordenada e deixa o original sozinho, .sort() rearanja a lista em si e retorna None. Passe `key=` para ordenar por algo que não seja o valor bruto, como nomes sem distinção de maiúsculas. Em dúvida, use sorted(), é o seguro.
JunoOrdenaçãosorted() nunca toca no original, .sort() muta no lugar e retorna `None`. Ambas levam `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=` executa uma vez por elemento, não por comparação, então não 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 graus de aprovação 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"Aprovado: {passing}/{len(raw_scores)}")
print(f"Maior pontuação: {sorted(raw_scores, reverse=True)[0]}")

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

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"Ignorando {f['name']} ({f['size_mb']} MB, muito grande)")
        skipped += 1
    else:
        print(f"Processando {f['name']}...")

print(f"\nConcluído. {skipped} arquivo(s) ignorado(s).")

Examine um log de requisições para erros, depois use um loop de repetição que sai no 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)} erro(s) no log de requisições:")
    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   # simular sucesso na segunda tentativa

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