¿Qué es el parcheado de monos?

549

Estoy tratando de entender, ¿qué es un parche de mono o un parche de mono?

¿Es algo así como métodos / operadores que sobrecargan o delegan?

¿Tiene algo en común con estas cosas?

Sergei Basharov
fuente
Creo que la definición de google es útil y más general: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

Respuestas:

522

No, no es como ninguna de esas cosas. Es simplemente el reemplazo dinámico de atributos en tiempo de ejecución.

Por ejemplo, considere una clase que tiene un método get_data. Este método realiza una búsqueda externa (en una base de datos o API web, por ejemplo), y varios otros métodos en la clase lo llaman. Sin embargo, en una prueba unitaria, no desea depender de la fuente de datos externa, por lo que reemplaza dinámicamente el get_datamétodo con un código auxiliar que devuelve algunos datos fijos.

Debido a que las clases de Python son mutables, y los métodos son solo atributos de la clase, puedes hacer esto tanto como quieras y, de hecho, incluso puedes reemplazar clases y funciones en un módulo exactamente de la misma manera.

Pero, como lo señaló un comentarista , tenga cuidado al parchear mono:

  1. Si hay algo más además de las llamadas de lógica de prueba get_data, también llamará a su reemplazo parcheado en lugar del original, lo que puede ser bueno o malo. Solo ten cuidado.

  2. Si existe alguna variable o atributo que también apunta a la get_datafunción en el momento en que la reemplaza, este alias no cambiará su significado y continuará apuntando al original get_data. (¿Por qué? Python simplemente vuelve a unir el nombre get_datade su clase a algún otro objeto de función; otros enlaces de nombre no se ven afectados en absoluto).

Daniel Roseman
fuente
1
@LutzPrechelt solo para que quede claro para mí, ¿a qué te refieres pointing to the original get_data function? ¿Quiere decir que cuando almacena una función dentro de una variable si alguien cambia esa función, la variable continuará apuntando a la anterior?
fabriciorissetto
3
@fabriciorissetto: normalmente no se cambian los objetos de función en Python. Cuando monas un parche get_data, vuelves a vincular el nombre get_dataa una función simulada. Si algún otro nombre en algún otro lugar del programa está vinculado a la función-anteriormente-conocida-como- get_data, nada cambiará para ese otro nombre.
Lutz Prechelt
1
@LutzPrechelt ¿Podría explicar un poco más sobre esto?
Calvin Ku
Creo que los parches de mono pueden ser útiles especialmente para la depuración y en las funciones de decoradores o fábricas de objetos. Sin embargo, recuerde explícita es mejor que implícito así que asegúrese de que su código es insensible al contexto, leer "Goto considera perjudicial", etc ...
aoeu256
Entonces, ¿es algo similar a usar la función 'eval', donde podría insertar un nuevo código en tiempo de ejecución?
Wintermute
363

Un MonkeyPatch es un fragmento de código de Python que extiende o modifica otro código en tiempo de ejecución (generalmente al inicio).

Un ejemplo simple se ve así:

from SomeOtherProduct.SomeModule import SomeClass

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

SomeClass.speak = speak

Fuente: página de MonkeyPatch en el wiki de Zope.

Paolo
fuente
126

¿Qué es un parche de mono?

En pocas palabras, el parche de mono está haciendo cambios en un módulo o clase mientras se ejecuta el programa.

Ejemplo de uso

Hay un ejemplo de parches de mono en la documentación de 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 desglosar esto, primero importamos nuestro módulo:

import pandas as pd

A continuación, creamos una definición de método, que existe sin consolidar y libre fuera del alcance de cualquier definición de clase (dado que la distinción es bastante insignificante entre una función y un método independiente, Python 3 elimina el método independiente):

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]

A continuación, simplemente adjuntamos ese método a la clase en la que queremos usarlo:

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

Y luego podemos usar el método en una instancia de la clase, y eliminar el método cuando hayamos terminado:

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

Advertencia para el cambio de nombre

Si está usando el cambio de nombre (prefijar los atributos con un doble guión bajo, que altera el nombre y que no recomiendo), tendrá que cambiar el nombre manualmente si hace esto. Como no recomiendo el cambio de nombre, no lo demostraré aquí.

Ejemplo de prueba

¿Cómo podemos usar este conocimiento, por ejemplo, en las pruebas?

