Exercícios de Python avançado com respostas

As 78 questões são os testes rápidos das aulas do Curso de Python Avançado, uma de cada aula e na ordem do curso, de type hints a empacotamento. Pense na resposta antes de abrir o gabarito: a explicação diz por que a alternativa certa vence e mostra a aula que trata do assunto.

Como usar: leia a pergunta, escolha a sua alternativa e só depois abra "Ver a resposta". Quando errar, a explicação diz por que e o link leva à aula que ensina o assunto. As mesmas questões aparecem nas aulas como teste rápido, e o Curso de Python Avançado é gratuito do começo ao fim.

Módulo 1: Boas-vindas ao Python Avançado

Questão 1 · aula Do intermediário para o avançado: o que muda

O que melhor descreve o salto do Python intermediário para o avançado?

  1. Aprender uma linguagem totalmente diferente do zero.
  2. Decorar recursos exóticos que quase nunca se usa.
  3. Usar a base do intermediário e acrescentar engenharia: tipar, testar, medir e projetar com intenção.
  4. Abandonar classes e funções e programar só com metaclasses.
Ver a resposta

Resposta: C. Usar a base do intermediário e acrescentar engenharia: tipar, testar, medir e projetar com intenção.

O avançado reaproveita tudo do Intermediário (classes, decoradores, geradores, testes) e acrescenta a camada de engenharia: código tipado, testado, medido e bem arquitetado. Há recursos novos, mas o fio condutor é projetar com intenção, não recomeçar.

Estudar a aula: Do intermediário para o avançado: o que muda

Questão 2 · aula Revisão relâmpago do intermediário

No exemplo, por que criar uma classe SaldoInsuficienteError em vez de usar um print de erro?

  1. Porque print não funciona dentro de funções.
  2. Para dar à falha um tipo próprio, que pode ser capturado com except e tratado de propósito.
  3. Porque toda função avançada precisa de uma classe.
  4. Para o programa rodar mais rápido.
Ver a resposta

Resposta: B. Para dar à falha um tipo próprio, que pode ser capturado com except e tratado de propósito.

Uma exceção própria dá à falha um tipo específico. Assim quem chama a função pode capturar exatamente esse erro com except SaldoInsuficienteError e reagir de forma adequada, em vez de tratar todos os erros do mesmo jeito. É a base da robustez que o módulo 12 aprofunda.

Estudar a aula: Revisão relâmpago do intermediário

Questão 3 · aula Seu ambiente e as ferramentas de engenharia

No Playground, o que acontece se você passar um texto onde a função espera list[float]?

  1. O Python recusa a rodar por causa do type hint.
  2. O Python roda mesmo assim, porque ignora os type hints em tempo de execução; quem apontaria o erro antes é o mypy.
  3. O type hint corrige o valor automaticamente.
  4. O programa trava sem nenhuma explicação possível.
Ver a resposta

Resposta: B. O Python roda mesmo assim, porque ignora os type hints em tempo de execução; quem apontaria o erro antes é o mypy.

Os type hints são documentação para o Python na hora de rodar: ele não impede tipos errados. Quem verifica os tipos sem executar, apontando a incoerência antes do bug, é o mypy. Por isso tipos e verificador andam juntos no avançado.

Estudar a aula: Seu ambiente e as ferramentas de engenharia

Módulo 2: Tipagem estática

Questão 4 · aula Type hints: o contrato explícito

O que o Python faz com um type hint como idade: int em tempo de execução?

  1. Recusa qualquer valor que não seja inteiro e gera erro.
  2. Converte o valor recebido para inteiro automaticamente.
  3. Guarda a anotação como documentação e não impõe o tipo; a verificação fica com o mypy.
  4. Ignora completamente a linha, como se fosse um comentário sem efeito nenhum.
Ver a resposta

Resposta: C. Guarda a anotação como documentação e não impõe o tipo; a verificação fica com o mypy.

O Python registra a anotação em __annotations__ e a trata como documentação, sem impor o tipo ao rodar. Quem verifica a coerência dos tipos sem executar o código é o mypy. Por isso type hint e verificador andam juntos.

Estudar a aula: Type hints: o contrato explícito

Questão 5 · aula typing: coleções, Optional e Union

Uma função de busca devolve o nome encontrado ou None. Qual anotação de retorno é a correta?

  1. -> str, porque na maioria das vezes acha um nome de texto normal.
  2. -> str | None, porque o resultado pode ser o texto encontrado ou a ausência de valor.
  3. -> None, porque o retorno é sempre incerto e não vale a pena anotar.
  4. -> list[str], porque toda busca em Python sempre devolve uma lista de resultados.
Ver a resposta

Resposta: B. -> str | None, porque o resultado pode ser o texto encontrado ou a ausência de valor.

Como a função pode não achar nada e devolver None, o retorno é str ou None, escrito str | None (ou Optional[str]). O tipo deixa explícito o caso vazio, e o mypy passa a cobrar a checagem de None antes de usar o valor.

Estudar a aula: typing: coleções, Optional e Union

Questão 6 · aula Callable, Any e apelidos de tipo

Por que usar Any por toda parte anula a vantagem da tipagem?

  1. Porque Any deixa o programa lento em tempo de execução, gastando muita memória.
  2. Porque Any desliga a verificação de tipos naquele ponto, então o mypy para de apontar erros.
  3. Porque Any só funciona em versões muito antigas do Python e quebra em versões novas.
  4. Porque Any converte todos os valores para texto antes de usá-los no código.
Ver a resposta

Resposta: B. Porque Any desliga a verificação de tipos naquele ponto, então o mypy para de apontar erros.

Any combina com qualquer coisa e permite qualquer operação, o que faz o mypy parar de verificar naquele ponto. Espalhado pelo código, ele anula o benefício dos tipos. Quando você quer aceitar qualquer valor mas manter a checagem, object é a escolha mais segura.

Estudar a aula: Callable, Any e apelidos de tipo

Questão 7 · aula Generics com TypeVar e Protocol

Por que Circulo é aceito onde se espera um TemArea, mesmo sem herdar de TemArea?

  1. Porque Protocol força todas as classes do arquivo a herdarem dele automaticamente.
  2. Porque o Protocol verifica por comportamento: qualquer objeto com o método area serve.
  3. Porque o mypy ignora Protocol e aceita qualquer objeto em qualquer lugar sem checar.
  4. Porque Circulo foi registrado manualmente como subclasse de TemArea em outro trecho.
Ver a resposta

Resposta: B. Porque o Protocol verifica por comportamento: qualquer objeto com o método area serve.

O Protocol descreve o contrato por comportamento: ter o método area que devolve float. Qualquer objeto que cumpra isso satisfaz o Protocol, sem precisar herdar dele. É o duck typing, agora verificado estaticamente pelo mypy.

Estudar a aula: Generics com TypeVar e Protocol

Questão 8 · aula mypy na prática e quando tipar

Em qual situação adicionar type hints traz mais retorno pelo esforço?

  1. Num script de cinco linhas que você roda uma vez e depois apaga.
  2. Numa biblioteca compartilhada e na lógica complexa que outras pessoas vão manter.
  3. Numa linha de exploração rápida no console só para testar uma ideia solta.
  4. Em todo e qualquer trecho, sempre, mesmo quando o tipo já é evidente.
Ver a resposta

Resposta: B. Numa biblioteca compartilhada e na lógica complexa que outras pessoas vão manter.

Os tipos rendem mais em código duradouro e compartilhado, como bibliotecas, e em lógica complexa, onde servem de documentação viva e pegam erros cedo. Em scripts descartáveis ou trechos triviais, o custo de anotar supera o benefício. A tipagem gradual permite escolher.

Estudar a aula: mypy na prática e quando tipar

Módulo 3: Dataclasses e estruturas de dados

Questão 9 · aula @dataclass: menos código, mais intenção

O que o decorador @dataclass gera automaticamente a partir dos campos declarados?

  1. Apenas um __init__, deixando a representação e a comparação por sua conta.
  2. O __init__, o __repr__ e o __eq__, cobrindo construção, exibição e comparação.
  3. Somente o __repr__, para imprimir o objeto de forma mais legível na tela.
  4. Nada de métodos; ele só valida os tipos dos campos em tempo de execução.
Ver a resposta

Resposta: B. O __init__, o __repr__ e o __eq__, cobrindo construção, exibição e comparação.

