Monkey Patching

Qué es el Monkey Patching

El Monkey Patching es una técnica de programación de los lenguajes dinámicos que consiste en modificar el código en tiempo de ejecución.

Esto quiere decir que en lugar de modificar el código fuente de una clase, por ejemplo, en tiempo de ejecución asignamos otra función a ese método y todas las llamadas posteriores en lugar de ejecutar el código definido en la clase ejecutarán el código "parcheado".

Esto está relacionado con el llamado Duck typing, que viene a decir que si nada como un pato y suena como un pato, esto es un pato. Y esto del pato hace referencia a que los tipos no tienen que cumplir una interfaz estricta, simplemente, si se comportan como tal, es el tipo esperado. Y con el Monkey Patching podemos hacer que un perro haga cuak como un pato, modificando el objeto perro en tiempo de ejecución.

If it walks like a duck and talks like a duck, it’s a duck, right? So if this duck is not giving you the noise that you want, you’ve got to just punch that duck until it returns what you expect.

-- Patrick Ewing

Qué hay que tener en cuenta para el Monkey Patching

Para poder hacer Monkey Patching necesitamos, tipado dinámico, ya que si un método espera un objeto tipo pato y le pasamos uno tipo perro, pero "parcheado", el método no debe quejarse.

También tenemos que tener en cuenta la vida útil de los módulos y objetos. Si parcheamos un módulo, es necesario conocer el alcance de esas modificaciones, si sólo afectarán a mi módulo o si pueden afectar a otros módulos que lo importen, o si parcheamos una instancia, es necesario conocer hasta dónde llegarán esas modificaciones.

Y por supuesto también es necesario tener acceso a las partes del código que se quieran modificar, si vamos a modificar un objeto con métodos privados.

Por suerte, en Python no tenemos métodos privados realmente, se suele usar el guión bajo (_metodo), que según la herramienta lo oculta como privado, pero en realidad en Python todo es accesible y por tanto podemos parchear lo que queramos.

Algunos ejemplos

Veamos el ejemplo que viene en la wikipedia:

>>> import math
>>> math.pi
3.141592653589793
>>> math.pi = 3
>>> math.pi
3

En este ejemplo se ve que se importa el módulo math y se modifica el valor de math.pi. Fácil y sencillo. No tiene mucho sentido este cambio, pero si lo hacemos en nuestro código, ya todo el código que venga después y use math.pi recibirá como valor 3, y esto afecta incluso a librerías de terceros.

Algo más práctico con código Django:

from django import shortcuts

old_render = shortcuts.render

def custom_render(*args, **kwargs):
    t1 = time.time()
    resp = old_render(*args, **kwargs)
    s = time.time() - t1
    print("Render time: %s seconds" % s)
    return resp

shortcuts.render = custom_render

En este ejemplo modificamos el comportamiento por defecto de la función render de django. Reemplazamos esta función por otra que lo único que hace es medir el tiempo que se tarda en la llamada a la función inicial y sacarlo por pantalla.

Otro ejemplo con Django:

def password_logger(self, newp):
    send_to_hacker_email(self.username + ":" + newp)
    self.real_set_password(newp)

user = User.objects.get(username="admin")
user.real_set_password = user.set_password
user.set_password = password_logger

Aquí se muestra cómo se puede modificar una instancia. En este ejemplo se modifica el método set_password del usuario admin, para que cuando se llame a este método se envíe la nueva contraseña por email a un presunto hacker.

En el caso de los modelos de django no tiene mucho sentido modificar la instancia, mejor sería modificar el modelo, la definición de la clase, pero vale como ejemplo.

Cuándo usar el Monkey Patching

El Monkey Patching es una herramienta muy poderosa, pero a la vez, es algo tremendamente peligroso, ya que hace que una clase o un módulo no se ejecuten como se espera, como dice su definición y por tanto puede dar más de un dolor de cabeza.

Un gran poder conlleva una gran responsabilidad

-- Ben Parker

Por esto hay que saber cuándo usar el Monkey Patching y cuando no.

Cuando SÍ:

  • Depuración o ejecución paso a paso: pdb, ipdb, mientras depuramos un código para que nos saque información por pantalla, etc.

  • Testing: Para reemplazar métodos, atributos o funciones en tiempo de ejecución en los tests, por ejemplo una función que genera números aleatorios la podemos transformar en algo predecible para testear otros métodos relacionados, o podemos reemplazar funciones que tarden mucho tiempo debido a sistemas externos, para que los tests se puedan ejecutar rápidamente.

  • Fixs o arreglos de libs externas: Para aplicar un parche o un arreglo de una biblioteca externa en una versión que aún no ha sido parcheada oficialmente.

Cuando NO:

En la mayoría de las ocasiones, no es recomendable usar Monkey Patching. Por lo tanto, si hay otra forma de hacer lo que queremos hacer, seguramente sea mejor idea que el Monkey Patching.

Como he comentado antes, el Monkey Patching hace que el código que se ejecuta sea diferente del descrito en la definición de la clase o módulo, por tanto, hay que evitarlo y cuando se use hay que documentarlo muy bien, porque si no, puede enmascarar problemas o hacer que futuras modificaciones al código original no tengan efecto.

Resumiendo, el Monkey Patching está muy guay, pero úsalo bajo tu propia responsabilidad.

Comments !