O que é o patch de macacos?

548

Estou tentando entender, o que é um patch de macaco ou um patch de macaco?

Isso é algo como métodos / operadores sobrecarregando ou delegando?

Tem algo em comum com essas coisas?

Sergei Basharov
fonte
Eu acho que a definição do google é útil e mais geral:Monkey patching is a technique to add, modify, or suppress the default behavior of a piece of code at runtime without changing its original source code.
Charlie Parker

Respostas:

522

Não, não é como nenhuma dessas coisas. É simplesmente a substituição dinâmica de atributos em tempo de execução.

Por exemplo, considere uma classe que possui um método get_data. Esse método faz uma pesquisa externa (em um banco de dados ou API da web, por exemplo) e vários outros métodos da classe o chamam. No entanto, em um teste de unidade, você não deseja depender da fonte de dados externa - portanto, substitui dinamicamente o get_datamétodo por um stub que retorna alguns dados fixos.

Como as classes Python são mutáveis ​​e os métodos são apenas atributos da classe, você pode fazer isso o quanto quiser - e, de fato, pode até substituir classes e funções em um módulo exatamente da mesma maneira.

Mas, como comentou um comentarista , tenha cuidado ao monkeypatching:

  1. Se algo além de sua lógica de teste exigir get_data, também chamará sua substituição corrigida pelo macaco em vez da original - o que pode ser bom ou ruim. Apenas tenha cuidado.

  2. Se existir alguma variável ou atributo que também aponte para a get_datafunção no momento da substituição, esse alias não alterará seu significado e continuará apontando para o original get_data. (Por quê? O Python apenas religa o nome get_datada sua classe a algum outro objeto de função; outras associações de nome não são afetadas.)

Daniel Roseman
fonte
1
@LutzPrechelt só para deixar claro para mim, o que você quer dizer com isso pointing to the original get_data function? Você quer dizer quando armazena uma função dentro de uma variável se alguém alterar essa função, a variável continuará apontando para a antiga?
Fabriciorissetto
3
@ fabriciorissetto: Você normalmente não altera objetos de função no Python. Ao fazer o patch do macaco get_data, você renova o nome get_datapara uma função simulada. Se algum outro nome em algum outro lugar do programa estiver vinculado à função anteriormente conhecida como- get_data, nada será alterado para esse outro nome.
Lutz Prechelt
1
@LutzPrechelt Você poderia explicar um pouco mais sobre isso?
Calvin Ku
Eu acho que o patch do macaco pode ser útil especialmente para depuração e nas funções de decoradores ou fábricas de objetos. No entanto, lembre-se explícita é melhor do que implícito para se certificar que seu código é contexto-insensitive, leia "Goto considerado nocivo", etc ...
aoeu256
Portanto, é algo parecido com o uso da função 'eval', onde você pode inserir um novo código em tempo de execução?
wintermute 23/02
363

Um MonkeyPatch é um pedaço de código Python que estende ou modifica outro código em tempo de execução (normalmente na inicialização).

Um exemplo simples é assim:

from SomeOtherProduct.SomeModule import SomeClass

def speak(self):
    return "ook ook eee eee eee!"

SomeClass.speak = speak

Fonte: Página MonkeyPatch no wiki do Zope.

Paolo
fonte
126

O que é um patch de macaco?

Simplificando, o patch do macaco está fazendo alterações em um módulo ou classe enquanto o programa está sendo executado.

Exemplo em uso

Há um exemplo de patch de macaco na documentação do Pandas:

import pandas as pd
def just_foo_cols(self):
    """Get a list of column names containing the string 'foo'

    """
    return [x for x in self.columns if 'foo' in x]

pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method

Para dividir isso, primeiro importamos nosso módulo:

import pandas as pd

Em seguida, criamos uma definição de método, que existe ilimitada e livre fora do escopo de qualquer definição de classe (uma vez que a distinção é bastante sem sentido entre uma função e um método não vinculado, o Python 3 elimina o método não vinculado):

def just_foo_cols(self):
    """Get a list of column names containing the string 'foo'

    """
    return [x for x in self.columns if 'foo' in x]

Em seguida, simplesmente anexamos esse método à classe em que queremos usá-lo:

pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class

E então podemos usar o método em uma instância da classe e excluir o método quando terminarmos:

df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method

Advertência para desconfiar de nomes

Se você estiver usando nomes diferentes (prefixar atributos com um sublinhado duplo, que altera o nome e que eu não recomendo), será necessário alterar o nome manualmente, se você fizer isso. Como eu não recomendo o nome errado, não o demonstrarei aqui.

Exemplo de teste

Como podemos usar esse conhecimento, por exemplo, em testes?

Digamos que precisamos simular uma chamada de recuperação de dados para uma fonte de dados externa que resulte em um erro, porque queremos garantir o comportamento correto nesse caso. Podemos aplicar um patch na estrutura de dados para garantir esse comportamento. (Então, usando um nome de método semelhante ao sugerido por Daniel Roseman :)

import datasource

def get_data(self):
    '''monkey patch datasource.Structure with this to simulate error'''
    raise datasource.DataRetrievalError

datasource.Structure.get_data = get_data

E quando testamos o comportamento que depende desse método que gera um erro, se implementado corretamente, obteremos esse comportamento nos resultados do teste.

Ao fazer o procedimento acima, o Structureobjeto será alterado durante toda a vida útil do processo, portanto, convém usar configurações e desmontagens em seus unittests para evitar isso, por exemplo:

