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 parece a esto:
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 obtengo esto:
Something expensive!
Something expensive!
Something expensive!
¿Qué estoy haciendo mal?
python
monkeypatching
guidoísmo
fuente
fuente
from module import non_module_member
y el parche de mono a nivel de módulo son incompatibles por esta razón y, en general, es mejor evitarlos.apackage
.from foo import bar
está bien y de hecho se recomienda cuando sea apropiado.Respuestas:
Puede ser útil pensar en cómo funcionan los espacios de nombres de Python: son esencialmente diccionarios. Entonces, cuando hagas 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.'
Esperamos que usted puede darse cuenta de por qué esto no funciona, entonces :-) Una vez que se importa un nombre en un espacio de nombres, el valor del nombre del espacio de nombres que ha importado desde 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 a_package.baz, arriba. Pero debido a que la barra importa do_something_expensive directamente, en lugar de hacer referencia a él 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.'
fuente
Hay un decorador realmente elegante para esto: Guido van Rossum: Lista de Python-Dev: Monkeypatching Idioms .
También está el paquete dectools , que vi en un PyCon 2010, que también puede usarse en este contexto, pero que en realidad podría ir al revés (monopatching en el nivel declarativo del método ... donde no está)
fuente
Si solo desea parchearlo para su llamada y, de lo contrario, 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!'
fuente
En el primer fragmento, hace
bar.do_something_expensive
referencia al objeto de función al que sea_package.baz.do_something_expensive
refiere en ese momento. Para realmente "parchear" eso, necesitaría cambiar la función en sí (solo está cambiando a qué se refieren los nombres); esto es posible, pero en realidad no desea hacerlo.En sus intentos de cambiar el comportamiento de
a_function
, ha hecho dos cosas:En el primer intento, convierte do_something_expensive en un nombre global en su módulo. Sin embargo, está llamando
a_function
, que no busca en su módulo para resolver nombres, por lo que todavía se refiere a la misma función.En el segundo ejemplo, cambia lo que se
a_package.baz.do_something_expensive
refiere, perobar.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 decirimport a_package.baz def a_function(): print a_package.baz.do_something_expensive()
La solución correcta es probablemente una de dos cosas:
a_function
para tomar una función como argumento y llamar a eso, en lugar de intentar colarse y cambiar la función a la que está codificada para referirse, oEl uso de globales (esto es lo que significa cambiar las cosas a nivel de módulo de otros módulos) es algo malo que conduce a un código no mantenible, confuso, no comprobable, no escalable, cuyo flujo es difícil de rastrear.
fuente
do_something_expensive
en laa_function()
función 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.fuente