A partir dos campos anotados, @dataclass gera o __init__ (construtor), o __repr__ (representação legível) e o __eq__ (comparação por conteúdo). Isso elimina o código repetitivo de uma classe de dados.

Estudar a aula: @dataclass: menos código, mais intenção

Questão 10 · aula field e default_factory: valores mutáveis

Por que se usa field(default_factory=list) em vez de igual a uma lista vazia direto?

  1. Porque a sintaxe com field roda mais rápido do que uma lista literal vazia.
  2. Porque a factory cria uma lista nova para cada instância, evitando estado compartilhado.
  3. Porque o Python não aceita nenhum tipo de valor padrão em campos de dataclass.
  4. Porque default_factory converte a lista em tupla imutável automaticamente.
Ver a resposta

Resposta: B. Porque a factory cria uma lista nova para cada instância, evitando estado compartilhado.

Uma lista como default direto seria compartilhada entre todas as instâncias, um bug clássico. O default_factory recebe uma função chamada a cada objeto novo, produzindo uma lista independente por instância. Por isso o Python barra o default mutável direto e pede a factory.

Estudar a aula: field e default_factory: valores mutáveis

Questão 11 · aula frozen e __post_init__: imutabilidade e validação

Qual é o papel do __post_init__ numa dataclass?

  1. Substituir o __init__ gerado por um construtor totalmente escrito à mão.
  2. Rodar logo após a construção para validar campos ou calcular valores derivados.
  3. Tornar a classe imutável, impedindo qualquer alteração dos campos depois.
  4. Gerar o __repr__ e o __eq__ que o decorador não teria criado sozinho.
Ver a resposta

Resposta: B. Rodar logo após a construção para validar campos ou calcular valores derivados.

O __post_init__ é chamado automaticamente logo após o __init__ atribuir os campos. É onde você valida os valores e levanta erro se algo estiver inconsistente, garantindo que o objeto nunca nasça inválido. A imutabilidade é papel do frozen, um recurso separado.

Estudar a aula: frozen e __post_init__: imutabilidade e validação

Questão 12 · aula dataclass, namedtuple, dict e tupla: qual usar

Qual estrutura é a melhor para um registro pequeno, imutável, com campos nomeados e sem métodos?

  1. Um dicionário, porque aceita qualquer chave a qualquer momento sem restrição.
  2. Uma namedtuple, que é imutável, leve, nomeia os campos e ainda é uma tupla.
  3. Uma lista comum, indexada por posição, sem nomes para os campos guardados.
  4. Uma dataclass mutável e com herança, mesmo sem nenhum método ou validação.
Ver a resposta

Resposta: B. Uma namedtuple, que é imutável, leve, nomeia os campos e ainda é uma tupla.

A namedtuple é ideal para um registro pequeno e fixo: imutável, leve, acessada por nome ou posição e compatível com tuplas. Quando surge necessidade de métodos, padrões ou validação, aí a dataclass passa a valer mais a pena.

Estudar a aula: dataclass, namedtuple, dict e tupla: qual usar

Questão 13 · aula Enum: constantes nomeadas e seguras

Qual a principal vantagem de usar um Enum em vez de textos soltos como pendente e pago?

  1. O Enum torna o programa mais rápido ao substituir textos por números internos.
  2. O Enum dá constantes nomeadas e verificáveis; um nome digitado errado gera erro na hora.
  3. O Enum permite qualquer valor a qualquer momento, sem restringir as opções possíveis.
  4. O Enum dispensa a necessidade de comparar valores em qualquer parte do código.
Ver a resposta

Resposta: B. O Enum dá constantes nomeadas e verificáveis; um nome digitado errado gera erro na hora.

O Enum troca valores mágicos por constantes nomeadas com significado. Além da legibilidade, ele torna erros de digitação barulhentos: referenciar um membro inexistente gera erro na hora, enquanto um texto solto escrito errado passaria despercebido.

Estudar a aula: Enum: constantes nomeadas e seguras

Módulo 4: Programação funcional

Questão 14 · aula Funções puras e imutabilidade

O que caracteriza uma função pura?

  1. Ela é escrita sem usar nenhuma variável local dentro do corpo.
  2. Seu resultado depende só dos argumentos e ela não causa efeitos colaterais.
  3. Ela sempre altera uma variável global para registrar que foi chamada.
  4. Ela roda mais rápido porque não recebe nenhum argumento de entrada.
Ver a resposta

Resposta: B. Seu resultado depende só dos argumentos e ela não causa efeitos colaterais.

Uma função pura depende apenas dos argumentos e não altera nada fora de si: mesma entrada, mesma saída, sem efeitos colaterais. Isso a torna fácil de testar, entender e reaproveitar, porque não há dependências ocultas de estado externo.

Estudar a aula: Funções puras e imutabilidade

Questão 15 · aula map, filter, lambda e compreensões

O que a chamada filter faz com um iterável e uma função que devolve verdadeiro ou falso?

  1. Aplica a função a cada item e devolve todos os resultados transformados.
  2. Mantém apenas os itens para os quais a função devolve verdadeiro.
  3. Ordena o iterável usando a função como critério de comparação.
  4. Soma todos os itens do iterável num único valor acumulado.
Ver a resposta

Resposta: B. Mantém apenas os itens para os quais a função devolve verdadeiro.

filter recebe uma função de teste e um iterável, e mantém só os itens em que a função devolve verdadeiro. Quem transforma cada item é o map; quem acumula tudo num valor é o reduce, da próxima aula.

Estudar a aula: map, filter, lambda e compreensões

Questão 16 · aula functools: reduce e partial

O que o partial faz ao receber uma função e alguns argumentos?

  1. Executa a função imediatamente com os argumentos e devolve o resultado.
  2. Cria uma função nova com esses argumentos já fixados, uma versão especializada.
  3. Divide a função em partes menores que rodam em paralelo.
  4. Acumula os argumentos num único valor, como faz o reduce.
Ver a resposta

Resposta: B. Cria uma função nova com esses argumentos já fixados, uma versão especializada.

O partial faz aplicação parcial: fixa alguns argumentos e devolve uma função nova que só precisa dos restantes. Assim você cria versões especializadas, como quadrado a partir de uma função de potência, sem repetir a lógica.

Estudar a aula: functools: reduce e partial

Questão 17 · aula lru_cache: memoização sem esforço

Por que o lru_cache só deve ser usado com funções puras?

  1. Porque funções impuras não podem receber decoradores em Python.
  2. Porque o cache devolve o valor guardado; se o resultado depende de estado externo que muda, ele fica errado.
  3. Porque o lru_cache converte a função em uma função pura automaticamente.
  4. Porque funções puras são as únicas que aceitam argumentos numéricos.
Ver a resposta

Resposta: B. Porque o cache devolve o valor guardado; se o resultado depende de estado externo que muda, ele fica errado.

O cache devolve o resultado guardado para os mesmos argumentos. Se a função for impura e depender de algo externo que muda, como a hora ou um arquivo, o valor em cache ficaria desatualizado e errado. Por isso a memoização exige pureza para ser confiável.

Estudar a aula: lru_cache: memoização sem esforço

Questão 18 · aula itertools: iteradores poderosos

Por que geralmente é preciso ordenar os dados antes de usar o groupby?

  1. Porque o groupby recusa qualquer iterável que não esteja ordenado e gera erro.
  2. Porque o groupby só agrupa itens consecutivos; sem ordenar, os iguais espalhados viram grupos separados.
  3. Porque a ordenação é o que ativa o modo preguiçoso do groupby na memória.
  4. Porque o groupby precisa contar quantos itens existem antes de agrupar qualquer coisa.
Ver a resposta

Resposta: B. Porque o groupby só agrupa itens consecutivos; sem ordenar, os iguais espalhados viram grupos separados.

O groupby junta apenas vizinhos com a mesma chave. Se os itens iguais estiverem espalhados, ele cria vários grupos separados para a mesma chave. Ordenar por essa chave antes coloca os iguais lado a lado, para que caiam num único grupo.

Estudar a aula: itertools: iteradores poderosos

Módulo 5: Decoradores avançados

Questão 19 · aula Recap do decorador e o functools.wraps

Por que aplicar @functools.wraps(funcao) na função wrapper de um decorador?

  1. Para o decorador rodar mais rápido.
  2. Para copiar os metadados (como __name__ e __doc__) da função original para o wrapper, preservando a identidade da função decorada.
  3. Porque sem ele o decorador nem funciona.
  4. Para permitir que o wrapper receba argumentos.
