Contents

Como gerenciar recursos em Python com gerenciadores de contexto

É essencial gerir corretamente os recursos ao criar aplicações para evitar fugas de memória, garantir uma limpeza adequada e manter a estabilidade das suas aplicações. Os gerenciadores de contexto oferecem uma solução refinada para essa situação. Os gerenciadores de contexto simplificam o gerenciamento de recursos automatizando o processo de aquisição e liberação de recursos.

O que são gerenciadores de contexto?

Um gestor de contexto é essencialmente um objeto que delineia estratégias para garantir recursos e libertá-los quando já não são necessários. Estas entidades oferecem a vantagem de simplificar a administração de recursos, apresentando-os num formato coerente, direto e sem ambiguidades. Ao utilizar gestores de contexto, é possível diminuir o código redundante e melhorar a legibilidade da sua base de código.

Uma aplicação de software requer frequentemente o registo de dados num ficheiro. Nos casos em que não é utilizado um gestor de contexto, o utilizador tem de gerir manualmente a abertura e o fecho do ficheiro de registo. No entanto, através da utilização de um gestor de contexto, o processo de criação e desmantelamento de recursos de registo pode ser simplificado, assegurando a execução eficiente da operação de registo e mantendo o tratamento adequado das tarefas associadas.

A instrução with

A utilização da instrução with na linguagem de programação Python permite aos programadores tirar partido das capacidades dos gestores de contexto de forma eficaz. Apesar de quaisquer ocorrências excepcionais durante a execução do bloco de código associado, esta construção garante que todos os recursos adquiridos são devidamente abandonados e cumprem o objetivo pretendido em conformidade.

 with context_manager_expression as resource:
    # Code block that uses the resource
# Resource is automatically released when the block exits

Através do emprego da instrução com , um gestor de contexto recebe autoridade sobre a administração de recursos, libertando o seu foco para ser direcionado para as complexidades da lógica do seu programa.

Usando gerenciadores de contexto embutidos

O Python fornece gerenciadores de contexto embutidos que atendem a situações predominantes, como a manipulação de arquivos com a função open() e a regulação de conexões de rede através do módulo socket .

Manipulação de ficheiros com open()

A função open() serve como um gestor de contexto integrado que facilita a interação com ficheiros na programação Python. Este utilitário é normalmente utilizado para ler ou escrever em ficheiros e produz um objeto de ficheiro após a execução. Uma das suas principais vantagens reside no encerramento automático de ficheiros, o que ajuda a evitar qualquer corrupção involuntária de dados quando se trabalha num bloco de código gerido.

 with open('file.txt', 'r') as file:
    content = file.read()
    # Do something with content
# File is automatically closed after exiting the block

Ligações de rede com socket()

O módulo socket oferece um gestor de contexto para sockets de rede que assegura a configuração e eliminação adequadas das ligações de rede, mitigando assim potenciais riscos de segurança associados a ligações terminadas incorretamente.

 import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect(('localhost', 8080))
    # Send/receive data over the socket
# Socket is automatically closed after exiting the block

Implementando gerenciadores de contexto personalizados

Python permite a criação de gerenciadores de contexto personalizados através de uma abordagem baseada em classe e baseada em função. A escolha entre estas abordagens depende dos requisitos específicos da situação em causa. Nesta secção, iremos examinar como implementar gestores de contexto personalizados utilizando ambos os métodos.

Gerenciadores de contexto usando a abordagem baseada em classe

Na abordagem baseada em classe, define-se uma classe que incorpora o conceito de gerenciamento de recursos por meio da implementação de dois métodos especiais conhecidos como \ \ enter\ \ e \_\ exit\ . Estes métodos de sublinhado mágico ou duplo servem propósitos distintos; o primeiro é responsável por configurar e produzir o recurso desejado, enquanto o segundo assegura que são executados procedimentos de limpeza completos, independentemente de quaisquer potenciais excepções que possam surgir durante a execução.

 class CustomContext:
    def __enter__(self):
        # Acquire the resource
        return resource

    def __exit__(self, exc_type, exc_value, traceback):
        # Release the resource
        pass

Para executar eficazmente vários processos em simultâneo numa determinada tarefa, é necessário utilizar um gestor de contexto que agilize este processo e trate de questões como a atribuição de recursos, a coordenação e a resolução de erros. A solução ideal deve gerar, executar e integrar automaticamente esses processos, garantindo a gestão adequada dos recursos e a sincronização entre eles.

 import multiprocessing
import queue

class ProcessPool:
    def __init__(self, num_processes):
        self.num_processes = num_processes
        self.processes = []

    def __enter__(self):
        self.queue = multiprocessing.Queue()

        for _ in range(self.num_processes):
            process = multiprocessing.Process(target=self._worker)
            self.processes.append(process)
            process.start()

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        for process in self.processes:
            # Sending a sentinel value to signal worker processes to exit
            self.queue.put(None)
        for process in self.processes:
            process.join()

    def _worker(self):
        while True:
            number = self.queue.get()
            if number is None:
                break
            calculate_square(number)

