Python oferece uma rica coleção de ferramentas e métodos especiais para interagir com objetos e classes de forma dinâmica. Esta capacidade, frequentemente referida como introspecção ou reflexão, permite que os programas inspecionem e modifiquem suas próprias estruturas e comportamentos em tempo de execução. Exploraremos algumas dessas funcionalidades, incluindo funções integradas e métodos mágicos, que são essenciais para uma programação orientada a objetos (POO) avançada e flexível.
vars()
A função vars() é uma ferramenta de introspecção que, de certa forma, se assemelha a dir(), mas com uma distinção importante. Ela requer que o objeto fornecido possua um atributo __dict__.
vars()retorna um dicionário contendo os atributos (chaves) e seus respectivos valores armazenados no__dict__do objeto.- Se o objeto fornecido não tiver um atributo
__dict__, uma exceçãoTypeErrorserá levantada. - Quando
vars()é chamada sem argumentos, ela retorna um dicionário com os atributos e valores do namespace local, funcionando de maneira similar alocals().
Veja um exemplo de uso com uma instância de classe:
class Dispositivo:
def __init__(self, tipo_dispositivo):
self.tipo = tipo_dispositivo
meu_dispositivo = Dispositivo("smartphone")
meu_dispositivo.fabricante = "Acme Corp"
meu_dispositivo.modelo = "XYZ-2000"
print(meu_dispositivo.__dict__)
print(vars(meu_dispositivo))
Saída:
{'tipo': 'smartphone', 'fabricante': 'Acme Corp', 'modelo': 'XYZ-2000'}
{'tipo': 'smartphone', 'fabricante': 'Acme Corp', 'modelo': 'XYZ-2000'}
dir()
A função dir() é uma utilidade poderosa para listar os atributos e métodos de um objeto. Sua funcionalidade varia ligeiramente dependendo do tipo de argumetno fornecido:
- Quando aplicada a um módulo,
dir()exibe todos os atributos do módulo. - Em uma instância de classe (seja ela clássica ou moderna),
dir()lista as variáveis da instância, bem como os métodos e atributos de classe definidos na própria classe e em todas as suas classes base. - Se usada em uma classe,
dir()mostra o conteúdo de__dict__da classe e de todas as suas classes base. Contudo, atributos de classe definidos em metaclasses não são exibidos. - Chamada sem argumentos,
dir()retorna uma lista dos nomes no escopo local do chamador.
Para objetos que sobrescrevem os atributos __dict__ ou __class__, dir() usa esses atributos. Para compatibilidade com versões anteriores, se __members__ e __methods__ forem definidos, eles também são considerados.
super()
A função super() é uma ferramenta essencial na programação orientada a objetos em Python, principalmente no contexto de herança. Seu objetivo principal é permitir a chamada de métodos de uma classe ancestral (ou "superclasse") em uma hierarquia de herança.
É importante notar que super() não se refere exclusivamente à classe pai direta, mas sim à próxima classe na Method Resolution Order (MRO), que é a sequência pela qual Python pesquisa métodos em uma hierarquia de herança. Isso é crucial para resolver problemas que surgem com herança múltipla, como a ordem de chamada de métodos e a evitação de chamadas duplicadas (o "problema do diamante").
Sintaxe básica de super():
super(tipo[, objeto_ou_tipo])
Uma diferença notável entre Python 3.x e Python 2.x é a simplificação da sintaxe. Em Python 3, é possível usar super().metodo() em vez de super(ClasseAtual, self).metodo():
Exemplo em Python 3.x:
class Entidade:
def exibir_id(self, id_val):
print(f"ID da Entidade: {id_val}")
class Usuario(Entidade):
def exibir_id(self, id_val):
print("Início da exibição do usuário.")
super().exibir_id(id_val) # Chama o método exibir_id da próxima classe na MRO
print("Fim da exibição do usuário.")
user = Usuario()
user.exibir_id(123)
Exemplo em Python 2.x (lembre-se de herdar de object para classes de "novo estilo"):
class Entidade(object):
def exibir_id(self, id_val):
print "ID da Entidade: %s" % id_val
class Usuario(Entidade):
def exibir_id(self, id_val):
print "Inicio da exibicao do usuario."
super(Usuario, self).exibir_id(id_val) # Chama o método exibir_id da Entidade
print "Fim da exibicao do usuario."
user = Usuario()
user.exibir_id(123)
Alguns pontos importantes sobre super():
-
Em herança simples,
super()e chamadas diretas ao construtor pai são similares.Ao lidar com uma única hierarquia de herança, o efeito de
super().__init__()é similar ao deNomeDaClassePai.__init__(self).class ObjetoBase: def __init__(self): print("ObjetoBase inicializado.") class SubClasseA(ObjetoBase): def __init__(self): print("Inicializando SubClasseA.") ObjetoBase.__init__(self) # Chamada explícita class SubClasseB(ObjetoBase): def __init__(self): print("Inicializando SubClasseB.") super().__init__() # Usando super() obj_base = ObjetoBase() obj_a = SubClasseA() obj_b = SubClasseB()Saída:
ObjetoBase inicializado. Inicializando SubClasseA. ObjetoBase inicializado. Inicializando SubClasseB. ObjetoBase inicializado.Note que
super()elimina a necessidade de referenciar explicitamente a classe base pelo nome. -
super()funciona apenas com classes de "novo estilo".Se uma classe base não herdar de
object(o que a tornaria uma classe de "estilo antigo" em Python 2.x),super()falhará com umTypeError. Em Python 3.x, todas as classes são de "novo estilo" por padrão. -
super()não representa a classe pai, mas sim a próxima classe na MRO.Este é um conceito fundamental. Em herança múltipla,
super()segue a Method Resolution Order (MRO) para encontrar o próximo método a ser chamado. A MRO é uma lista que define a ordem de pesquisa de métodos. Você pode inspecioná-la usandoSuaClasse.mro()ouSuaClasse.__mro__.Considere o seguinte exemplo de herança múltipla:
class BaseComponent: def __init__(self): print("Inicializando BaseComponent.") class ComponenteA(BaseComponent): def __init__(self): print("Entrando em ComponenteA.") super().__init__() print("Saindo de ComponenteA.") class ComponenteB(BaseComponent): def __init__(self): print("Entrando em ComponenteB.") super().__init__() print("Saindo de ComponenteB.") class SistemaComplexo(ComponenteA, ComponenteB): pass sistema = SistemaComplexo() print(SistemaComplexo.__mro__)Saída:
Entrando em ComponenteA. Entrando em ComponenteB. Inicializando BaseComponent. Saindo de ComponenteB. Saindo de ComponenteA. (<class>, <class>, <class>, <class>, <class>) </class></class></class></class></class>Observe que a ordem de execução do construtor segue a MRO:
SistemaComplexo->ComponenteA->ComponenteB->BaseComponent. Se tivéssemos chamadoBaseComponent.__init__(self)diretamente emComponenteA,ComponenteBteria sido ignorado. -
super()ajuda a evitar chamadas duplicadas em herança múltipla (problema do diamante).No cenário de herança em diamante, onde uma classe herda de duas classes que, por sua vez, herdam da mesma classe base, o uso de chamadas diretas ao construtor pode levar à execução duplicada do construtor da classe mais base.
super()resolve isso garantindo que cada método seja chamado apenas uma vez na ordem correta da MRO.Exemplo com chamadas diretas (problema):
class ServicoBase: def __init__(self): print("ServicoBase inicializado.") class ServicoLog(ServicoBase): def __init__(self): print("ServicoLog inicializado.") ServicoBase.__init__(self) # Chamada direta class ServicoAutenticacao(ServicoBase): def __init__(self): print("ServicoAutenticacao inicializado.") ServicoBase.__init__(self) # Chamada direta class SistemaGerenciador(ServicoLog, ServicoAutenticacao): def __init__(self): print("SistemaGerenciador inicializado.") ServicoLog.__init__(self) ServicoAutenticacao.__init__(self) print("--- Com chamadas diretas ---") gerenciador_direto = SistemaGerenciador()Saída (ServicoBase inicializado duas vezes):
--- Com chamadas diretas --- SistemaGerenciador inicializado. ServicoLog inicializado. ServicoBase inicializado. ServicoAutenticacao inicializado. ServicoBase inicializado.Exemplo usando
super()(solução):class ServicoBase: def __init__(self): print("ServicoBase inicializado.") class ServicoLog(ServicoBase): def __init__(self): print("ServicoLog inicializado.") super().__init__() class ServicoAutenticacao(ServicoBase): def __init__(self): print("ServicoAutenticacao inicializado.") super().__init__() class SistemaGerenciador(ServicoLog, ServicoAutenticacao): def __init__(self): print("SistemaGerenciador inicializado.") super().__init__() print("\n--- Com super() ---") gerenciador_super = SistemaGerenciador() print(SistemaGerenciador.__mro__)Saída (ServicoBase inicializado uma vez):
--- Com super() --- SistemaGerenciador inicializado. ServicoLog inicializado. ServicoAutenticacao inicializado. ServicoBase inicializado. (<class>, <class>, <class>, <class>, <class>) </class></class></class></class></class>
isinstance() e issubclass()
isinstance(objeto, classe_ou_tupla)
Verifica se um objeto é uma instância de uma classe fornecida ou de qaulquer classe em uma tupla de classes. Retorna True ou False.
class Forma:
pass
class Circulo(Forma):
pass
minha_forma = Forma()
meu_circulo = Circulo()
print(isinstance(minha_forma, Forma)) # True
print(isinstance(meu_circulo, Circulo)) # True
print(isinstance(meu_circulo, Forma)) # True (Circulo é subclasse de Forma)
print(isinstance(minha_forma, Circulo)) # False
issubclass(subclasse, superclasse_ou_tupla)
Verifica se uma subclasse é de fato uma subclasse de uma superclasse fornecida ou de qualquer classe em uma tupla de superclasses. Retorna True ou False.
class Forma:
pass
class Circulo(Forma):
pass
class Retangulo(Forma):
pass
class Quadrado(Retangulo):
pass
print(issubclass(Circulo, Forma)) # True
print(issubclass(Quadrado, Retangulo)) # True
print(issubclass(Quadrado, Forma)) # True (Quadrado é subclasse de Retangulo, que é de Forma)
print(issubclass(Forma, Circulo)) # False
Reflexão (Introspecção)
O que é Reflexão?
Reflexão, ou introspecção, refere-se à capacidade de um programa de examinar, modificar e interagir com sua própria estrutura, tipos e comportamento em tempo de execução. Em Python, onde "tudo é um objeto", essa capacidade é inerente e pode ser aplicada a classes, objetos, funções e módulos.
Funções para Introspecção em Python
Python oferece quatro funções chave para trabalhar com reflexão, aplicáveis tanto a classes quanto a objetos:
hasattr(objeto, nome_atributo)
Verifica se um objeto possui um atributo (variável ou método) com o nome_atributo especificado como uma string. Retorna True se o atributo existir, False caso contrário.
class Funcionario:
def __init__(self, nome, cargo):
self.nome = nome
self.cargo = cargo
def apresentar(self):
print(f"{self.nome} trabalha como {self.cargo}.")
colaborador = Funcionario("Ana", "Desenvolvedora")
print(hasattr(colaborador, 'nome'))
print(hasattr(colaborador, 'apresentar'))
print(hasattr(colaborador, 'salario')) # Atributo não existe
Saída:
True
True
False
getattr(objeto, nome_atributo, valor_padrao=None)
Retorna o valor de um atributo nomeado de um objeto. O nome_atributo deve ser uma string. Se o atributo não existir e um valor_padrao for fornecido, este será retornado. Caso contrário, se o atributo não existir e nenhum valor_padrao for fornecido, uma exceção AttributeError é levantada.
class Produto:
def __init__(self, sku, nome, preco):
self.sku = sku
self.nome = nome
self.preco = preco
def calcular_desconto(self, percentual):
return self.preco * (1 - percentual / 100)
item = Produto("P001", "Smartphone X", 1200)
print(getattr(item, 'nome'))
print(getattr(item, 'calcular_desconto')) # Retorna o objeto método
print(getattr(item, 'calcular_desconto')(10)) # Chama o método
print(getattr(item, 'descricao', 'Nenhuma descrição disponível.')) # Atributo não existe, retorna padrão
Saída:
Smartphone X
<bound method Produto.calcular_desconto of <__main__.Produto object at 0x...>>
1080.0
Nenhuma descrição disponível.
setattr(objeto, nome_atributo, valor)
Define o valor de um atributo de um objeto. Se o atributo já existir, seu valor é alterado. Se não existir, um novo atributo é criado no objeto.
class Configuracao:
def __init__(self, tema):
self.tema = tema
cfg = Configuracao("escuro")
print(cfg.__dict__)
setattr(cfg, 'fonte', 'Roboto') # Adiciona um novo atributo
print(cfg.__dict__)
setattr(cfg, 'tema', 'claro') # Modifica um atributo existente
print(cfg.__dict__)
Saída:
{'tema': 'escuro'}
{'tema': 'escuro', 'fonte': 'Roboto'}
{'tema': 'claro', 'fonte': 'Roboto'}
delattr(objeto, nome_atributo)
Deleta um atributo nomeado de um objeto. Se o atributo não existir, uma exceção AttributeError é levantada.
class DadosUsuario:
def __init__(self, id_usuario, email):
self.id_usuario = id_usuario
self.email = email
usuario = DadosUsuario(101, 'user@example.com')
print(usuario.__dict__)
delattr(usuario, 'email')
print(usuario.__dict__)
# delattr(usuario, 'telefone') # Isso levantaria um AttributeError
Saída:
{'id_usuario': 101, 'email': 'user@example.com'}
{'id_usuario': 101}
Reflexão com Classes (Classes são Objetos)
As funções de reflexão também podem ser usadas diretamente com classes, pois em Python, classes são objetos de primeira classe.
class ConfiguracaoSistema:
VERSAO = "1.0.0" # Atributo de classe
def __init__(self, ambiente):
self.ambiente = ambiente
def obter_status(self):
return f"Sistema rodando em {self.ambiente} (v{self.VERSAO})"
@staticmethod
def log_evento(mensagem):
print(f"LOG: {mensagem}")
print(getattr(ConfiguracaoSistema, 'VERSAO'))
print(getattr(ConfiguracaoSistema, 'obter_status'))
print(getattr(ConfiguracaoSistema, 'log_evento'))
print(getattr(ConfiguracaoSistema, 'log_evento')("Início da aplicação"))
Saída:
1.0.0
<function ConfiguracaoSistema.obter_status at 0x...>
<function ConfiguracaoSistema.log_evento at 0x...>
LOG: Início da aplicação
Reflexão de Membros do Módulo Atual
É possível usar reflexão para inspecionar funções e variáveis definidas no módulo atual, acessando-o através de sys.modules[__name__] ou sys.modules['__main__'].
import sys
def processar_dados():
print("Processando dados...")
def gerar_relatorio():
print("Gerando relatório...")
modulo_atual = sys.modules[__name__] # ou sys.modules['__main__'] se for o script principal
print(hasattr(modulo_atual, 'processar_dados'))
metodo_relatorio = getattr(modulo_atual, 'gerar_relatorio')
metodo_relatorio()
Saída:
True
Gerando relatório...
Importação Dinâmica de Módulos com __import__
A função __import__ permite importar módulos dinamicamente usando seu nome em string, e então utilizar reflexão para acessar seus membros.
Suponha um arquivo operacoes_uteis.py:
# operacoes_uteis.py
def somar(a, b):
return a + b
def subtrair(a, b):
return a - b
No script principle:
# script_principal.py
nome_modulo = 'operacoes_uteis'
modulo_util = __import__(nome_modulo)
print(hasattr(modulo_util, 'somar'))
func_somar = getattr(modulo_util, 'somar')
print(func_somar(5, 3))
Saída:
True
8
Benefícios da Reflexão
**1. Mecanismos "Plug-and-Play":**A reflexão permite a construção de sistemas onde componentes ou funcionalidades podem ser "conectados" ou "desconectados" dinamicamente. Por exemplo, uma aplicação pode definir interfaces (métodos esperados) e esperar que outros módulos forneçam implementações. Mesmo que uma implementação ainda não esteja disponível, a lógica principal pode ser escrita, utilizando reflexão para chamar as funcionalidades esperadas quando elas finalmente forem fornecidas. Isso é um tipo de "ligação tardia", onde a decisão sobre qual código executar é adiada para o tempo de execução.
# service_manager.py
class ServicoIndisponivel(Exception):
pass
class GerenciadorDeServicos:
def __init__(self):
self._servicos = {}
def registrar_servico(self, nome, obj_servico):
self._servicos[nome] = obj_servico
def executar_operacao(self, nome_servico, nome_operacao, *args, **kwargs):
servico = self._servicos.get(nome_servico)
if not servico:
raise ServicoIndisponivel(f"Serviço '{nome_servico}' não registrado.")
if hasattr(servico, nome_operacao):
metodo = getattr(servico, nome_operacao)
return metodo(*args, **kwargs)
else:
raise AttributeError(f"Serviço '{nome_servico}' não possui a operação '{nome_operacao}'.")
# logger_service.py
class LoggerService:
def log_message(self, message):
print(f"[LOG] {message}")
def error_message(self, message):
print(f"[ERROR] {message}")
# main_app.py
from service_manager import GerenciadorDeServicos
from logger_service import LoggerService
app_manager = GerenciadorDeServicos()
app_manager.registrar_servico("logger", LoggerService())
try:
app_manager.executar_operacao("logger", "log_message", "Aplicação iniciada com sucesso.")
app_manager.executar_operacao("logger", "error_message", "Falha na conexão com o banco de dados.")
app_manager.executar_operacao("database", "connect") # Serviço não registrado
except (ServicoIndisponivel, AttributeError) as e:
print(f"Erro: {e}")
**2. Importação Dinâmica de Módulos:**A reflexão, em conjunto com __import__ ou a função importlib.import_module (mais recomendada para Python 3), permite que uma aplicação decida quais módulos carregar em tempo de execução, com base em configurações, entradas do usuário ou outras condições. Isso é útil para arquiteturas de plugins, carregamento de drivers específicos ou configurações de ambiente.
__getattr__, __getattribute__, __setattr__, __delattr__
Estes são métodos especiais (também conhecidos como "métodos mágicos" ou "dunder methods") que interceptam o acesso a atributos de instâncias de classe. Eles oferecem um controle granular sobre como os atributos são criados, lidos, modificados e excluídos.
__setattr__(self, nome, valor): Chamado sempre que uma instância tem um atributo definido ou modificado.__delattr__(self, nome): Invocado quando uma instância tem um atributo deletado.__getattr__(self, nome): Chamado somente quando um atributo é acessado e NÃO é encontrado pelos meios normais (ou seja, não está em__dict__da instância ou da classe, nem nas classes base).__getattribute__(self, nome): Invocado para TODOS os acessos a atributos. É o primeiro método a ser chamado.
A ordem de chamada é crucial: __getattribute__ é sempre chamado primeiro. Se ele não encontrar o atributo ou levantar um AttributeError (especificamente), então __getattr__ será chamado (se existir).
Interação entre __getattribute__ e __getattr__
Considere uma classe de "novo estilo" (que herda de object):
class ObservadorAtributo(object):
def __init__(self):
print("ObservadorAtributo inicializado.")
def __getattr__(self, nome):
print(f"__getattr__ chamado para: {nome} (atributo não encontrado).")
# Devemos levantar um AttributeError aqui se não pudermos fornecer o atributo
raise AttributeError(f"Atributo '{nome}' não existe.")
def __getattribute__(self, nome):
print(f"__getattribute__ chamado para: {nome}.")
# Para continuar a cadeia de pesquisa de atributos normal,
# é NECESSÁRIO chamar o método da superclasse.
# Caso contrário, o atributo nunca será encontrado e só veremos esta mensagem.
return super().__getattribute__(nome)
def __setattr__(self, nome, valor):
print(f"__setattr__ chamado para: {nome} = {valor}.")
super().__setattr__(nome, valor) # Importante para evitar recursão infinita
def __delattr__(self, nome):
print(f"__delattr__ chamado para: {nome}.")
super().__delattr__(nome)
print("--- Criando instância ---")
monitor = ObservadorAtributo()
print("\n--- Acessando atributo existente ---")
monitor.novo_atributo = 42 # Isso chama __setattr__
print(monitor.novo_atributo) # Isso chama __getattribute__ e encontra o atributo
print("\n--- Acessando atributo inexistente ---")
try:
print(monitor.atributo_fantasma) # Isso chama __getattribute__ e então __getattr__
except AttributeError as e:
print(f"Erro capturado: {e}")
Saída:
--- Criando instância ---
ObservadorAtributo inicializado.
--- Acessando atributo existente ---
__setattr__ chamado para: novo_atributo = 42.
__getattribute__ chamado para: novo_atributo.
42
--- Acessando atributo inexistente ---
__getattribute__ chamado para: atributo_fantasma.
__getattr__ chamado para: atributo_fantasma (atributo não encontrado).
Erro capturado: Atributo 'atributo_fantasma' não existe.
Como visto no exemplo, __getattribute__ é chamado para todos os acessos. Se ele próprio não chamar super().__getattribute__(nome), o atributo não será encontrado, e __getattr__ (se presente) será chamado apenas se __getattribute__ levantar um AttributeError explicitamente (o que super().__getattribute__(nome) faz por padrão se o atributo não for encontrado).
Cuidado com a recursão em __setattr__
Ao implementar __setattr__, é crucial evitar chamadas recursivas infinitas. Se você atribuir um valor a self.algum_atributo dentro de __setattr__, isso chamará __setattr__ novamente, criando um loop infinito.
class ConfigManager(object):
def __init__(self):
# Correto: Usa super().__setattr__ para inicializar atributos
# Caso contrário, self.log_level = 'INFO' chamaria __setattr__ recursivamente
super().__setattr__('log_level', 'INFO')
def __setattr__(self, nome, valor):
print(f"Interceptando configuração: {nome} = {valor}")
# Errado: Isso causaria recursão infinita!
# self.nome = valor
# Correto: Chama o setter da classe pai para definir o atributo
super().__setattr__(nome, valor)
config = ConfigManager()
config.log_level = 'DEBUG' # Chama __setattr__
print(config.log_level) # Chama __getattribute__ (não implementado, usa padrão)
Saída:
Interceptando configuração: log_level = DEBUG
DEBUG
Customização de Tipos Padrão (Empacotamento/Delegação)
Python oferece tipos de dados padrão e métodos embutidos. No entanto, muitas vezes é necessário estender ou modificar esses tipos para adicionar funcionalidade específica, validação ou controle de acesso. Isso pode ser feito por herança ou por um padrão conhecido como "empacotamento" ou "delegação".
Customização via Herança
Estender um tipo padrão via herança é direto. A nova classe herda todos os comportamentos do tipo base e pode sobrescrever ou adicionar novos métodos e atributos.
class ListaValidada(list):
def adicionar(self, elemento):
"""Adiciona um elemento com verificação de tipo."""
if not isinstance(elemento, int):
raise TypeError("A ListaValidada aceita apenas inteiros.")
super().append(elemento) # Chama o método append da classe 'list'
@property
def elemento_central(self):
"""Retorna o elemento do meio da lista."""
if not self: # Lista vazia
return None
indice = len(self) // 2
return self[indice]
minha_lista = ListaValidada([1, 2, 3, 4])
print(minha_lista)
minha_lista.adicionar(5)
print(minha_lista)
# minha_lista.adicionar("string") # Isso levantaria um TypeError
print(f"Elemento central: {minha_lista.elemento_central}")
# Outros métodos de lista ainda funcionam normalmente
minha_lista.insert(0, -10)
print(minha_lista)
minha_lista.clear() # Python 3.x tem clear()
print(minha_lista)
Saída:
[1, 2, 3, 4]
[1, 2, 3, 4, 5]
Elemento central: 3
[-10, 1, 2, 3, 4, 5]
[]
Customização via Delegação (Empacotamento com __getattr__)
A delegação é uma alternativa à herança. Em vez de herdar de um tipo padrão, a nova classe contém uma instância do tipo padrão e "delegação" chamadas de método ou acesso a atributos para essa instância interna. Isso oferece mais controle sobre quais métodos são expostos e como eles funcionam. O método mágico __getattr__ é a chave para implementar a delegação.
import time
class ArquivoComLog:
def __init__(self, caminho_arquivo, modo='r', codificacao='utf-8'):
self._arquivo = open(caminho_arquivo, modo, encoding=codificacao)
self._caminho = caminho_arquivo
self._modo = modo
def escrever_com_timestamp(self, linha):
"""Sobrescreve o método write para adicionar um timestamp."""
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
self._arquivo.write(f"{timestamp} {linha}\n")
def __getattr__(self, nome_atributo):
"""Delega o acesso a outros atributos e métodos para o objeto de arquivo interno."""
# Se o atributo não for encontrado na classe ArquivoComLog,
# tenta encontrá-lo no objeto self._arquivo.
return getattr(self._arquivo, nome_atributo)
def __str__(self):
return f"<ArquivoComLog para '{self._caminho}' em modo '{self._modo}'>"
# Exemplo de uso:
with open('log_eventos.txt', 'w', encoding='utf-8') as f:
f.write("") # Garante que o arquivo esteja vazio para o teste
log_file = ArquivoComLog('log_eventos.txt', 'w+')
log_file.escrever_com_timestamp("Início do registro de eventos.")
log_file.escrever_com_timestamp("Um evento importante ocorreu.")
# Métodos como seek, read, close são delegados automaticamente
log_file.seek(0)
conteudo = log_file.read()
print(conteudo)
log_file.close()
# Exemplo de delegação para uma lista com controle de permissão
class ListaControlada:
def __init__(self, itens_iniciais=None, permitir_limpeza=False):
self._lista = list(itens_iniciais) if itens_iniciais else []
self.permitir_limpeza = permitir_limpeza
def limpar(self):
"""Método personalizado com controle de permissão."""
if not self.permitir_limpeza:
raise PermissionError("Operação de limpeza não permitida.")
self._lista.clear()
def __getattr__(self, nome_atributo):
"""Delega outros métodos de lista para a lista interna."""
return getattr(self._lista, nome_atributo)
def __str__(self):
return str(self._lista)
minha_lista_controlada = ListaControlada([10, 20, 30], permitir_limpeza=False)
print(minha_lista_controlada)
minha_lista_controlada.append(40) # Delegado
print(minha_lista_controlada)
# Tenta limpar sem permissão
try:
minha_lista_controlada.limpar()
except PermissionError as e:
print(f"Erro: {e}")
minha_lista_controlada.permitir_limpeza = True
print("Permissão de limpeza concedida.")
minha_lista_controlada.limpar()
print(minha_lista_controlada)
Saída:
2023-10-27 10:00:00 Início do registro de eventos.
2023-10-27 10:00:01 Um evento importante ocorreu.
[10, 20, 30]
[10, 20, 30, 40]
Erro: Operação de limpeza não permitida.
Permissão de limpeza concedida.
[]