Ver a resposta

Resposta: B. Para copiar os metadados (como __name__ e __doc__) da função original para o wrapper, preservando a identidade da função decorada.

Sem wraps, a função decorada assume o nome e a docstring do wrapper, perdendo a própria identidade. O functools.wraps copia __name__, __doc__ e outros metadados da função original para o wrapper, o que mantém logs, help() e ferramentas de documentação corretos.

Estudar a aula: Recap do decorador e o functools.wraps

Questão 20 · aula Decorador que recebe argumentos

Por que um decorador que recebe argumentos precisa de três níveis de função?

  1. Porque três é o número máximo de funções aninhadas que o Python aceita.
  2. Porque @deco(arg) primeiro chama deco(arg), que precisa devolver o decorador de verdade; esse nível extra abriga a configuração.
  3. Porque o functools.wraps exige três funções.
  4. Porque cada argumento precisa de uma função própria.
Ver a resposta

Resposta: B. Porque @deco(arg) primeiro chama deco(arg), que precisa devolver o decorador de verdade; esse nível extra abriga a configuração.

Ao escrever @deco(arg), o Python executa deco(arg) primeiro; o retorno dessa chamada é que decora a função. Logo, deco(arg) precisa devolver um decorador. Esse nível a mais (a fábrica) recebe a configuração e devolve o decorador comum, que por sua vez devolve o wrapper.

Estudar a aula: Decorador que recebe argumentos

Questão 21 · aula Decorar uma classe inteira

O que um decorador de classe recebe como argumento?

  1. Uma instância da classe já criada.
  2. O próprio objeto classe, que ele pode modificar e então devolver.
  3. Uma lista com todos os métodos da classe.
  4. Apenas o nome da classe, como texto.
Ver a resposta

Resposta: B. O próprio objeto classe, que ele pode modificar e então devolver.

Um decorador de classe recebe o próprio objeto classe (não uma instância). Dentro dele você pode adicionar métodos e atributos, registrar a classe em um catálogo ou modificá-la de outras formas, e então devolve a classe, que passa a valer no lugar da original. O @dataclass é exatamente isso.

Estudar a aula: Decorar uma classe inteira

Questão 22 · aula Empilhar decoradores e a ordem de aplicação

Em @a seguido de @b sobre uma função f, qual é a equivalência correta?

  1. f = b(a(f)), porque o de cima se aplica primeiro.
  2. f = a(b(f)), porque a aplicação vai de baixo para cima: b envolve f, e a envolve o resultado.
  3. f = a(f) e depois f = b(f), de forma independente.
  4. A ordem nunca muda nada, então tanto faz.
Ver a resposta

Resposta: B. f = a(b(f)), porque a aplicação vai de baixo para cima: b envolve f, e a envolve o resultado.

Decoradores empilhados se aplicam de baixo para cima. O @b, mais próximo do def, envolve f primeiro; o @a envolve o resultado. Isso equivale a f = a(b(f)). Na execução, o wrapper de a roda por fora e chama o de b, como camadas de cebola.

Estudar a aula: Empilhar decoradores e a ordem de aplicação

Questão 23 · aula Casos reais: log, cronômetro, cache, validação e retry

Qual é a forma recomendada de adicionar cache (memoização) a uma função pura em Python?

  1. Escrever um dicionário global e gerenciá-lo à mão em cada função.
  2. Usar o decorador pronto functools.lru_cache, que memoiza os resultados por argumento.
  3. Salvar os resultados em um arquivo a cada chamada.
  4. Repetir o cálculo sempre, porque cache não vale a pena.
Ver a resposta

Resposta: B. Usar o decorador pronto functools.lru_cache, que memoiza os resultados por argumento.

A biblioteca padrão já traz o functools.lru_cache, um decorador que memoiza os retornos por argumento e descarta os menos usados quando enche. Ele exige argumentos hasháveis e uma função pura. Reinventar um cache à mão é mais trabalhoso e propenso a erros.

Estudar a aula: Casos reais: log, cronômetro, cache, validação e retry

Módulo 6: Context managers e o with

Questão 24 · aula Por que o with existe

Qual é a principal garantia que o comando with oferece?

  1. Fazer o código rodar mais rápido.
  2. Garantir que a limpeza do recurso (como fechar um arquivo) aconteça ao sair do bloco, mesmo que uma exceção ocorra.
  3. Impedir que exceções aconteçam dentro do bloco.
  4. Substituir a necessidade de tratar erros.
Ver a resposta

Resposta: B. Garantir que a limpeza do recurso (como fechar um arquivo) aconteça ao sair do bloco, mesmo que uma exceção ocorra.

O with garante que o recurso seja limpo ao sair do bloco, com saída normal ou por exceção. Ele não impede erros nem acelera nada; o que faz é assegurar a limpeza sempre, transformando uma promessa frágil (fechar à mão) em uma garantia da linguagem.

Estudar a aula: Por que o with existe

Questão 25 · aula O protocolo __enter__ e __exit__

No protocolo de context manager, o que vai para a variável depois do as em with obj as x?

  1. Sempre o próprio objeto obj.
  2. O valor que o método __enter__ devolve.
  3. O valor que o método __exit__ devolve.
  4. O resultado do __init__.
Ver a resposta

Resposta: B. O valor que o método __enter__ devolve.

Ao entrar no bloco, o Python chama obj.__enter__(), e o valor retornado por esse método é atribuído à variável depois do as. Muitas vezes __enter__ devolve self (e então x é o próprio obj), mas ele pode devolver qualquer coisa, como um arquivo ou um cursor.

Estudar a aula: O protocolo __enter__ e __exit__

Questão 26 · aula O atalho: @contextlib.contextmanager

Em um context manager criado com @contextmanager, o que representa o código depois do yield?

  1. A preparação do recurso, equivalente ao __enter__.
  2. A limpeza do recurso, equivalente ao __exit__, e deve ficar em um finally.
  3. O valor que vai para a variável do as.
  4. Uma parte que nunca é executada.
Ver a resposta

Resposta: B. A limpeza do recurso, equivalente ao __exit__, e deve ficar em um finally.

Na função decorada com @contextmanager, o código antes do yield é a entrada (__enter__), o valor cedido vai para o as, e o código depois do yield é a saída (__exit__). Para garantir que a limpeza rode mesmo com exceção, o yield fica em um try e a limpeza no finally.

Estudar a aula: O atalho: @contextlib.contextmanager

Questão 27 · aula Ferramentas do contextlib: ExitStack e suppress

Em que situação o contextlib.ExitStack é a ferramenta certa?

  1. Quando você quer ignorar uma exceção específica.
  2. Quando o número de recursos a gerenciar só é conhecido em tempo de execução, e todos devem ser limpos ao sair.
  3. Quando você tem exatamente um recurso fixo.
  4. Quando quer acelerar a abertura de arquivos.
Ver a resposta

Resposta: B. Quando o número de recursos a gerenciar só é conhecido em tempo de execução, e todos devem ser limpos ao sair.

O ExitStack empilha uma quantidade variável de context managers, adicionados com enter_context, e garante que todos sejam limpos ao sair do bloco, na ordem inversa. É a escolha quando o número de recursos não é fixo no código. Ignorar exceções específicas é papel do suppress.

Estudar a aula: Ferramentas do contextlib: ExitStack e suppress

Questão 28 · aula Casos reais: arquivos, locks, transações e cronômetro

Por que usar with em um lock de threads é considerado essencial, e não só conveniente?

  1. Porque with deixa o lock mais rápido.
  2. Porque garante que o lock seja liberado mesmo se o trecho protegido lançar uma exceção, evitando que outras threads travem para sempre.
  3. Porque with impede que duas threads existam ao mesmo tempo.
  4. Porque sem with o lock nem funciona.
Ver a resposta

Resposta: B. Porque garante que o lock seja liberado mesmo se o trecho protegido lançar uma exceção, evitando que outras threads travem para sempre.

Um lock precisa ser liberado sempre. Se você liberar à mão e uma exceção pular essa linha, o lock fica fechado e todas as outras threads que o esperam travam indefinidamente. O with adquire e libera o lock automaticamente, garantindo a liberação mesmo em caso de erro. Isso é correção, não só conveniência.

Estudar a aula: Casos reais: arquivos, locks, transações e cronômetro

Módulo 7: Geradores e iteradores avançados

Questão 29 · aula O protocolo do iterador por dentro