def calculate_square(number):
    result = number * number
    print(f"The square of {number} is {result}")

if __name__ == "__main__":
    numbers = [1, 2, 3, 4, 5]

    # Usage
    with ProcessPool(3) as pool:
        for num in numbers:
            pool.queue.put(num)

    # Processes are automatically started and
     # joined when exiting the 'with' block

O gerenciador de contexto ProcessPool administra efetivamente um grupo de processos de trabalho, alocando cálculos (o cálculo dos quadrados de inteiros) entre eles para processamento simultâneo. Ao aproveitar essa forma de paralelismo, é possível obter uma alocação mais criteriosa das unidades de processamento central disponíveis, bem como acelerar a conclusão de tarefas em comparação com a execução de operações seqüencialmente em um processo solitário.

/pt/images/output-of-class-based-context-manager.jpg

Gerenciadores de contexto usando uma abordagem baseada em função

O módulo contextlib oferece o decorador @contextmanager , que facilita a criação de gerenciadores de contexto por meio de funções geradoras. Os decoradores permitem melhorar o comportamento de uma função, acrescentando novas capacidades sem alterar a sua forma original.

No contexto de uma abordagem baseada em decoradores, a utilização de uma função geradora permite o emprego das instruções yield e finally para demarcar a aquisição e a libertação de recursos, respetivamente.

 from contextlib import contextmanager

@contextmanager
def custom_context():
    # Code to acquire the resource
    resource = ...

    try:
        yield resource # Resource is provided to the with block
    finally:
        # Code to release the resource
        pass

Para criar um gestor de contexto para medir o tempo de execução de um determinado bloco de código, pode utilizar-se uma abordagem orientada para a função. Isto implica a incorporação de um mecanismo de temporização no bloco de código especificado e o subsequente cálculo da duração da sua execução após a conclusão.

 import time
from contextlib import contextmanager

@contextmanager
def timing_context():
    start_time = time.time()

    try:
        yield
    finally:
        end_time = time.time()
        elapsed_time = end_time - start_time
        print(f"Elapsed time: {elapsed_time} seconds")

# Usage
with timing_context():
    # Code block to measure execution time
    time.sleep(2)

The timing\_context

/pt/images/output-of-function-based-context-manager.jpg

Utilizando qualquer um dos métodos, é possível criar gestores de contexto personalizados que englobem complexas complexidades de gestão de recursos e tarefas recorrentes, optimizando assim a organização do código e facilitando os esforços de manutenção.

Gerenciadores de contexto aninhados

Os gerenciadores de contexto aninhados podem ser vantajosos em cenários que exigem o gerenciamento de vários recursos simultaneamente. Ao utilizar contextos aninhados, é possível manter uma sequência de operações ininterrupta e sem falhas, enquanto se adquire e liberta diligentemente cada recurso conforme necessário.

Em certos casos, pode ser necessário que um programa recupere informações de um ficheiro e as armazene numa base de dados. O tratamento destes dois recursos distintos representa um desafio que pode ser resolvido através da utilização de gestores de contexto aninhados uns nos outros.

 import sqlite3

class DatabaseConnection:
    def __enter__(self):
        self.connection = sqlite3.connect('lite.db')
        return self.connection

    def __exit__(self, exc_type, exc_value, traceback):
        self.connection.close()

# Using nested context managers
with DatabaseConnection() as db_conn, open('data.txt', 'r') as file:
    cursor = db_conn.cursor()

    # Create the table if it doesn't exist
    cursor.execute("CREATE TABLE IF NOT EXISTS data_table (data TEXT)")

    # Read data from file and insert into the database
    for line in file:
        data = line.strip()
        cursor.execute("INSERT INTO data_table (data) VALUES (?)", (data,))

    db_conn.commit()

Neste caso, o gestor de contexto DatabaseConnection é responsável pela gestão da ligação à base de dados, enquanto o gestor de contexto inerente open() se encarrega do tratamento do ficheiro.

Para gerir eficazmente o ficheiro e a ligação à base de dados, é importante incluir estes dois contextos numa única instrução. Isto assegura que a libertação adequada de ambos os recursos ocorrerá caso surjam excepções durante o processo de leitura do ficheiro ou de inserção da base de dados.

Personalizando funções com decoradores

A administração eficaz de recursos é um pré-requisito fundamental para o desempenho e a estabilidade ideais em sistemas de software. A ocorrência de fugas de recursos pode levar à acumulação de memória excessiva, resultando numa eficiência reduzida e em potenciais vulnerabilidades. Foi demonstrado que a utilização de gestores de contexto proporciona uma abordagem refinada para enfrentar os desafios associados à gestão de recursos.