Trilha de aprendizado · Nível 11 · Tutorial 11

Criar gerenciadores de contexto com contextlib

Ao concluir, você poderá expressar ciclos simples de aquisição e liberação com contextlib.contextmanager, usando um gerador sem comprometer a propagação de falhas.

  • Nível: Avançado
  • Duração: 20 min
  • 8 passos
Criar gerenciadores de contexto com contextlib

O que você vai percorrer

  1. Do gerador ao gerenciador de contexto Use @contextmanager para transformar um gerador em um gerenciador utilizável com with e acompanhar suas três fases de execução. 2 min
  2. Respeitar o contrato de um único yield Analise os caminhos de execução de um gerenciador criado com @contextmanager e garanta que cada entrada bem-sucedida produza exatamente um valor. 2 min
  3. Acompanhar a exceção até o yield Veja os dois caminhos de saída de um contexto baseado em gerador e acompanhe o destino de uma exceção levantada dentro do bloco with. 3 min
  4. Proteger a liberação com finally Use try/finally ao redor do yield para liberar um recurso simulado tanto após o uso normal quanto quando o bloco with falha. 3 min
  5. Observar falhas sem escondê-las Registre a falha que ocorreu dentro do bloco with sem impedir que ela continue para o código externo. 3 min
  6. Criar um gerenciador para cada uso Cada chamada a uma função decorada com @contextmanager cria um gerenciador destinado a um único ciclo de entrada e saída. 2 min
  7. Escolher entre gerador e classe Escolha a forma de implementação pela clareza do ciclo de vida e da interface necessária. 2 min
  8. Aplicação final: um contexto que libera e propaga Prática final para validar aquisição, liberação e propagação de falhas em um gerenciador baseado em gerador. 5 min

O que você vai aprender

  • Implementar um gerenciador de contexto com @contextmanager e um único yield por entrada bem-sucedida.
  • Organizar a liberação em finally para atender tanto ao sucesso quanto à falha do bloco consumidor.
  • Explicar como uma exceção do bloco with chega ao ponto do yield.
  • Evitar reutilização indevida do gerenciador e supressão acidental de exceções.
  • Escolher entre a implementação com classe e a implementação baseada em gerador.

Antes de começar

  • Criar gerenciadores de contexto com classes
  • Produzir valores sob demanda com yield
  • Criar decoradores com functools.wraps

Passo 1 de 8

Do gerador ao gerenciador de contexto

Use @contextmanager para transformar um gerador em um gerenciador utilizável com with e acompanhar suas três fases de execução.

Um adaptador para o protocolo de contexto

Do gerador ao with

Você já implementou o protocolo diretamente com __enter__ e __exit__. Com contextlib.contextmanager, uma função geradora pode expressar o mesmo ciclo de forma linear:

<ul><li>o código antes de yield prepara o contexto;</li><li>o valor do yield vai para o nome após as;</li><li>o código depois de yield é retomado quando o bloco with termina normalmente.</li></ul>

O decorador adapta essa função geradora para que ela possa ser usada por with.

As três fases do contexto

Diagrama em três partes mostrando preparação antes do yield, execução do bloco with durante a suspensão do gerador e retomada após o bloco.

O yield é a fronteira entre o gerenciador e o código consumidor.

Ler o ciclo no código

Um rastreamento em memória

Este exemplo só registra eventos em uma lista. Ainda não há um recurso real para liberar.

python
from contextlib import contextmanager

@contextmanager
def registrar(eventos):
    eventos.append("preparar")
    yield "sessao-42"
    eventos.append("retomar")


eventos = []
contexto = registrar(eventos)
eventos.append("gerenciador criado")

with contexto as sessao:
    eventos.append(f"consumir {sessao}")

print(eventos)
# ['gerenciador criado', 'preparar', 'consumir sessao-42', 'retomar']

A chamada ainda não executa o corpo

registrar(eventos) cria um gerenciador; por isso, "gerenciador criado" aparece antes de "preparar".

Na entrada do with, o gerador avança até yield "sessao-42". O nome sessao recebe a string "sessao-42", e não o objeto gerenciador. Ao fim normal do bloco, o gerador é retomado e registra "retomar".

Dica

Leitura útil

Pense no yield como uma porta: o gerenciador chega até ela, entrega um valor ao consumidor e fica suspenso enquanto o bloco with executa.

Mapeie cada trecho à sua fase

Fases do exemplo