Como um iterador sinaliza que não há mais valores a entregar?

  1. Devolvendo None no __next__.
  2. Levantando a exceção StopIteration, que o for captura e trata como fim do laço.
  3. Devolvendo uma lista vazia.
  4. Chamando iter() de novo.
Ver a resposta

Resposta: B. Levantando a exceção StopIteration, que o for captura e trata como fim do laço.

Quando os valores acabam, o método __next__ levanta StopIteration. Essa exceção é um sinal combinado de fim, não um erro comum: o laço for a captura automaticamente e encerra a iteração em silêncio, sem propagar nada para o seu código.

Estudar a aula: O protocolo do iterador por dentro

Questão 30 · aula Generator expressions e a memória

Qual é a diferença central entre (n*n for n in dados) e [n*n for n in dados]?

  1. Nenhuma, são sinônimos exatos.
  2. A dos parênteses é um gerador que produz valores sob demanda (memória quase constante); a dos colchetes constrói a lista inteira na memória.
  3. A dos colchetes é mais lenta em todos os casos.
  4. A dos parênteses só funciona com números.
Ver a resposta

Resposta: B. A dos parênteses é um gerador que produz valores sob demanda (memória quase constante); a dos colchetes constrói a lista inteira na memória.

Os parênteses criam uma generator expression, que calcula cada valor sob demanda e não guarda tudo, mantendo a memória quase constante e permitindo um único percurso. Os colchetes criam a lista completa de imediato, que ocupa memória proporcional ao tamanho, mas pode ser reutilizada.

Estudar a aula: Generator expressions e a memória

Questão 31 · aula Delegação com yield from

O que a linha yield from outro_gerador faz dentro de um gerador?

  1. Devolve o objeto gerador outro_gerador inteiro como um único valor.
  2. Produz, um a um, todos os valores de outro_gerador, delegando a iteração a ele.
  3. Encerra o gerador atual imediatamente.
  4. Converte o gerador em uma lista.
Ver a resposta

Resposta: B. Produz, um a um, todos os valores de outro_gerador, delegando a iteração a ele.

O yield from delega a produção ao gerador (ou iterável) interno: ele produz todos os valores desse objeto, um a um, como se fossem do gerador externo. Substitui o laço for x in outro: yield x e, no caso de corrotinas, também repassa valores enviados de volta.

Estudar a aula: Delegação com yield from

Questão 32 · aula Pipelines preguiçosos e geradores infinitos

Como pegar com segurança os primeiros 10 valores de um gerador infinito?

  1. Chamar list() no gerador e depois fatiar o resultado com [:10].
  2. Usar itertools.islice(gerador, 10), que consome só os 10 primeiros itens e para.
  3. Não é possível trabalhar com geradores infinitos.
  4. Converter o gerador em uma lista primeiro.
Ver a resposta

Resposta: B. Usar itertools.islice(gerador, 10), que consome só os 10 primeiros itens e para.

Chamar list() em um gerador infinito nunca termina, pois tenta materializar itens sem fim. O itertools.islice consome do gerador apenas a quantidade pedida, de forma preguiçosa, e para em seguida, sendo a forma segura de cortar uma sequência infinita.

Estudar a aula: Pipelines preguiçosos e geradores infinitos

Questão 33 · aula send e corrotinas: a ponte para o async

O que o método send faz em um gerador usado como corrotina?

  1. Encerra o gerador imediatamente.
  2. Retoma o gerador e envia um valor, que vira o resultado da expressão yield onde ele estava pausado.
  3. Cria uma cópia nova do gerador.
  4. Converte o gerador em uma função comum.
Ver a resposta

Resposta: B. Retoma o gerador e envia um valor, que vira o resultado da expressão yield onde ele estava pausado.

O send retoma a execução do gerador pausado em um yield e, ao mesmo tempo, injeta um valor que se torna o resultado dessa expressão yield. É o que permite a comunicação de mão dupla, característica das corrotinas, e é a raiz conceitual do async e await modernos.

Estudar a aula: send e corrotinas: a ponte para o async

Módulo 8: Concorrência com threads

Questão 34 · aula Concorrência, paralelismo e o GIL

Por que criar dez threads para fatorar dez números enormes não acelera o programa no CPython?

  1. Porque o Python não permite criar mais de uma thread.
  2. Porque o GIL deixa só uma thread executar bytecode Python por vez, e cálculo puro segura a trava o tempo todo, sem revezamento.
  3. Porque fatorar números não é possível com threads.
  4. Porque threads deixam qualquer programa mais lento.
Ver a resposta

Resposta: B. Porque o GIL deixa só uma thread executar bytecode Python por vez, e cálculo puro segura a trava o tempo todo, sem revezamento.

Fatorar é uma tarefa CPU bound: as threads só calculam, nunca esperam por I/O, então nenhuma libera o GIL. Com apenas uma thread rodando bytecode por vez, elas se serializam e o ganho some. Para paralelismo real de CPU, o caminho é multiprocessing.

Estudar a aula: Concorrência, paralelismo e o GIL

Questão 35 · aula Criando threads com threading

Por que dar start e join dentro do mesmo laço, para cada thread, elimina o ganho de tempo?

  1. Porque o start passa a não funcionar dentro de laços.
  2. Porque o join logo após o start faz o programa esperar cada thread terminar antes de disparar a próxima, virando execução sequencial.
  3. Porque o Python proíbe join dentro de laços.
  4. Porque args deixa de aceitar valores nesse caso.
Ver a resposta

Resposta: B. Porque o join logo após o start faz o programa esperar cada thread terminar antes de disparar a próxima, virando execução sequencial.

O join bloqueia até a thread acabar. Se você chama join logo depois do start, a próxima thread só começa quando a anterior termina, então nada se sobrepõe. O padrão certo é disparar todas com start e só então dar join em cada uma.

Estudar a aula: Criando threads com threading

Questão 36 · aula Race condition e o Lock

Por que contador += 1 pode dar resultado errado com duas threads sem trava?

  1. Porque o operador += não existe para inteiros.
  2. Porque a operação envolve ler, somar e gravar, e duas threads podem intercalar esses passos, perdendo incrementos.
  3. Porque threads não conseguem acessar variáveis globais.
  4. Porque o Python soma os valores em ordem alfabética.
Ver a resposta

Resposta: B. Porque a operação envolve ler, somar e gravar, e duas threads podem intercalar esses passos, perdendo incrementos.

O += não é atômico: são três passos separados. Se uma thread lê o valor, outra também lê o mesmo valor antes da primeira gravar, e ambas gravam em cima, um incremento se perde. O Lock garante que só uma thread execute esses passos por vez.

Estudar a aula: Race condition e o Lock

Questão 37 · aula Fila e o padrão produtor-consumidor

Para que serve colocar um valor sentinela (como None) na queue.Queue?

  1. Para acelerar o processamento dos itens.
  2. Para avisar o consumidor de que não há mais trabalho e ele pode sair do laço e encerrar.
  3. Para limpar a memória da fila automaticamente.
  4. Para transformar a fila em uma lista comum.
Ver a resposta

Resposta: B. Para avisar o consumidor de que não há mais trabalho e ele pode sair do laço e encerrar.

Um consumidor em while True fica esperando itens para sempre. O sentinela é um valor combinado que sinaliza o fim do trabalho: ao retirá-lo, o consumidor sai do laço. Com vários consumidores, coloca-se um sentinela para cada um.

Estudar a aula: Fila e o padrão produtor-consumidor

Questão 38 · aula ThreadPoolExecutor na prática

Você precisa processar milhares de números com cálculo pesado. Qual executor usar?

  1. ThreadPoolExecutor, porque threads são sempre mais rápidas.
  2. ProcessPoolExecutor, porque a tarefa é CPU bound e processos rodam em paralelo real, sem o GIL travar tudo.
  3. Nenhum: cálculo não pode ser paralelizado.
  4. ThreadPoolExecutor com mil threads para forçar o paralelismo.
Ver a resposta

Resposta: B. ProcessPoolExecutor, porque a tarefa é CPU bound e processos rodam em paralelo real, sem o GIL travar tudo.

Cálculo pesado é CPU bound. Com threads, o GIL serializa a execução e não há ganho. O ProcessPoolExecutor usa processos separados, cada um com seu interpretador, então aproveita os vários núcleos de verdade. A interface é quase idêntica à do ThreadPoolExecutor.