Supongamos que necesitamos simular una llamada de recuperación de datos a una fuente de datos externa que resulte en un error, porque queremos asegurar un comportamiento correcto en tal caso. Podemos mono parchear la estructura de datos para garantizar este comportamiento. (Entonces, usando un nombre de método similar al 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

Y cuando lo probamos para detectar un comportamiento que se basa en que este método genera un error, si se implementa correctamente, obtendremos ese comportamiento en los resultados de la prueba.

Simplemente hacer lo anterior alterará el Structureobjeto durante la vida útil del proceso, por lo que querrá usar configuraciones y desmontajes en sus pruebas de unidad para evitar hacerlo, por ejemplo:

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

(Si bien lo anterior está bien, probablemente sería una mejor idea usar la mockbiblioteca para parchear el código. mockEl patchdecorador sería menos propenso a errores que hacer lo anterior, lo que requeriría más líneas de código y, por lo tanto, más oportunidades para introducir errores Todavía tengo que revisar el código, mockpero imagino que usa parches de mono de manera similar).

Aaron Hall
fuente
Entonces, ¿es la carga del monkeypatcher almacenar una referencia al método real? por ejemplo, ¿qué sucede si uno olvida el paso "retener un puntero", se pierde?
Tommy
3
@Tommy Si los reembolsos a un método "sobrescrito" van a cero, se recolecta basura y, por lo tanto, se "pierde" durante la vida útil del proceso (o a menos que el módulo en el que se originó se vuelva a cargar, pero eso generalmente se desaconseja).
Aaron Hall
33

De acuerdo con Wikipedia :

En Python, el término parche de mono solo se refiere a modificaciones dinámicas de una clase o módulo en tiempo de ejecución, motivado por la intención de parchear el código de terceros existente como una solución a un error o característica que no actúa como usted desea.

David Heffernan
fuente
16

Primero: el parcheado de monos es un hack malvado (en mi opinión).

A menudo se usa para reemplazar un método en el nivel de módulo o clase con una implementación personalizada.

El caso de uso más común es agregar una solución alternativa para un error en un módulo o clase cuando no puede reemplazar el código original. En este caso, reemplaza el código "incorrecto" a través de parches de mono con una implementación dentro de su propio módulo / paquete.

Andreas Jung
fuente
8
En caso de que algunos módulos apliquen parches a la misma cosa: estás condenado.
Andreas Jung el
49
Si bien su poder podría ser peligroso en general, es excelente para las pruebas
dkrikun
1
El caso de uso más común es en realidad para pruebas, especialmente pruebas unitarias. Desea probar solo su código, por lo que parchea cualquier llamada externa para devolver un resultado esperado.
brocoli
1
no es malo, lo uso para corregir errores en el software de otras personas hasta que salga una nueva versión en lugar de bifurcar y crear una nueva dependencia.
nurettin
1
El parcheado de mono se puede hacer de una "manera puramente funcional", no de una manera mutable, "sensible al contexto", similar a goto, solo haciendo parches dentro de los decoradores que devuelven una nueva versión parcheada de su clase / método (en lugar de modificarlo). Muchos programadores de C # / Java no saben sobre el desarrollo impulsado por REPL, por lo que codifican en sus IDE que requieren que todo esté estáticamente definido. Dado que C # / Java no tenía parches de mono, asumen que es malo cuando lo ven en JavaScript, Smalltalk, Lisp, Python, etc., ya que va en contra de su práctica de desarrollo dirigida por IDE estático.
aoeu256
13

Los parches de mono solo se pueden hacer en lenguajes dinámicos, de los cuales python es un buen ejemplo. Cambiar un método en tiempo de ejecución en lugar de actualizar la definición del objeto es un ejemplo; de manera similar, agregar atributos (ya sean métodos o variables) en tiempo de ejecución se considera parche de mono. A menudo se realizan cuando se trabaja con módulos para los que no tiene la fuente, de modo que las definiciones de objetos no se pueden cambiar fácilmente.

Esto se considera malo porque significa que la definición de un objeto no describe completa o exactamente cómo se comporta realmente.

Aaron Dufour
fuente
Sin embargo, el parche de mono puede ser útil siempre que en lugar de modificar un objeto o clase existente, cree una nueva versión de un objeto con miembros parcheados dentro de un decorador que grita "oye, voy a parchearlo".
aoeu256
Puede usar anotaciones en miembros parcheados para almacenar en el miembro parche qué decorador se usó para parchear los parches. Digamos que tiene un decorador que no se puede deshacer que crea una nueva versión que no se puede deshacer de un objeto de función con un método de deshacer. Puede poner en el decorador un campo de parche que apunte a su decorador que no se puede deshacer.
aoeu256
5

Monkey parchear es reabrir las clases o métodos existentes en clase en tiempo de ejecución y cambiar el comportamiento, que debe usarse con precaución, o debe usarlo solo cuando realmente lo necesite.

Como Python es un lenguaje de programación dinámico, las clases son mutables para que pueda volver a abrirlas y modificarlas o incluso reemplazarlas.

kamal
fuente
1

¿Qué es el parcheado de monos? Los parches de mono son una técnica utilizada para actualizar dinámicamente el comportamiento de un fragmento de código en tiempo de ejecución.

¿Por qué usar parches de mono? Nos permite modificar o ampliar el comportamiento de bibliotecas, módulos, clases o métodos en tiempo de ejecución sin modificar realmente el código fuente

Conclusión Los parches de mono son una técnica genial y ahora hemos aprendido cómo hacerlo en Python. Sin embargo, como discutimos, tiene sus propios inconvenientes y debe usarse con cuidado.

Para obtener más información, consulte [1]: https://medium.com/@nagillavenkatesh1234/monkey-patching-in-python-explained-with-examples-25eed0aea505

akash kumar
fuente