tiempo real matrices graficos graficas graficar grafica coordenadas barras python monkeypatching

real - ¿Cómo arregla un mono una función en Python?



matplotlib python (5)

Tengo problemas para reemplazar una función de un módulo diferente con otra función y me está volviendo loco.

Digamos que tengo un módulo bar.py que se ve así:

from a_package.baz import do_something_expensive def a_function(): print do_something_expensive()

Y tengo otro módulo que se ve así:

from bar import a_function a_function() from a_package.baz import do_something_expensive do_something_expensive = lambda: ''Something really cheap.'' a_function() import a_package.baz a_package.baz.do_something_expensive = lambda: ''Something really cheap.'' a_function()

Esperaría obtener los resultados:

Something expensive! Something really cheap. Something really cheap.

Pero en cambio, entiendo esto:

Something expensive! Something expensive! Something expensive!

¿Qué estoy haciendo mal?


En el primer fragmento de bar.do_something_expensive , haces bar.do_something_expensive hace referencia al objeto de función al que a_package.baz.do_something_expensive refiere en ese momento. Para realmente "monopatch" que necesitaría cambiar la función en sí (solo está cambiando a qué nombres se refieren); esto es posible, pero en realidad no quiere hacer eso.

En sus intentos por cambiar el comportamiento de a_function , ha hecho dos cosas:

  1. En el primer intento, haces de do_something_expensive un nombre global en tu módulo. Sin embargo, está llamando a_function , que no se ve en su módulo para resolver nombres, por lo que todavía se refiere a la misma función.

  2. En el segundo ejemplo, cambia lo que a_package.baz.do_something_expensive hace referencia, pero bar.do_something_expensive no está ligado mágicamente a él. Ese nombre todavía se refiere al objeto de función que buscó cuando se inició.

El enfoque más simple pero lejos de ser ideal sería cambiar bar.py para decir

import a_package.baz def a_function(): print a_package.baz.do_something_expensive()

La solución correcta es probablemente una de dos cosas:

  • Redefine a_function para tomar una función como argumento y llamarla, en lugar de tratar de colarse y cambiar a qué función está codificada para referirse, o
  • Almacene la función para ser utilizada en una instancia de una clase; así es como hacemos el estado mutable en Python.

El uso de variables globales (esto es lo que cambia el material a nivel de módulo de otros módulos) es una mala cosa que lleva a un código inmanejable, confuso, no comprobable y no escamoteable cuyo flujo es difícil de rastrear.


Hay un decorador realmente elegante para esto: Guido van Rossum: lista de Python-Dev: modismos de parcheo .

También está el paquete dectools , que vi en PyCon 2010, que también se puede usar en este contexto, pero que en realidad podría ser al revés (monopatching en el nivel declarativo del método ... donde tú no estás)


Puede ser útil pensar en cómo funcionan los espacios de nombres de Python: son esencialmente diccionarios. Entonces cuando haces esto:

from a_package.baz import do_something_expensive do_something_expensive = lambda: ''Something really cheap.''

piensa en esto, de esta manera:

do_something_expensive = a_package.baz[''do_something_expensive''] do_something_expensive = lambda: ''Something really cheap.''

Con suerte, puede darse cuenta de por qué esto no funciona :-) Una vez que importa un nombre en un espacio de nombres, el valor del nombre en el espacio de nombres desde el que importó es irrelevante. Solo está modificando el valor de do_something_expensive en el espacio de nombres del módulo local, o en el espacio de nombres de un_paquete.baz, arriba. Pero debido a que la barra importa do_something_expensive directamente, en lugar de hacer referencia a ella desde el espacio de nombres del módulo, debe escribir en su espacio de nombres:

import bar bar.do_something_expensive = lambda: ''Something really cheap.''


Si solo quiere parcharlo para su llamada y dejar el código original, puede usar https://docs.python.org/3/library/unittest.mock.html#patch (desde Python 3.3):

with patch(''a_package.baz.do_something_expensive'', new=lambda: ''Something really cheap.''): print do_something_expensive() # prints ''Something really cheap.'' print do_something_expensive() # prints ''Something expensive!''


do_something_expensive en la función a_function() es solo una variable dentro del espacio de nombres del módulo que apunta a un objeto de función. Cuando redefine el módulo lo está haciendo en un espacio de nombres diferente.