Estudar a aula: ThreadPoolExecutor na prática

Módulo 9: Async e asyncio

Questão 39 · aula O problema do I/O que espera

Qual a diferença central entre a concorrência de threads e a de async?

  1. Async usa vários núcleos e threads usa um só.
  2. Com threads a troca de tarefa é preemptiva (o sistema decide a qualquer hora); com async é cooperativa (só nos pontos de await).
  3. Async é sempre mais rápido para qualquer tarefa, inclusive cálculo.
  4. Threads não podem fazer I/O.
Ver a resposta

Resposta: B. Com threads a troca de tarefa é preemptiva (o sistema decide a qualquer hora); com async é cooperativa (só nos pontos de await).

Com threads, o sistema pode interromper e trocar a qualquer momento, o que gera race conditions e exige travas. Com async, a troca só ocorre nos pontos de await, num único thread; o modelo é cooperativo e mais previsível. Nenhum dos dois acelera cálculo puro no CPython.

Estudar a aula: O problema do I/O que espera

Questão 40 · aula Corrotinas: async def e await

O que acontece quando você escreve buscar('x') sem await, sendo buscar uma corrotina?

  1. A corrotina executa imediatamente e devolve o resultado.
  2. O Python cria e devolve um objeto corrotina, sem rodar o corpo; nada é executado até você aguardar com await.
  3. O programa para com um erro de sintaxe.
  4. A corrotina vira uma função comum.
Ver a resposta

Resposta: B. O Python cria e devolve um objeto corrotina, sem rodar o corpo; nada é executado até você aguardar com await.

Chamar uma corrotina não executa o corpo; apenas cria um objeto corrotina. É preciso aguardá-lo com await (dentro de outra corrotina) ou entregá-lo ao asyncio.run para que o trabalho aconteça. Esquecer o await gera o aviso de coroutine never awaited.

Estudar a aula: Corrotinas: async def e await

Questão 41 · aula O event loop e asyncio.run

Qual é o papel do asyncio.run em um programa assíncrono?

  1. Rodar cada corrotina em um núcleo diferente do processador.
  2. Ser a porta de entrada: iniciar o event loop, executar a corrotina principal e fechar o loop no fim.
  3. Substituir o await dentro das corrotinas.
  4. Transformar funções comuns em corrotinas automaticamente.
Ver a resposta

Resposta: B. Ser a porta de entrada: iniciar o event loop, executar a corrotina principal e fechar o loop no fim.

O asyncio.run é a fronteira entre o código comum e o assíncrono: ele cria o event loop, roda a corrotina principal com todos os seus awaits e fecha o loop de forma organizada. Há um único asyncio.run no topo; lá dentro você usa await e funções como gather.

Estudar a aula: O event loop e asyncio.run

Questão 42 · aula Rodando várias tarefas com gather

Por que asyncio.gather com três esperas de 1 segundo termina em cerca de 1 segundo, e não em 3?

  1. Porque o gather ignora duas das corrotinas.
  2. Porque ele agenda as três ao mesmo tempo no event loop, e as esperas se sobrepõem em vez de somar.
  3. Porque cada corrotina roda em um núcleo diferente.
  4. Porque o gather encurta o tempo de cada asyncio.sleep.
Ver a resposta

Resposta: B. Porque ele agenda as três ao mesmo tempo no event loop, e as esperas se sobrepõem em vez de somar.

O gather entrega as três corrotinas de uma vez ao event loop. Enquanto uma espera no seu asyncio.sleep, as outras também esperam nos delas, tudo sobreposto. O total vira o da mais demorada, cerca de 1 segundo, não a soma dos três.

Estudar a aula: Rodando várias tarefas com gather

Questão 43 · aula Async ou threads: como escolher

Por que usar time.sleep dentro de uma corrotina é um problema?

  1. Porque time.sleep não existe em programas assíncronos.
  2. Porque ele bloqueia o event loop: enquanto dorme, nenhuma outra corrotina avança, e a concorrência se perde.
  3. Porque ele acelera demais o event loop.
  4. Porque time.sleep converte a corrotina em thread.
Ver a resposta

Resposta: B. Porque ele bloqueia o event loop: enquanto dorme, nenhuma outra corrotina avança, e a concorrência se perde.

O event loop depende de as corrotinas cederem o controle nos pontos de await. O time.sleep é bloqueante: ele prende o único thread durante a pausa, então nenhuma outra corrotina roda. Dentro de async, use asyncio.sleep, que cede o loop enquanto espera.

Estudar a aula: Async ou threads: como escolher

Módulo 10: Orientação a objetos avançada

Questão 44 · aula property: getter, setter e validação

Qual a principal vantagem de usar @property com setter em vez de deixar o atributo público?

  1. Deixa o programa mais rápido.
  2. Permite validar o valor a cada atribuição, mantendo a sintaxe obj.atributo, sem obrigar quem usa a chamar métodos get e set.
  3. Impede totalmente que a classe seja usada.
  4. Remove a necessidade de __init__.
Ver a resposta

Resposta: B. Permite validar o valor a cada atribuição, mantendo a sintaxe obj.atributo, sem obrigar quem usa a chamar métodos get e set.

A property intercepta leitura e escrita: o setter valida o valor a cada atribuição, protegendo as invariantes da classe. E tudo isso sem mudar a interface, quem usa continua escrevendo obj.atributo em vez de chamar getPreco e setPreco. É o encapsulamento pythônico.

Estudar a aula: property: getter, setter e validação

Questão 45 · aula classmethod e staticmethod

Por que um construtor alternativo usa @classmethod com cls em vez de uma função comum?

  1. Porque funções comuns não podem criar objetos.
  2. Porque cls é a própria classe: chamar cls(...) cria a instância e faz subclasses criarem objetos delas mesmas automaticamente.
  3. Porque @classmethod deixa o código mais rápido.
  4. Porque construtores só funcionam dentro de decoradores.
Ver a resposta

Resposta: B. Porque cls é a própria classe: chamar cls(...) cria a instância e faz subclasses criarem objetos delas mesmas automaticamente.

O @classmethod recebe cls, a própria classe. Ao devolver cls(...), o construtor alternativo cria uma instância da classe recebida, então uma subclasse que herda o método produz objetos dela mesma sem precisar reescrever nada. É mais organizado e reutilizável que uma função solta.

Estudar a aula: classmethod e staticmethod

Questão 46 · aula Métodos dunder: integrando à linguagem

Qual a diferença entre __repr__ e __str__?

  1. São idênticos; ter os dois é redundância.
  2. __repr__ é a representação técnica para desenvolvedores; __str__ é a amigável para o usuário, e print prefere __str__.
  3. __str__ é para números e __repr__ para textos.
  4. __repr__ só funciona dentro de listas.
Ver a resposta

Resposta: B. __repr__ é a representação técnica para desenvolvedores; __str__ é a amigável para o usuário, e print prefere __str__.

O __repr__ dá a representação técnica, sem ambiguidade, útil para quem programa e usada em console e coleções. O __str__ dá a versão amigável para o usuário, preferida pelo print. Se definir só um, defina __repr__, pois ele serve de fallback para o print.

Estudar a aula: Métodos dunder: integrando à linguagem

Questão 47 · aula Herança múltipla e a ordem de resolução

Em herança múltipla, o que super() chama de verdade?

  1. Sempre a classe pai declarada em primeiro lugar.
  2. A próxima classe na ordem de resolução de métodos (MRO), que nem sempre é o pai direto.
  3. Todas as classes bases ao mesmo tempo.
  4. A classe object diretamente.
Ver a resposta

Resposta: B. A próxima classe na ordem de resolução de métodos (MRO), que nem sempre é o pai direto.

super() chama o próximo método na MRO, não necessariamente o pai. É isso que torna a herança cooperativa: quando cada classe chama super(), a cadeia percorre a MRO e cada classe contribui uma vez, fazendo a base comum rodar uma única vez no problema do diamante.

Estudar a aula: Herança múltipla e a ordem de resolução

Questão 48 · aula Composição, herança e __slots__

O que o __slots__ oferece e o que ele cobra em troca?

  1. Deixa o código mais bonito, sem nenhum custo.
  2. Economiza memória ao dispensar o dicionário interno da instância, mas impede acrescentar atributos fora da lista declarada.
  3. Torna a classe mais rápida ao remover todos os métodos.
  4. Permite herança múltipla ilimitada.
Ver a resposta

