Solucionado (ver solução)
Solucionado
(ver solução)
1
resposta

[Dúvida] Segurança e boas práticas no gerenciamento de conexões em Python

Professor(a), durante meus estudos sobre gerenciamento de recursos em Python com banco de dados, criei a função obter_conexao() para abrir a conexão:

def obter_conexao():
    conectar = psycopg2.connect(
        host="localhost",
        database="Restaurante",
        user="postgres",
        password="Minha Senha"
    )
    return conectar

Com base nisso, testei quatro abordagens para abrir e fechar conexões e cursores:


1️⃣ Manual total (try/finally)

def teste_manual_total():
    conexao = None
    cursor = None
    try:
        conexao = obter_conexao()
        cursor = conexao.cursor()
        cursor.execute("SELECT 1")
        print("Resultado:", cursor.fetchone())
    except Exception as erro:
        print("Erro:", erro)
    finally:
        if cursor:
            cursor.close()
        if conexao:
            conexao.close()

teste_manual_total()

2️⃣ Automático (with) sem try/except)

def teste_automatico_simples():
    with obter_conexao() as conexao:
        with conexao.cursor() as cursor:
            cursor.execute("SELECT 1")
            print("Resultado:", cursor.fetchone())

teste_automatico_simples()

3️⃣ Automático (with) com try/except)

def teste_automatico_with():
    try:
        with obter_conexao() as conexao:
            with conexao.cursor() as cursor:
                cursor.execute("SELECT 1")
                print("Resultado:", cursor.fetchone())
    except Exception as erro:
        print("Erro:", erro)

teste_automatico_with()

4️⃣ Híbrido (finally)

def teste_hibrido():
    conexao = None
    try:
        conexao = obter_conexao()
        print("✅ Conexão criada")

        with conexao.cursor() as cursor:
            cursor.execute("SELECT 1")
            print("Resultado do cursor:", cursor.fetchone())

    except Exception as erro:
        print("❌ Erro ocorrido:", erro)

    finally:
        if conexao:
            conexao.close()
            print("Conexão fechada no finally")

teste_hibrido()

Pergunta final:

Considerando segurança, prevenção de vazamento de recursos e boas práticas de desenvolvimento, qual dessas abordagens (manual, automática ou híbrida) é considerada a mais segura e recomendada, tanto para projetos reais quanto para aprendizado?

Na minha avaliação, embora a escolha dependa do contexto, a abordagem híbrida — que utiliza with para o cursor (fechamento automático) e fechamento manual da conexão no finally — parece ser a mais segura. Por favor, me corrija se eu estiver equivocado sobre essa questão.

Além disso, poderia explicar as vantagens e desvantagens de cada abordagem, especialmente em relação ao uso de with e finally?

1 resposta
solução!

Oii, Samuel. Tudo bem?

Você tocou em um ponto que costuma confundir até desenvolvedores experientes: o comportamento exato dos gerenciadores de contexto (o with) em diferentes bibliotecas de banco de dados.

Você está correto na sua avaliação: para o seu cenário usando psycopg2, a Abordagem 4 (Híbrida) é, de fato, a mais segura entre as opções que você listou para garantir que a conexão seja encerrada.

Vou explicar o detalhe técnico que justifica isso e mostrar uma alternativa padrão do Python para deixar o código mais limpo.

O comportamento do with no psycopg2

A questão central é que o comando with funciona de forma diferente para Conexões e Cursores:

  1. O Cursor (with conexao.cursor()): Quando o bloco with termina, o cursor é fechado automaticamente. Isso libera recursos de memória.
  2. A Conexão (with obter_conexao()): No psycopg2 (e também no sqlite3), o gerenciador de contexto da conexão não fecha a conexão. Ele serve para Controle de Transação:
  • Se o bloco terminar com sucesso: ele faz COMMIT.
  • Se der erro: ele faz ROLLBACK.
  • A conexão permanece aberta após o with.

Por isso, suas abordagens 2 e 3 deixariam conexões abertas no banco de dados até que o script Python fosse finalizado, o que em uma aplicação real causaria o esgotamento de recursos.

Análise das suas abordagens

  • 1 (manual total): Segura, mas trabalhosa e propensa a esquecimento (muito código repetitivo).
  • 2 e 3 (Automático): Perigosas para conexões diretas, pois geram vazamento de conexões (elas não fecham ao final do bloco).
  • 4 (Híbrida): É a mais segura das opções apresentadas. Você usa o with onde ele fecha o recurso (cursor) e usa o finally para garantir o que o with da conexão não faz (fechar a conexão).

A solução Pythonica: contextlib.closing

Para não precisar escrever o bloco try/finally manualmente toda vez (como na abordagem híbrida), o Python possui uma ferramenta na biblioteca padrão chamada contextlib.closing. Ela força o método .close() a ser chamado ao final do bloco with.

Exemplo:

from contextlib import closing

def teste_com_closing():
    try:
        # o 'closing' garante que conexao.close() será chamado ao final
        with closing(obter_conexao()) as conexao:
            
            # o 'with' do cursor já garante o cursor.close()
            with conexao.cursor() as cursor:
                cursor.execute("SELECT 1")
                print("Resultado:", cursor.fetchone())
                
                # Atenção: O 'commit' automático de transação não acontece 
                # usando 'closing'. Se for INSERT/UPDATE, chame:
                # conexao.commit()
                
    except Exception as erro:
        print("Erro:", erro)

Para o momento atual do curso, continue usando a Abordagem 4 (Híbrida) se quiser controle manual e explícito, ou experimente o closing se preferir um código mais limpo.

Em projetos reais de mercado, geralmente utilizamos Connection Pools (como o ThreadedConnectionPool), que mantêm as conexões abertas para serem reutilizadas, evitando o custo de abrir e fechar a todo momento. Mas para aprender a base, sua lógica está perfeita.

Alura Conte com o apoio da comunidade Alura na sua jornada. Abraços e bons estudos!