Monkey patching é a técnica de modificar código dinamicamente. Você altera ou adiciona métodos a classes ou módulos existentes. Isso acontece em tempo de execução, sem tocar no código fonte original. Primeiro, entenda que é uma ferramenta poderosa. Segundo, use com cautela para evitar efeitos colaterais. Essa técnica é comum em testes automatizados e hotfixes. Além disso, frameworks como gevent e mock a utilizam. Por exemplo, você pode substituir uma função de rede por uma simulada. Assim, testes rodam sem chamar serviços externos. Portanto, monkey patching resolve problemas urgentes e específicos.
Características fundamentais do monkey patching
Uma característica central é a modificação em tempo real. Nenhuma reinicialização do programa é necessária. Outra propriedade importante é o escopo global ou local. Uma vez alterado, o comportamento muda para todo o sistema. Isso pode gerar confusão se outros módulos dependerem do original. Frequentemente, essa técnica é usada para corrigir bugs em bibliotecas. Quando a biblioteca não é sua, monkey patching é uma saída. Porém, patches mal feitos causam erros imprevisíveis. Por essa razão, muitos desenvolvedores recomendam evitar o uso. Alternativas como herança ou injeção de dependência são mais seguras. Em testes, o monkey patching é amplamente aceito. Afinal, você controla completamente o ambiente.
Uma representação conceitual simples é:
Quando utilizar monkey patching (e quando evitar)
Use monkey patching em testes unitários com frequência.
Por exemplo, para simular APIs externas ou bancos de dados.
Ele também ajuda a corrigir bugs urgentes em produção.
Quando uma biblioteca tem um erro crítico, você pode contornar.
Monkey patching é útil para logging temporário e debugging.
Você pode adicionar prints sem alterar o código original.
Além disso, em ambientes de pesquisa, ele acelera experimentos.
Por outro lado, evite monkey patching em código compartilhado.
A equipe ficará confusa com comportamentos inesperados.
Também não use para modificar classes centrais como str ou list.
Isso quebra outros códigos que confiam no comportamento padrão.
Portanto, limite o patch a módulos que você controla.
Primeiro, pergunte-se: existe uma alternativa mais clara? Se a resposta for sim, prefira herança ou composição. Segundo, avalie o impacto em outras partes do sistema. Monkey patching é uma solução elegante apenas em situações específicas. Por exemplo, mock em testes é perfeitamente aceitável. Outro caso é adicionar um método a uma classe de terceiros. Desde que você documente, não há grandes problemas. Então, use com responsabilidade e moderação. Código claro e previsível é sempre melhor.
Exemplo prático: corrigindo uma função problemática
O código abaixo demonstra monkey patching em ação.
Suponha que uma biblioteca externa tenha um método lento.
Nós vamos substituí-lo por uma versão mais rápida.
Primeiro, criamos uma classe Calculadora com erro.
O método dividir não trata divisão por zero.
Em vez de alterar a classe original, aplicamos um patch.
Substituímos o método por uma versão segura em tempo real.
Além disso, mostramos como adicionar um novo método.
Esse novo método não existia na classe original.
Finalmente, restauramos o método original para demonstrar.
Perceba como o comportamento muda dinamicamente.
Isso ilustra o poder e o risco da técnica.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
import sys from datetime import datetime # Classe original de uma biblioteca hipotética class Calculadora: """Calculadora com um método problemático.""" def dividir(self, a, b): """Método original que não trata divisão por zero.""" # Simula um problema: retorna None em vez de erro if b == 0: return None # Comportamento indesejado! return a / b def multiplicar(self, a, b): return a * b # Função de patch (substituto) def dividir_seguro(self, a, b): """Versão melhorada que levanta exceção.""" if b == 0: raise ValueError("Divisão por zero não é permitida") return a / b # Demonstração do monkey patching print("=== Comportamento original ===") calc = Calculadora() print(f"10 / 2 = {calc.dividir(10, 2)}") # 5.0 print(f"5 / 0 = {calc.dividir(5, 0)}") # None (comportamento ruim!) print("\n=== Aplicando monkey patch ===") # Substitui o método problemático Calculadora.dividir = dividir_seguro print("Método 'dividir' foi substituído!") try: resultado = calc.dividir(5, 0) print(f"5 / 0 = {resultado}") # Não chegará aqui except ValueError as e: print(f"Erro capturado: {e}") # Agora o método funciona corretamente print(f"10 / 2 = {calc.dividir(10, 2)}") # 5.0 print("\n=== Adicionando um novo método ===") # Monkey patching também pode adicionar métodos novos def potencia(self, a, b): """Calcula a potência (a elevado a b).""" return a ** b Calculadora.potencia = potencia print(f"2 ** 10 = {calc.potencia(2, 10)}") # 1024 # O método multiplicar permanece inalterado print(f"3 * 7 = {calc.multiplicar(3, 7)}") # 21 print("\n=== Injetando logging em método existente ===") # Exemplo: adicionar logging sem alterar código original original_multiplicar = Calculadora.multiplicar def multiplicar_com_log(self, a, b): print(f"[{datetime.now()}] Multiplicando {a} * {b}") resultado = original_multiplicar(self, a, b) print(f"[{datetime.now()}] Resultado: {resultado}") return resultado # Aplica o patch com logging Calculadora.multiplicar = multiplicar_com_log print("Testando método com logging:") calc.multiplicar(4, 5) print("\n=== Restaurando comportamento original ===") # É possível restaurar se você guardou a referência Calculadora.dividir = Calculadora.__dict__.get('_dividir_original', None) if not hasattr(Calculadora, 'dividir') or Calculadora.dividir is None: # Na prática, guarde a referência antes de patch print("A referência original foi perdida. Isso é um risco!") # Uma forma segura de restaurar (se guardou antes) class CalculadoraOriginal: def dividir(self, a, b): if b == 0: return None return a / b print("\n=== Cuidados importantes ===") print("1. Monkey patching afeta todas as instâncias existentes.") print("2. É difícil rastrear onde o patch foi aplicado.") print("3. Use apenas quando absolutamente necessário.") |
No exemplo, a classe Calculadora original tinha um bug.
Ela retornava None para divisão por zero.
Nós substituímos o método por uma versão segura.
Todas as instâncias existentes foram afetadas imediatamente.
Isso é útil para corrigir problemas em produção.
Porém, um patch mal feito pode piorar a situação.
Além disso, adicionamos um novo método completamente novo.
A biblioteca original nunca teve esse método.
A restauração do método original é possível com referência guardada.
Sem cuidado, você pode perder a funcionalidade original.
Portanto, sempre documente e teste seus patches.
Em testes unitários, use unittest.mock.patch para segurança.
Outro ponto crucial é o escopo do patch.
Ele permanece ativo enquanto o programa rodar.
Isso pode causar comportamentos estranhos em threads diferentes.
Uma boa prática é aplicar patches temporários e restaurá-los.
Ou então, use contextos como mock.patch para isolamento.
Assim, você evita vazamento de patches para outros testes.
Finalmente, lembre-se: código legível é melhor que código esperto.
Monkey patching é uma ferramenta de último caso.
Use com moderação e sempre com documentação clara.
Dessa forma, você mantém a sanidade da equipe.
E também evita bugs noturnos inexplicáveis.
Portanto, prefira soluções convencionais sempre que possível.
unittest.mock oferecem patches temporários seguros.