Relacione cada elemento do código à fase que ele representa.

Toque em um item e depois no par correspondente.

Passo 2 de 8

Respeitar o contrato de um único yield

Analise os caminhos de execução de um gerenciador criado com @contextmanager e garanta que cada entrada bem-sucedida produza exatamente um valor.

Um valor na entrada, nenhum na saída

O contrato do decorador

Uma função decorada com @contextmanager deve executar yield exatamente uma vez em cada entrada que for bem-sucedida.

Não conte apenas quantos yield aparecem no código: acompanhe o caminho realmente percorrido. Na entrada, o gerador precisa chegar ao primeiro yield. Na saída do with, ele pode terminar, mas não pode produzir outro valor.

Dois momentos do mesmo gerador

O mesmo yield separa a entrada da saída do contexto.

Diagrama de fluxo com uma preparação levando a um único ponto yield, seguido do bloco with e uma finalização; dois caminhos incorretos mostram parada antes do yield e uma segunda produção após o bloco.

Entrada: chegar a um yield. Saída: retomar e encerrar sem outro yield.

Dois erros de quantidade

Exemplo

Terminar antes de entregar

from contextlib import contextmanager

@contextmanager
def acesso(ativo):
    if not ativo:
        return
    yield "recurso"

with acesso(False) as valor:
    print(valor)

Com ativo=False, a função termina normalmente antes do primeiro yield. Como a entrada não recebeu valor algum, contextlib sinaliza RuntimeError.

Exemplo

Entregar de novo na saída

from contextlib import contextmanager

@contextmanager
def acesso():
    yield "primeiro"
    yield "segundo"

with acesso() as valor:
    print(valor)

O primeiro valor é associado a valor. Ao sair do with, o gerador é retomado e tenta produzir "segundo". Isso também gera RuntimeError: um gerenciador não pode fornecer um segundo valor nessa etapa.

Atenção

Return não é preparação com falha

Um return antes do primeiro yield encerra o gerador normalmente e viola o contrato, resultando em RuntimeError. Já um raise ValueError(...) na preparação é uma falha explícita: essa exceção é propagada, e o bloco with não chega a começar.

Siga o caminho efetivo

Condição e laço podem mudar o contrato

Analise o número de valores produzidos em cada chamada.

python
from contextlib import contextmanager

@contextmanager
def lote(itens):
    for item in itens:
        yield item

with lote(["A", "B"]) as item:
    print(item)

O que acontece aqui?

O laço não torna vários recursos disponíveis dentro do mesmo with. Ele produz "A" na entrada e tenta produzir "B" na saída. Portanto, esse uso viola o contrato e gera RuntimeError.

Para ser válido, cada caminho de uma entrada bem-sucedida deve alcançar um único yield, e a retomada posterior deve encerrar o gerador.

Classifique cada caminho

Qual é o resultado?

Considere:

@contextmanager
def conexao(modo):
    if modo == "fechado":
        return
    if modo == "indisponivel":
        raise ConnectionError("serviço indisponível")
    yield "conectado"

O que ocorre em with conexao("fechado") as estado:?

Escolha o caminho válido

Qual implementação respeita o contrato para qualquer valor de ativo?

Passo 3 de 8

Acompanhar a exceção até o yield

Veja os dois caminhos de saída de um contexto baseado em gerador e acompanhe o destino de uma exceção levantada dentro do bloco with.

Dois caminhos a partir do yield

O ponto de suspensão também recebe a falha

Depois de entregar um valor com yield, o gerador fica suspenso enquanto o bloco with é executado. Se o bloco termina normalmente, o gerador é retomado e segue para a próxima instrução.

Se o bloco levanta uma exceção, o adaptador criado por @contextmanager a reintroduz exatamente no ponto em que o gerador estava suspenso: o yield. Sem código que trate essa exceção, ela interrompe o gerador e continua para fora do with.

Fluxo normal e fluxo excepcional

Compare o retorno normal ao gerador com o caminho de uma falha no bloco consumidor.

Diagrama com um gerador pausado no yield. Um caminho segue do bloco with concluído até as instruções posteriores ao yield; outro caminho mostra uma exceção voltando do bloco with para o yield e saindo do contexto.

Na saída excepcional, a exceção chega ao yield; por isso, uma instrução comum logo depois dele não é alcançada.

Uma instrução após yield não é garantia de execução

Exemplo

Execução normal