Resposta: B. Economiza memória ao dispensar o dicionário interno da instância, mas impede acrescentar atributos fora da lista declarada.

Ao declarar __slots__, o Python reserva espaço fixo para os atributos e remove o __dict__ por instância, o que economiza memória de forma significativa com muitas instâncias. Em troca, você não pode criar atributos fora da lista. Vale para classes simples e numerosas com estrutura fixa.

Estudar a aula: Composição, herança e __slots__

Módulo 11: Metaclasses e descritores

Questão 49 · aula Tudo é objeto: até as classes

Em Python, qual é o resultado de type(int)?

  1. O número inteiro zero.
  2. A própria classe int.
  3. type, porque toda classe (inclusive int) é uma instância de type.
  4. Um erro, porque int não tem tipo.
Ver a resposta

Resposta: C. type, porque toda classe (inclusive int) é uma instância de type.

int é uma classe, e toda classe em Python é uma instância de type. Por isso type(int) devolve type. O mesmo vale para str, list e qualquer classe que você declarar com a palavra class.

Estudar a aula: Tudo é objeto: até as classes

Questão 50 · aula O que é uma metaclass

Quando os métodos __new__ e __init__ de uma metaclass são executados?

  1. Toda vez que um objeto da classe é instanciado.
  2. No momento em que a própria classe é criada (definida).
  3. Somente quando o programa termina.
  4. Nunca, a menos que você chame type() na mão.
Ver a resposta

Resposta: B. No momento em que a própria classe é criada (definida).

Uma metaclass controla a criação de classes, não de objetos. Seu __new__ e __init__ rodam quando a classe é definida. Para interceptar a criação de objetos, o método certo é o __new__ ou __init__ da própria classe, não da metaclass.

Estudar a aula: O que é uma metaclass

Questão 51 · aula Descritores: como @property funciona por dentro

Qual afirmação sobre @property e descritores está correta?

  1. @property não tem relação com descritores; é um recurso separado.
  2. @property é um descritor pronto: cria um objeto com __get__, __set__ e __delete__ ligados às suas funções.
  3. Descritores só funcionam com números.
  4. Um descritor precisa obrigatoriamente definir __get__, __set__ e __delete__ ao mesmo tempo.
Ver a resposta

Resposta: B. @property é um descritor pronto: cria um objeto com __get__, __set__ e __delete__ ligados às suas funções.

property é implementada como um descritor: ao usar @property, o Python monta um objeto com __get__ (e, se você definir, __set__ e __delete__) apontando para as suas funções. Um descritor precisa de apenas um desses métodos para existir, não dos três.

Estudar a aula: Descritores: como @property funciona por dentro

Questão 52 · aula Interceptar atributos: __getattr__ e amigos

Qual a diferença central entre __getattr__ e __getattribute__?

  1. Não há diferença; são dois nomes para o mesmo método.
  2. __getattr__ é chamado só quando o atributo não é encontrado; __getattribute__ é chamado em todo acesso.
  3. __getattr__ é para escrita e __getattribute__ para leitura.
  4. __getattribute__ só funciona em classes com metaclass.
Ver a resposta

Resposta: B. __getattr__ é chamado só quando o atributo não é encontrado; __getattribute__ é chamado em todo acesso.

__getattribute__ intercepta todo acesso a atributo, exista ele ou não, e por isso é perigoso e raramente sobrescrito. __getattr__ é o plano B, chamado apenas quando a busca normal falha, o que o torna ideal e seguro para proxies e valores dinâmicos.

Estudar a aula: Interceptar atributos: __getattr__ e amigos

Questão 53 · aula __init_subclass__: o atalho para quase tudo

Você quer registrar automaticamente cada subclasse de uma classe-base. Qual a primeira escolha?

  1. Escrever uma metaclass, pois é o único jeito de reagir a subclasses.
  2. Usar __init_subclass__ na classe-base, que reage à criação de subclasses sem metaclass.
  3. Usar __getattribute__ em cada subclasse.
  4. Chamar type() na mão para cada subclasse.
Ver a resposta

Resposta: B. Usar __init_subclass__ na classe-base, que reage à criação de subclasses sem metaclass.

__init_subclass__ resolve o registro automático de subclasses de forma simples e legível, sem os riscos e a complexidade de uma metaclass. A metaclass fica reservada para os casos que nenhuma alternativa mais leve cobre.

Estudar a aula: __init_subclass__: o atalho para quase tudo

Módulo 12: Exceções, logging e robustez

Questão 54 · aula Uma hierarquia de exceções do seu domínio

Por que ordenar os blocos except do mais específico para o mais geral?

  1. Por questão de estilo apenas; a ordem não afeta o resultado.
  2. Porque o Python usa o primeiro except que casa; a base primeiro capturaria tudo e o específico nunca rodaria.
  3. Porque except geral não funciona depois de um específico.
  4. Para o programa rodar mais rápido.
Ver a resposta

Resposta: B. Porque o Python usa o primeiro except que casa; a base primeiro capturaria tudo e o específico nunca rodaria.

O Python testa os except de cima para baixo e para no primeiro que casa. Se a base (mais geral) vier antes, ela captura o erro e o bloco específico nunca é alcançado. Por isso o específico vem primeiro e a base por último.

Estudar a aula: Uma hierarquia de exceções do seu domínio

Questão 55 · aula raise from: encadear erros sem perder a causa

O que raise NovoErro(...) from erro_original faz?

  1. Descarta o erro original e levanta só o novo.
  2. Levanta o novo erro preservando o original em __cause__, para o rastreamento mostrar a causa.
  3. Impede que o novo erro seja capturado.
  4. Transforma o erro em um aviso.
Ver a resposta

Resposta: B. Levanta o novo erro preservando o original em __cause__, para o rastreamento mostrar a causa.

O raise ... from ... levanta a nova exceção e guarda a original em __cause__. O rastreamento então mostra as duas, com a frase A causa direta da exceção acima foi, contando a história completa da falha.

Estudar a aula: raise from: encadear erros sem perder a causa

Questão 56 · aula else, finally e o estilo EAFP

Por que o estilo EAFP costuma ser preferido a LBYL em Python?

  1. Porque exceções nunca custam nada.
  2. Porque evita checagens redundantes e a condição de corrida entre checar e agir, além de ser mais idiomático.
  3. Porque LBYL não funciona com dicionários.
  4. Porque o Python proíbe o uso de if antes de acessar dados.
Ver a resposta

Resposta: B. Porque evita checagens redundantes e a condição de corrida entre checar e agir, além de ser mais idiomático.

EAFP tenta a operação e trata a exceção, evitando duplicar a lógica de checagem e a janela de corrida em que o estado muda entre o if e o uso. É o estilo idiomático de Python, embora LBYL ainda sirva quando a falha é comum e a checagem é barata.

Estudar a aula: else, finally e o estilo EAFP

Questão 57 · aula O módulo logging: adeus, print

Qual a principal vantagem do logging sobre o print em produção?

  1. logging é sempre mais rápido que print em qualquer situação.
  2. logging separa níveis de severidade e destino configurável, permitindo filtrar e redirecionar sem mexer no código.
  3. print não funciona fora do Playground.
  4. logging escreve automaticamente em bancos de dados.
Ver a resposta

Resposta: B. logging separa níveis de severidade e destino configurável, permitindo filtrar e redirecionar sem mexer no código.

O logging tem níveis (DEBUG a CRITICAL) e destinos configuráveis por handlers e formatters. Você decide, na configuração, o que ver e para onde mandar, sem editar o código instrumentado. O print manda tudo igual para uma só saída, sem esse controle.

Estudar a aula: O módulo logging: adeus, print

Questão 58 · aula Programação defensiva: validar cedo, falhar claro

Por que não se deve validar a entrada do usuário com assert?

  1. Porque assert é mais lento que um if.
  2. Porque os assert podem ser desligados (rodando com -O), então a validação sumiria em produção otimizada.
  3. Porque assert não aceita mensagens.
  4. Porque assert só funciona com números.
Ver a resposta

Resposta: B. Porque os assert podem ser desligados (rodando com -O), então a validação sumiria em produção otimizada.

Os assert são desativados quando o Python roda no modo otimizado (-O). Se a validação de entrada dependesse deles, ela desapareceria em produção. Por isso assert serve para invariantes internas, e a validação de dados externos é feita com um if que levanta uma exceção.

Estudar a aula: Programação defensiva: validar cedo, falhar claro

