Especialização em Sistemas com Python & POO com Type Hints • Capítulo 05


🗺️ Mapa Conceitual do Tópico

flowchart TD
    subgraph POO_MODEL["Modelo de Objetos do Python (Data Model)"]
        CLASS["Classe como Objeto de Primeira Classe\nInstância da Metaclasse type"]
        LIFECYCLE["Ciclo de Vida do Objeto\n__new__(cls) [Alocação] → __init__(self) [Inicialização]"]
        SLOTS["Otimização de Memória via __slots__\nEliminação de __dict__ e Redução Drástica de RAM"]
    end

    subgraph DISPATCH["Tipologias de Métodos e Despacho"]
        INST["Métodos de Instância (self)\nManipula o estado mutável do Heap"]
        CLASS_M["@classmethod (cls) & typing.Self\nFábricas Polimórficas (Factory Methods)"]
        STATIC_M["@staticmethod\nFunções Utilitárias Puras sem Estado"]
    end

    subgraph ENCAPSULATION["Encapsulamento e Descritores (@property)"]
        PEP8["_protegido (Convenção Pública de Respeito)"]
        MANGLING["__privado (Name Mangling: _Classe__atributo)"]
        PROP["@property [Getter] & @campo.setter [Validação]"]
    end

    POO_MODEL --> DISPATCH
    DISPATCH --> ENCAPSULATION
    ENCAPSULATION --> DOMAIN["Modelagem de Domínio Corporativo com Tipagem Estrita"]

    style POO_MODEL fill:#ede7f6,stroke:#512da8,stroke-width:2px
    style DISPATCH fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style ENCAPSULATION fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

🏛️ 1. O Modelo de Objetos do Python e Ciclo de Vida

No modelo de dados do Python (Python Data Model), tudo é um objeto, inclusive as próprias classes, funções e módulos. Toda classe definida pelo usuário é uma instância direta da metaclasse fundamental type.

O Ciclo de Instanciação: __new__ vs __init__

A instanciação de um objeto ocorre em duas etapas coordenadas:

  1. __new__(cls, *args, **kwargs): O método alocador estático. É invocado primeiro, recebe a classe cls, requisita memória à PVM e retorna a nova instância de PyObject. É o local adequado para implementar o padrão Singleton ou instanciar tipos imutáveis customizados.
  2. __init__(self, *args, **kwargs): O método inicializador. Recebe a instância recém-criada através de self e atribui valores aos atributos no namespace interno.
from typing import Self

class ConexaoPool:
    _instancia: Self | None = None

    def __new__(cls, *args: object, **kwargs: object) -> Self:
        # Padrão Singleton atômico via __new__
        if cls._instancia is None:
            cls._instancia = super().__new__(cls)
        return cls._instancia

⚙️ 2. Tipologias de Métodos: Instância, Classe e Estáticos

O Python organiza o comportamento em três categorias estritas de métodos:

Tipo de Método Decorador Primeiro Argumento Acesso a Estado Caso de Uso Canônico
Instância (Nenhum) self Acessa e muta o estado da instância e da classe. Comportamentos centrais de negócio (ex.: sacar(), transferir()).
Classe @classmethod cls Acessa e muta apenas atributos de classe (não vê self). Métodos de Fábrica (Factory Methods) e desserialização (from_json(), from_dict()).
Estático @staticmethod (Nenhum) Não acessa nem self nem cls. Função pura acoplada ao namespace da classe. Validações algorítmicas, funções de hashing e verificadores de formato.

Métodos de Fábrica com typing.Self (PEP 673)

No Python 3.11+, o tipo especial Self garante que métodos de classe retornem o tipo exato da subclasse em cenários de herança, garantindo checagem estrita no mypy:

from typing import Self
import json

class ConfiguracaoSistema:
    def __init__(self, ambiente: str, debug: bool) -> None:
        self.ambiente: str = ambiente
        self.debug: bool = debug

    @classmethod
    def from_json(cls, payload: str) -> Self:
        dados = json.loads(payload)
        return cls(ambiente=dados["ambiente"], debug=bool(dados["debug"]))

🔒 3. Encapsulamento, Name Mangling e o Descritor @property

Diferente de C++ ou Java, Python não possui as palavras-chave public, protected ou private em nível de linguagem. O encapsulamento repousa sobre convenções semióticas e descritores:

  1. Convenção de Atributo Protegido (_nome): Indica aos desenvolvedores e ferramentas estáticas que o membro é de uso interno da classe ou de suas subclasses.
  2. Desfiguração de Nomes (Name Mangling - __nome): Quando um identificador inicia com dois underscores (sem terminar com dois), o interpretador reescreve seu nome internamente para _NomeDaClasse__nome. O objetivo primário é prevenir colisões de atributos em hierarquias profundas de herança, e não segurança criptográfica.
  3. Descritores de Propriedade (@property): Expõem métodos computados com a sintaxe transparente de acesso a atributos (obj.saldo), permitindo anexar validações defensivas e controle de invariantes através do setter:
from decimal import Decimal

class ContaCorrente:
    def __init__(self, titular: str, limite: Decimal) -> None:
        self._titular: str = titular
        self._limite: Decimal = Decimal("0.00")
        self.limite = limite # Invoca o setter com validação

    @property
    def limite(self) -> Decimal:
        """Getter: Retorna o limite com acesso somente-leitura se omitir o setter."""
        return self._limite

    @limite.setter
    def limite(self, novo_limite: Decimal) -> None:
        """Setter: Assegura invariantes antes da mutação de estado."""
        if novo_limite < Decimal("0.00"):
            raise ValueError(f"Limite não pode ser negativo: {novo_limite}")
        self._limite = novo_limite

⚡ 4. Otimização de Memória com __slots__

Por padrão, toda instância em Python armazena seus atributos em um dicionário dinâmico chamado __dict__. Embora isso permita adicionar atributos em tempo de execução, gera um desperdício substancial de memória Heap (cerca de 150 a 200 bytes por instância).

Ao declarar __slots__ = ("_numero", "_titular", "_saldo"):

  • O interpretador aloca um array estático de ponteiros em C para a instância.
  • O dicionário __dict__ deixa de ser criado.
  • A economia de memória atinge 40% a 65%, ideal para aplicações que manipulam milhões de instâncias simultâneas em memória.