Com este gerenciador, a saída normal alcança a instrução após o yield:

<code>eventos = []

with observar(eventos) as valor:
eventos.append(f"consumir {valor}")

print(eventos)

['preparar', 'consumir conexão', 'retomar normalmente']</code>

Execução com falha no consumidor

Execute este script no seu computador e observe a lista impressa.

python
from contextlib import contextmanager

@contextmanager
def observar(eventos):
    eventos.append("preparar")
    yield "conexão"
    eventos.append("retomar normalmente")

eventos = []

try:
    with observar(eventos) as valor:
        eventos.append(f"consumir {valor}")
        raise ValueError("falha do consumidor")
except ValueError as erro:
    eventos.append(f"fora do with: {erro}")

print(eventos)
# ['preparar', 'consumir conexão', 'fora do with: falha do consumidor']

Dica

Leia a ausência como evidência

A mensagem "retomar normalmente" não aparece porque a exceção interrompe o gerador no próprio yield. O except externo apenas torna visível que a mesma falha continuou sua propagação para fora do with.

Reconstrua o caminho da falha

Ordene os eventos

Considere o script anterior. Coloque os eventos na ordem em que ocorrem quando o bloco with levanta ValueError.

  1. O bloco with levanta ValueError.
  2. A exceção chega ao yield suspenso; a instrução posterior ao yield é ignorada.
  3. ValueError continua para fora do with e é capturada pelo except externo.
  4. O gerador executa a preparação e para no yield.
  5. O bloco with recebe o valor e inicia seu trabalho.

Passo 4 de 8

Proteger a liberação com finally

Use try/finally ao redor do yield para liberar um recurso simulado tanto após o uso normal quanto quando o bloco with falha.

A região protegida começa após adquirir

Aquira, entregue e libere

Em um gerenciador criado com @contextmanager, organize um ciclo linear:

  1. adquira o recurso;
  2. entre em try;
  3. entregue o valor com yield;
  4. libere o recurso em finally.

Depois de uma entrada bem-sucedida, o gerador fica suspenso no yield enquanto o corpo do with executa. Ao sair do bloco — normalmente ou por exceção — ele é retomado e o finally executa a liberação.

Dois caminhos, uma liberação

O finally é alcançado nos dois caminhos de saída que vêm depois de uma aquisição bem-sucedida.

Diagrama de fluxo mostrando aquisição, pausa no yield, dois caminhos de saída — normal e por exceção — que convergem na liberação do recurso.

Após o yield, a retomada normal e a retomada por exceção passam pela mesma etapa de liberação.

Dica

Limite correto do try

Coloque o try imediatamente depois de a aquisição terminar com sucesso. Assim, o finally protege exatamente o período em que o recurso existe e precisa ser liberado.

Execute um recurso simulado

Teste os dois tipos de saída

Crie um arquivo chamado contexto_finally.py, cole o código completo abaixo e execute python contexto_finally.py. O recurso é apenas um objeto em memória: os eventos permitem observar a ordem das operações.

contexto_finally.py

Um gerenciador baseado em gerador com limpeza garantida após a aquisição.

python
from contextlib import contextmanager


class RecursoSimulado:
    def __init__(self, eventos):
        self.eventos = eventos
        self.aberto = False

    def adquirir(self):
        self.aberto = True
        self.eventos.append("adquirir")

    def liberar(self):
        self.aberto = False
        self.eventos.append("liberar")


@contextmanager
def usar_recurso(eventos):
    recurso = RecursoSimulado(eventos)
    recurso.adquirir()
    try:
        yield recurso
    finally:
        recurso.liberar()


# Saída normal
eventos_normais = []
with usar_recurso(eventos_normais) as recurso:
    assert recurso.aberto is True
    eventos_normais.append("usar")

assert eventos_normais == ["adquirir", "usar", "liberar"]
assert recurso.aberto is False
print("normal:", eventos_normais)

# Saída por exceção
eventos_com_falha = []
try:
    with usar_recurso(eventos_com_falha) as recurso:
        assert recurso.aberto is True
        eventos_com_falha.append("usar")
        raise ValueError("falha do consumidor")
except ValueError as erro:
    eventos_com_falha.append(f"fora: {erro}")

assert eventos_com_falha == [
    "adquirir",
    "usar",
    "liberar",
    "fora: falha do consumidor",
]
assert recurso.aberto is False
print("falha:", eventos_com_falha)

Exemplo