Módulo 13: Testes avançados

Questão 59 · aula pytest: o teste que só usa assert

No pytest, o que caracteriza uma função de teste?

  1. Ela precisa herdar de uma classe TestCase.
  2. O nome começa com test_ e ela usa assert para verificar o resultado.
  3. Ela precisa retornar True ou False.
  4. Ela deve estar dentro da função que está testando.
Ver a resposta

Resposta: B. O nome começa com test_ e ela usa assert para verificar o resultado.

No pytest, um teste é apenas uma função cujo nome começa com test_ e que usa a instrução assert. Não há necessidade de herdar de classe nem de retornar nada: se o assert é verdadeiro, o teste passa; se é falso, falha com um relatório detalhado.

Estudar a aula: pytest: o teste que só usa assert

Questão 60 · aula Fixtures: preparar o cenário do teste

Numa fixture do pytest que usa yield, o que é o código escrito depois do yield?

  1. Código morto, que nunca roda.
  2. O teardown: a limpeza executada após o teste terminar, mesmo se ele falhar.
  3. Um segundo teste embutido na fixture.
  4. A parte que prepara o cenário antes do teste.
Ver a resposta

Resposta: B. O teardown: a limpeza executada após o teste terminar, mesmo se ele falhar.

Numa fixture com yield, o código antes do yield é o setup e o de depois é o teardown, que o pytest executa quando o teste termina, inclusive em caso de falha. É o jeito idiomático de garantir a limpeza de recursos.

Estudar a aula: Fixtures: preparar o cenário do teste

Questão 61 · aula parametrize e testar exceções

Como você testa que uma função levanta ValueError para uma entrada inválida?

  1. Chama a função e espera o teste quebrar sozinho.
  2. Usa with pytest.raises(ValueError): e chama a função dentro do bloco.
  3. Coloca a chamada dentro de um assert comum.
  4. Não é possível testar exceções com pytest.
Ver a resposta

Resposta: B. Usa with pytest.raises(ValueError): e chama a função dentro do bloco.

O with pytest.raises(ValueError): afirma que o bloco levanta a exceção esperada. Se ela ocorre, o teste passa; se não, falha. O parâmetro match ainda permite conferir a mensagem, garantindo que o erro é o certo pelo motivo certo.

Estudar a aula: parametrize e testar exceções

Questão 62 · aula Mocks e monkeypatch: isolar dependências

Por que usar um mock ao testar código que envia e-mail?

  1. Para o teste enviar e-mails de verdade e conferir a caixa de entrada.
  2. Para substituir o envio real por um dublê, evitando o efeito colateral e verificando se o código o chamou corretamente.
  3. Porque mocks deixam o e-mail chegar mais rápido.
  4. Para testar se o servidor de e-mail da empresa está no ar.
Ver a resposta

Resposta: B. Para substituir o envio real por um dublê, evitando o efeito colateral e verificando se o código o chamou corretamente.

O mock substitui a dependência externa por um dublê controlado, evitando enviar e-mail de verdade e permitindo verificar que o seu código chamou o envio com os argumentos certos. O teste foca no seu código, não na infraestrutura de e-mail.

Estudar a aula: Mocks e monkeypatch: isolar dependências

Questão 63 · aula Cobertura e TDD na prática

Qual é a ordem correta do ciclo TDD?

  1. Escrever o código, depois o teste, depois apagar o teste.
  2. Red (teste que falha), green (código mínimo que passa) e refactor (melhorar com o teste protegendo).
  3. Refactor, green e red, nessa ordem.
  4. Medir a cobertura, escrever o código e nunca testar.
Ver a resposta

Resposta: B. Red (teste que falha), green (código mínimo que passa) e refactor (melhorar com o teste protegendo).

O ciclo TDD é red, green, refactor: primeiro um teste que falha porque o comportamento não existe, depois o mínimo de código para ele passar, e por fim a melhoria do código com a segurança de que o teste verde avisa se algo quebrar.

Estudar a aula: Cobertura e TDD na prática

Módulo 14: Performance e profiling

Questão 64 · aula Medir antes de otimizar com timeit

Por que a regra é medir antes de otimizar?

  1. Porque medir deixa o programa mais rápido sozinho.
  2. Porque a intuição sobre o que é lento costuma errar, e sem medir você arrisca otimizar fora do gargalo.
  3. Porque o Python proíbe otimizar sem medir.
  4. Porque medir substitui a necessidade de testes.
Ver a resposta

Resposta: B. Porque a intuição sobre o que é lento costuma errar, e sem medir você arrisca otimizar fora do gargalo.

A intuição humana sobre gargalos é ruim. Medir mostra onde o tempo realmente vai, evitando que você gaste esforço reescrevendo um trecho que não é o vilão. Só depois de saber onde está o gargalo faz sentido otimizar.

Estudar a aula: Medir antes de otimizar com timeit

Questão 65 · aula Achar o gargalo com cProfile e pstats

No relatório do cProfile, qual a diferença entre tottime e cumtime?

  1. São a mesma coisa com nomes diferentes.
  2. tottime é o tempo só no corpo da função; cumtime inclui também o tempo das funções que ela chama.
  3. tottime inclui as chamadas internas; cumtime não.
  4. cumtime conta apenas o número de chamadas.
Ver a resposta

Resposta: B. tottime é o tempo só no corpo da função; cumtime inclui também o tempo das funções que ela chama.

tottime mede o tempo gasto apenas no corpo da própria função. cumtime é cumulativo: soma o corpo da função com tudo que ela chama por dentro. Uma função que só delega trabalho tem cumtime alto e tottime baixo.

Estudar a aula: Achar o gargalo com cProfile e pstats

Questão 66 · aula Complexidade e a estrutura de dados certa

Por que buscar um valor com in é muito mais rápido em set do que em lista?

  1. Porque set ocupa menos memória que lista.
  2. Porque o set usa uma tabela de dispersão e vai quase direto ao valor (O(1)), enquanto a lista varre item por item (O(n)).
  3. Porque o set está sempre ordenado.
  4. Porque a lista não permite o operador in.
Ver a resposta

Resposta: B. Porque o set usa uma tabela de dispersão e vai quase direto ao valor (O(1)), enquanto a lista varre item por item (O(n)).

O set é uma tabela de dispersão: calcula onde o valor deveria estar e verifica quase direto, em tempo praticamente constante. A lista precisa varrer do início até achar, um custo que cresce com o tamanho. Por isso a busca em set é O(1) e a busca em lista é O(n).

Estudar a aula: Complexidade e a estrutura de dados certa

Questão 67 · aula Estruturas eficientes: deque, heapq e bisect

Por que usar deque em vez de lista para uma fila que remove do início?

  1. Porque a deque ocupa menos memória sempre.
  2. Porque remover do início de uma lista é O(n), enquanto na deque é O(1).
  3. Porque a lista não permite remover do início.
  4. Porque a deque mantém os itens ordenados automaticamente.
Ver a resposta

Resposta: B. Porque remover do início de uma lista é O(n), enquanto na deque é O(1).

Remover do início de uma lista força o Python a deslocar todos os outros itens, um custo O(n). A deque é feita para as duas pontas: adicionar e remover no início ou no fim é O(1). Para filas, essa diferença se acumula rápido.

Estudar a aula: Estruturas eficientes: deque, heapq e bisect

Questão 68 · aula Memoização com lru_cache e geradores para memória

Por que concatenar strings com += dentro de um laço é lento?

  1. Porque o operador += não funciona com strings.
  2. Porque strings são imutáveis, então cada += cria uma nova string e copia todo o conteúdo anterior, virando O(n ao quadrado).
  3. Porque o Python bloqueia laços com strings.
  4. Porque strings pequenas não cabem na memória.
Ver a resposta

Resposta: B. Porque strings são imutáveis, então cada += cria uma nova string e copia todo o conteúdo anterior, virando O(n ao quadrado).

Strings são imutáveis: cada += não altera a string, ele cria uma nova copiando tudo que já existia mais o novo pedaço. Repetir isso num laço faz o custo crescer com o quadrado do tamanho. Acumular numa lista e usar str.join uma vez resolve em tempo linear.

Estudar a aula: Memoização com lru_cache e geradores para memória

Módulo 15: Empacotamento e boas práticas

Questão 69 · aula Estrutura de projeto e pacotes