def setUp(self):
    # retain a pointer to the actual real method:
    self.real_get_data = datasource.Structure.get_data
    # monkey patch it:
    datasource.Structure.get_data = get_data

def tearDown(self):
    # give the real method back to the Structure object:
    datasource.Structure.get_data = self.real_get_data

(Embora o que foi dito acima seja bom, provavelmente seria uma idéia melhor usar a mockbiblioteca para corrigir o código. mockO patchdecorador do código seria menos propenso a erros do que o descrito acima, o que exigiria mais linhas de código e, portanto, mais oportunidades para introduzir erros. Ainda tenho que revisar o código, mockmas imagino que ele use o patch para macacos de maneira semelhante.)

Aaron Hall
fonte
então, o ônus do monkeypatcher é armazenar uma referência ao método real? por exemplo, o que acontece se alguém esquecer a etapa "reter um ponteiro", ela será perdida?
Tommy
3
@ Tommy Se refcounts para um método "sobrescrito" ir para zero - ele é coletado como lixo e, portanto, "perdido" durante a vida do processo (ou a menos que o módulo em que ele se originou seja recarregado, mas isso geralmente não seja recomendado).
Aaron Hall
33

De acordo com a Wikipedia :

No Python, o termo patch de macaco refere-se apenas a modificações dinâmicas de uma classe ou módulo em tempo de execução, motivadas pela intenção de corrigir o código de terceiros existente como uma solução alternativa para um bug ou recurso que não age como você deseja.

David Heffernan
fonte
16

Primeiro: patch de macacos é um truque do mal (na minha opinião).

É frequentemente usado para substituir um método no nível do módulo ou da classe por uma implementação personalizada.

O caso de usuário mais comum é adicionar uma solução alternativa para um bug em um módulo ou classe quando você não pode substituir o código original. Nesse caso, você substitui o código "errado" através do patch do macaco por uma implementação dentro do seu próprio módulo / pacote.

Andreas Jung
fonte
8
No caso de alguns módulos fazerem o patch da mesma coisa: você está condenado.
Andreas Jung
49
Enquanto o seu poder poderia realmente ser perigoso em geral, é ótimo para testes
dkrikun
1
O caso de uso mais comum é realmente para testes, especialmente testes de unidade. Você deseja testar apenas seu código e corrigir qualquer chamada externa para retornar um resultado esperado.
Brocoli
1
não é mau, eu o uso para corrigir erros no software de outras pessoas até que um novo lançamento seja lançado, em vez de criar e criar uma nova dependência.
Nurettin
1
O patch para macacos pode ser feito de uma maneira "pura funcional", não da maneira mutável, "sensível ao contexto" e do tipo "goto", apenas corrigindo os decoradores que retornam uma nova versão corrigida da sua classe / método (em vez de modificá-la). Muitos programadores de C # / Java não conhecem o desenvolvimento orientado a REPL, portanto, codificam em seus IDEs que exigem que tudo seja definido estaticamente. Como o C # / Java não possui patch de macaco, eles assumem seu mal quando o veem em JavaScript, Smalltalk, Lisp, Python, etc ..., pois contraria sua prática estática de desenvolvimento orientado a IDE.
aoeu256 09/09/19
13

O patch para macacos só pode ser feito em linguagens dinâmicas, das quais python é um bom exemplo. Alterar um método no tempo de execução, em vez de atualizar a definição do objeto, é um exemplo; da mesma forma, adicionar atributos (métodos ou variáveis) ao tempo de execução é considerado uma correção de macaco. Isso geralmente é feito ao trabalhar com módulos para os quais você não possui a fonte, de modo que as definições de objetos não possam ser facilmente alteradas.

Isso é considerado ruim porque significa que a definição de um objeto não descreve completamente ou com precisão como ele realmente se comporta.

Aaron Dufour
fonte
No entanto, o patch para macacos pode ser útil desde que, em vez de modificar um objeto ou classe existente, você crie uma nova versão de um objeto com os membros corrigidos dentro de um decorador que grite "ei, eu vou corrigi-lo".
aoeu256 9/09/19
Você pode usar anotações em membros corrigidos para armazenar no membro corrigido qual decorador foi usado para corrigir os desvios. Digamos que você tenha um decorador desfazível que cria uma nova versão desfazível de um objeto de função com um método desfazer. Você pode colocar no decorador um campo patcher apontando para o decorador desfazível.
aoeu256 09/09/19
5

O patch para macacos está reabrindo as classes ou métodos existentes na classe em tempo de execução e alterando o comportamento, que deve ser usado com cautela, ou você deve usá-lo somente quando realmente precisar.

Como o Python é uma linguagem de programação dinâmica, as Classes são mutáveis ​​para que você possa reabri-las e modificá-las ou até substituí-las.

Kamal
fonte
1

O que é o patch de macacos? O patch para macacos é uma técnica usada para atualizar dinamicamente o comportamento de um pedaço de código em tempo de execução.

Por que usar patch de macaco? Ele nos permite modificar ou estender o comportamento de bibliotecas, módulos, classes ou métodos em tempo de execução sem modificar o código-fonte

Conclusão O patch do macaco é uma técnica interessante e agora aprendemos como fazer isso no Python. No entanto, como discutimos, ele tem suas próprias desvantagens e deve ser usado com cuidado.

Para obter mais informações, consulte [1]: https://medium.com/@nagillavenkatesh1234/monkey-patching-in-python-explained-with-examples-25eed0aea505

akash kumar
fonte