Saída esperada

normal: ['adquirir', 'usar', 'liberar']
falha: ['adquirir', 'usar', 'liberar', 'fora: falha do consumidor']

Na segunda execução, liberar ocorre antes de o except externo observar o ValueError. O gerenciador não captura a exceção: ela atravessa o yield, aciona o finally e continua para fora do with.

Observe o limite da garantia

E se a aquisição falhar?

O finally protege o que vem depois de uma aquisição bem-sucedida. Se recurso.adquirir() lançar uma exceção antes de o fluxo alcançar o try e o yield, o bloco with não chegou a ser aberto e não existe uma saída posterior dele.

Por isso, uma aquisição que pode falhar parcialmente deve cuidar da própria limpeza parcial no ponto em que ela acontece. Não presuma que o finally ao redor do yield resolverá algo que não foi adquirido com sucesso.

Relate a evidência

Após executar o script, descreva as duas sequências de eventos e explique por que elas demonstram, ao mesmo tempo, liberação garantida e propagação da exceção.

Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 8

Observar falhas sem escondê-las

Registre a falha que ocorreu dentro do bloco with sem impedir que ela continue para o código externo.

Capturar não é necessariamente propagar

O efeito de terminar o except

Quando o bloco with falha, a exceção reaparece no yield. Portanto, um except que envolve o yield pode observá-la.

Mas há uma consequência importante: se esse except captura a exceção e o gerador termina normalmente, a exceção é suprimida. O código após o with continua como se a falha tivesse sido tratada.

Para apenas registrar a ocorrência e preservar a falha, use raise sem argumento dentro do except. Ele relança a mesma exceção ativa.

Dois finais possíveis para a exceção

Diagrama comparando uma exceção que chega ao yield e é suprimida quando o except termina, com outra que é registrada e relançada para fora do with; em ambos os casos, a limpeza em finally é executada.

Terminar o except suprime a exceção capturada; raise sem argumento a mantém em propagação.

Registrar, relançar e liberar

Gerenciador que não esconde a falha

O except registra o problema, raise o relança e o finally libera o recurso nos dois caminhos de saída.

python
from contextlib import contextmanager

@contextmanager
def usar_recurso(eventos):
    recurso = {"ativo": True}
    eventos.append("adquirir")

    try:
        yield recurso
    except Exception as erro:
        eventos.append(f"falha: {erro}")
        raise
    finally:
        recurso["ativo"] = False
        eventos.append("liberar")


eventos = []
try:
    with usar_recurso(eventos) as recurso:
        assert recurso["ativo"]
        raise ValueError("dados inválidos")
except ValueError as erro:
    eventos.append(f"externo: {erro}")

print(eventos)
# ['adquirir', 'falha: dados inválidos', 'liberar', 'externo: dados inválidos']

Atenção

`return False` não equivale a `__exit__`

Na implementação com classe, __exit__ retorna um valor booleano para indicar supressão ou propagação. Em uma função decorada com @contextmanager, não use return False esperando esse efeito.

Se o except que recebeu a exceção terminar normalmente — inclusive com return False — a exceção capturada será suprimida. Para propagá-la após registrá-la, a instrução necessária é raise sem argumento.

Exemplo

Versão que suprime sem querer

except Exception as erro:
    eventos.append(f"falha: {erro}")
    # fim do except: a exceção não chega ao código externo

O finally ainda executa a liberação, mas o chamador não poderá capturar a falha original porque ela foi suprimida.

Complete o relançamento

Não esconda a falha

Complete a frase: depois de registrar uma exceção capturada no except, use ____ sem argumento para que ela continue para fora do with.

Passo 6 de 8

Criar um gerenciador para cada uso

Cada chamada a uma função decorada com @contextmanager cria um gerenciador destinado a um único ciclo de entrada e saída.

Uma chamada, um ciclo de vida

Não guarde a instância para reutilizar

A chamada de uma função decorada com @contextmanager cria um gerenciador ligado a um único ciclo: entrar no with, suspender no yield e encerrar na saída. Depois desse ciclo, essa mesma instância não deve entrar em outro with.

Para um novo uso, chame a função decorada novamente. A função é uma fábrica de gerenciadores; o valor que ela retornou não é reutilizável.

Instâncias independentes

Compare uma instância já encerrada com novas instâncias criadas por chamadas separadas.