Qual a diferença entre um módulo e um pacote em Python?

  1. Não há diferença, são sinônimos.
  2. Módulo é um arquivo .py; pacote é uma pasta que agrupa módulos e é marcada por um __init__.py.
  3. Pacote é um arquivo e módulo é uma pasta.
  4. Módulo só existe na biblioteca padrão.
Ver a resposta

Resposta: B. Módulo é um arquivo .py; pacote é uma pasta que agrupa módulos e é marcada por um __init__.py.

Um módulo é um único arquivo .py. Um pacote é uma pasta que reúne módulos relacionados e, tradicionalmente, contém um __init__.py que a marca como pacote importável. O pacote dá namespace e organização a um conjunto de módulos.

Estudar a aula: Estrutura de projeto e pacotes

Questão 70 · aula pyproject.toml e imports bem resolvidos

Segundo a PEP 8, qual estilo de import é o preferido por padrão?

  1. O import relativo implícito, sem pontos.
  2. O import absoluto, que nomeia o caminho completo a partir da raiz do pacote.
  3. Copiar o código em vez de importar.
  4. Sempre usar dois pontos para subir de nível.
Ver a resposta

Resposta: B. O import absoluto, que nomeia o caminho completo a partir da raiz do pacote.

A PEP 8 recomenda imports absolutos como padrão, porque são explícitos, fáceis de rastrear e resistem melhor à reorganização do projeto. Imports relativos com ponto são aceitáveis dentro de pacotes, mas o relativo implícito foi removido do Python moderno.

Estudar a aula: pyproject.toml e imports bem resolvidos

Questão 71 · aula PEP 8 e ferramentas de estilo

Qual é a principal vantagem de usar um formatador como o black?

  1. Ele encontra bugs de lógica no programa.
  2. Ele reescreve o código num estilo único e determinístico, encerrando as discussões de formatação na equipe.
  3. Ele executa os testes automaticamente.
  4. Ele verifica os tipos do código.
Ver a resposta

Resposta: B. Ele reescreve o código num estilo único e determinístico, encerrando as discussões de formatação na equipe.

O black aplica um estilo único e previsível ao código, sem opções para debater. Isso garante consistência e, principalmente, acaba com as discussões de formatação em revisão de código. Encontrar bugs, verificar tipos e rodar testes são papéis de outras ferramentas.

Estudar a aula: PEP 8 e ferramentas de estilo

Questão 72 · aula Princípios SOLID em Python

O que diz o princípio da responsabilidade única?

  1. Cada função deve ter uma única linha.
  2. Uma classe deve ter apenas um motivo para mudar, concentrando uma única responsabilidade.
  3. Cada programa deve ter só uma classe.
  4. Uma classe deve implementar todos os métodos possíveis.
Ver a resposta

Resposta: B. Uma classe deve ter apenas um motivo para mudar, concentrando uma única responsabilidade.

A responsabilidade única prega que uma classe tenha um só motivo para mudar, ou seja, uma responsabilidade bem definida. Uma classe que calcula, formata e envia mistura três assuntos e três motivos de mudança. Separar cada responsabilidade torna o código mais fácil de alterar sem quebrar o resto.

Estudar a aula: Princípios SOLID em Python

Questão 73 · aula Padrões pythônicos e código limpo

O que o princípio DRY recomenda?

  1. Escrever o código mais curto possível, a qualquer custo.
  2. Não duplicar conhecimento: cada lógica deve ter uma representação única, para que a mudança aconteça em um lugar só.
  3. Eliminar todos os comentários do código.
  4. Repetir a lógica em vários lugares por segurança.
Ver a resposta

Resposta: B. Não duplicar conhecimento: cada lógica deve ter uma representação única, para que a mudança aconteça em um lugar só.

DRY significa não se repetir: cada pedaço de conhecimento ou lógica deve morar em um único lugar. Assim, uma correção é feita uma vez só, sem risco de esquecer uma cópia. Não é sobre reduzir linhas a qualquer custo, e sim sobre evitar a duplicação de conhecimento.

Estudar a aula: Padrões pythônicos e código limpo

Módulo 16: Projeto final: mini biblioteca de utilidades

Questão 74 · aula Visão do projeto e arquitetura

Por que dividir o pacote catalogo em módulos separados por responsabilidade?

  1. Para o código rodar mais rápido.
  2. Para que cada módulo tenha um assunto claro, deixando o projeto navegável e as mudanças locais.
  3. Porque o Python exige um módulo por classe.
  4. Para aumentar o número de arquivos.
Ver a resposta

Resposta: B. Para que cada módulo tenha um assunto claro, deixando o projeto navegável e as mudanças locais.

Dividir por responsabilidade, aplicando a responsabilidade única, faz cada módulo tratar de um assunto que cabe numa frase. Isso torna o projeto navegável e localiza as mudanças: alterar o cálculo de preço não afeta a definição dos modelos nem a infraestrutura.

Estudar a aula: Visão do projeto e arquitetura

Questão 75 · aula Dataclasses tipadas e Enum do domínio

Para que serve o método __post_init__ numa dataclass?

  1. Para substituir o __init__ inteiro manualmente.
  2. Para rodar validações ou ajustes logo após a dataclass montar o objeto, garantindo que ele seja válido.
  3. Para tornar a classe mais rápida.
  4. Para gerar o __repr__.
Ver a resposta

Resposta: B. Para rodar validações ou ajustes logo após a dataclass montar o objeto, garantindo que ele seja válido.

O __post_init__ é chamado automaticamente após a dataclass criar o objeto com os campos. É o lugar certo para validar as regras do domínio, como nome não vazio e preço não negativo, levantando erro na criação. Assim, se o objeto existe, ele é válido.

Estudar a aula: Dataclasses tipadas e Enum do domínio

Questão 76 · aula Decoradores próprios e context manager

No context manager criado com contextmanager, o que roda depois do yield?

  1. Nada, o yield encerra a função.
  2. O código de saída, executado quando o bloco with termina, mesmo se houver erro (por estar no finally).
  3. O código que roda antes do bloco with.
  4. Apenas o valor de retorno da função.
Ver a resposta

Resposta: B. O código de saída, executado quando o bloco with termina, mesmo se houver erro (por estar no finally).

No context manager feito com contextmanager, o código antes do yield roda na entrada do with e o código depois do yield roda na saída. Colocando-o num finally, ele executa mesmo se ocorrer um erro dentro do bloco, garantindo a limpeza ou, aqui, a medição do tempo.

Estudar a aula: Decoradores próprios e context manager

Questão 77 · aula Exceções próprias e logging

Qual a vantagem de uma hierarquia de exceções com uma base CatalogoError?

  1. Faz o código rodar mais rápido.
  2. Permite capturar um erro específico ou, com a base, qualquer erro da biblioteca de uma vez.
  3. Dispensa o uso de try e except.
  4. Impede que a biblioteca levante erros.
Ver a resposta

Resposta: B. Permite capturar um erro específico ou, com a base, qualquer erro da biblioteca de uma vez.

Com uma exceção base da qual todas herdam, quem usa a biblioteca pode capturar um erro exato, como PrecoInvalidoError, para tratar aquele caso, ou capturar a base CatalogoError para pegar qualquer falha da biblioteca de uma vez. A hierarquia dá essa flexibilidade de granularidade.

Estudar a aula: Exceções próprias e logging

Questão 78 · aula Testes com pytest, empacotamento e encerramento

Por que testar também os casos de erro, e não só o caminho feliz?

  1. Porque o pytest exige pelo menos um teste de erro.
  2. Porque provar que o código falha do jeito certo, com a exceção esperada, é parte da garantia e trava regressões.
  3. Porque testes de erro deixam o código mais rápido.
  4. Porque o caminho feliz não precisa de teste.
Ver a resposta

Resposta: B. Porque provar que o código falha do jeito certo, com a exceção esperada, é parte da garantia e trava regressões.

Testar os casos de erro com pytest.raises prova que a biblioteca falha de forma previsível, levantando a exceção certa diante de entradas inválidas. Isso documenta o comportamento e impede que uma mudança futura quebre essa garantia sem ninguém perceber. Acertos e erros, os dois importam.

Estudar a aula: Testes com pytest, empacotamento e encerramento

Terminou a lista?

Quem acertou quase tudo já pode encarar o exame final do curso, com 20 questões sorteadas e aprovação a partir de 70%. Quem errou bastante ganha mais voltando às aulas dos módulos em que tropeçou: cada questão acima leva direto à aula certa.

Ir para o Curso de Python Avançado