Diagrama de ciclo de vida: uma chamada cria uma instância que passa por entrar, yield e encerrar; duas outras chamadas criam instâncias independentes para usos distintos.

Cada chamada cria seu próprio percurso até o encerramento.

Dica

Diagnóstico confiável

Se a segunda entrada falhar, identifique primeiro a causa estrutural: a mesma instância já completou seu ciclo. Não baseie a correção no nome exato da exceção ou na mensagem exibida pela sua versão do Python.

Corrigir usos sucessivos

Instância guardada: reutilização inválida

A variável sessao guarda um único gerenciador. O segundo with tenta reiniciar algo que já terminou.

python
from contextlib import contextmanager

@contextmanager
def abrir_sessao():
    print("abrir")
    try:
        yield "conexão"
    finally:
        print("fechar")

sessao = abrir_sessao()

with sessao as conexao:
    print(conexao)

with sessao as conexao:  # uso inválido da mesma instância
    print(conexao)

Chame a função a cada uso

Agora cada with recebe uma instância nova.

python
from contextlib import contextmanager

@contextmanager
def abrir_sessao():
    print("abrir")
    try:
        yield "conexão"
    finally:
        print("fechar")

with abrir_sessao() as conexao:
    print(conexao)

with abrir_sessao() as conexao:
    print(conexao)

O que muda?

Nos dois exemplos, o valor após as continua sendo "conexão", produzido pelo yield. A diferença está no objeto que controla o ciclo: no código correto, cada chamada de abrir_sessao() cria outro gerenciador.

Também vale para aninhamento

Duas entradas aninhadas, duas chamadas

Em contextos aninhados, crie uma instância para cada entrada.

python
from contextlib import contextmanager

@contextmanager
def marcador(nome):
    print(f"entra: {nome}")
    try:
        yield nome
    finally:
        print(f"sai: {nome}")

with marcador("externo") as fora:
    with marcador("interno") as dentro:
        print(f"{fora} -> {dentro}")

Julgue o código

No exemplo aninhado, as duas entradas são válidas porque marcador("externo") e marcador("interno") criam gerenciadores diferentes.

Passo 7 de 8

Escolher entre gerador e classe

Escolha a forma de implementação pela clareza do ciclo de vida e da interface necessária.

O ciclo define a escolha

Prefira o gerador quando o ciclo for linear

Use @contextmanager quando o recurso segue um roteiro local e direto: preparar, entregar um valor ao with e liberar. O código antes do yield prepara; o bloco consumidor usa o valor; finally finaliza.

A escolha não é uma competição por menos linhas. Pergunte: o contexto precisa apenas conduzir esse ciclo ou precisa oferecer uma interface própria?

Dois formatos, duas necessidades

Comparação visual entre um fluxo linear de aquisição, uso e liberação e um objeto persistente com estado e várias operações públicas.

Um ciclo localizado favorece o gerador; estado e operações além da entrada e saída favorecem uma classe.

Quando a classe comunica melhor o contrato

Interface além do escopo

Prefira uma classe quando o gerenciador precisa manter estado persistente ou expor operações públicas que façam parte do uso do recurso. Por exemplo, se o código consumidor precisa consultar métricas, alterar uma configuração ou chamar uma operação específica durante o contexto, uma interface de objeto pode deixar essas responsabilidades explícitas.

Isso não torna a classe automaticamente reutilizável ou reentrante. Essas propriedades dependem do ciclo de vida que a implementação realmente define.

Exemplo

Dois cenários

Ciclo linear — gerador

@contextmanager
def registrar(eventos):
    eventos.append("início")
    try:
        yield eventos
    finally:
        eventos.append("fim")

O valor é entregue e depois liberado; não há outra operação pública necessária.

Interface própria — classe

Um recurso que precisa expor, durante o with, operações como pausar(), retomar() e uma propriedade total_processado tem uma interface maior que o simples ciclo de entrada e saída. Uma classe tende a tornar esse contrato mais claro.

Escolha pelo requisito

Associe o cenário à forma mais clara

Relacione cada requisito à implementação mais adequada.

Toque em um item e depois no par correspondente.

Passo 8 de 8

Aplicação final: um contexto que libera e propaga

Prática final para validar aquisição, liberação e propagação de falhas em um gerenciador baseado em gerador.

Contrato integrado

O que sua solução precisa preservar

Nesta aplicação, o gerenciador deve adquirir um recurso simulado, entregar seu valor após as, registrar uma falha do bloco consumidor, relançá-la e liberar o recurso em qualquer saída. Cada chamada a sessao(...) cria um gerenciador novo para um uso.

Dois caminhos de saída

Diagrama de fluxo mostrando aquisição, yield para o bloco with e dois retornos ao gerador: saída normal e saída por exceção; ambos passam pela liberação, enquanto a exceção continua para fora.

Depois do yield, a retomada pode ser normal ou excepcional. O finally une os dois caminhos na liberação.

Dica

Checklist antes de executar

Confira estes quatro pontos no código: um único yield; except envolvendo o yield; raise sem argumento após registrar a falha; e finally para a liberação.

Execute a validação local

Monte o script

No seu computador, crie um arquivo chamado validar_contexto.py, copie o código completo abaixo e execute python validar_contexto.py. Os asserts verificam o valor de as, a ordem dos eventos, a propagação da exceção e dois usos com novas instâncias.

validar_contexto.py

python
from contextlib import contextmanager


@contextmanager
def sessao(eventos):
    eventos.append("adquirir")
    recurso = {"id": "R1"}

    try:
        yield recurso
    except Exception as erro:
        eventos.append(f"falha:{type(erro).__name__}")
        raise
    finally:
        eventos.append("liberar")


# Caso 1: saída normal e valor entregue por "as".
eventos = []
with sessao(eventos) as recurso:
    assert recurso == {"id": "R1"}
    eventos.append("consumidor:sucesso")

assert eventos == [
    "adquirir",
    "consumidor:sucesso",
    "liberar",
]

# Caso 2: a falha do consumidor é registrada, liberada e continua observável fora.
eventos = []
try:
    with sessao(eventos) as recurso:
        assert recurso["id"] == "R1"
        eventos.append("consumidor:falha")
        raise ValueError("dados inválidos")
except ValueError as erro:
    assert str(erro) == "dados inválidos"
    eventos.append("fora:ValueError")

assert eventos == [
    "adquirir",
    "consumidor:falha",
    "falha:ValueError",
    "liberar",
    "fora:ValueError",
]

# Caso 3: cada chamada cria um novo gerenciador para um novo uso.
eventos = []
with sessao(eventos) as primeiro:
    eventos.append(f"uso:{primeiro['id']}:1")
with sessao(eventos) as segundo:
    eventos.append(f"uso:{segundo['id']}:2")

assert primeiro is not segundo
assert eventos == [
    "adquirir",
    "uso:R1:1",
    "liberar",
    "adquirir",
    "uso:R1:2",
    "liberar",
]

print("Todas as verificações passaram.")

Relate o que observou

Evidência da execução

Após executar o script, relate a sequência observada no caso de falha. Explique por que o ValueError continuou observável fora do with e como os dois usos evitaram reutilizar o mesmo gerenciador.

Escreva pelo menos 120 caracteres (0/120).

Revisão final

Resumo

Decisão e contratos

Use @contextmanager quando preparação, entrega e limpeza formarem um ciclo linear e localizado. Prefira uma classe quando o recurso exigir estado persistente ou operações públicas além da entrada e saída do contexto.

  • Uma entrada bem-sucedida produz exatamente um valor com yield.
  • Coloque a limpeza no finally para executá-la na saída normal e na saída por exceção.
  • Um except em torno do yield que termina normalmente suprime a exceção; use raise para apenas registrá-la e propagá-la.
  • Não entre novamente no mesmo gerenciador retornado: chame a função decorada para cada uso.
  • Escolha gerador ou classe pela clareza do ciclo de vida e da interface necessária.

Tutorial concluído

Parabéns! Você concluiu: Criar gerenciadores de contexto com contextlib

Muito bem! Agora você consegue implementar e validar contextos lineares com @contextmanager sem esconder exceções nem reutilizar uma instância encerrada.

Baixe o Aplicativo agora para ter acesso a + de 5000 cursos gratuitos, exercícios, certificado e muito conteúdo sem pagar nada!

  • Cursos online 100% gratuitos do início ao fim

    Milhares de cursos online em vídeo, ebooks e áudiobooks.

  • Mais de 60 mil exercícios gratuitos

    Para testar seus conhecimentos no decorrer dos cursos online

  • Certificado Digital gratuito válido em todo o Brasil

    Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail

Aplicativo Cursa na tela de ebook, na tela de curso em vídeo e na tela de exercícios do curso, mais o certificado de conclusão de curso