¿Cómo parchear un mono una función en Python?
-
24-09-2019 - |
Pregunta
Estoy teniendo problemas para reemplazar una función de un módulo diferente con otra función y me está volviendo loco.
Vamos a decir que tengo una bar.py módulo que es similar al siguiente:
from a_package.baz import do_something_expensive
def a_function():
print do_something_expensive()
Y tengo otro módulo que es similar al siguiente:
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()
Yo esperaría para obtener los resultados:
Something expensive!
Something really cheap.
Something really cheap.
Pero en vez me sale esto:
Something expensive!
Something expensive!
Something expensive!
¿Qué estoy haciendo mal?
Solución
Puede ayudar a pensar en cómo el trabajo de Python espacios de nombres: son esencialmente los diccionarios. Así que cuando usted hace esto:
from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'
pensar en ello como esto:
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 de es irrelevante. Sólo se está modificando el valor de do_something_expensive en el espacio del módulo local o en el espacio de nombres de a_package.baz, arriba. Pero debido a que las importaciones de barras do_something_expensive directamente, en lugar de hacer referencia a ella desde el espacio de nombres del módulo, es necesario escribir en su espacio de nombres:
import bar
bar.do_something_expensive = lambda: 'Something really cheap.'
Otros consejos
Hay un decorador muy elegante para esto: Guido van Rossum: lista de Python-Dev:. Monkeypatching modismos
También existe la href="http://pypi.python.org/pypi/dectools/0.1.3" rel="noreferrer"> dectools paquete , que viste un PyCon 2010, que puede ser capaz de ser utilizado en este contexto también, pero que en realidad podría ir a otro lado (monkeypatching a nivel declarativo método ... en el que no estás)
Si sólo desea parche para su llamada y de lo contrario dejar el código original se puede utilizar 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!'
En el primer fragmento, que hacen bar.do_something_expensive
referirse al objeto función que se refiere a a_package.baz.do_something_expensive
en ese momento. Para realmente "monkeypatch" que lo que se necesita para cambiar la función en sí (sólo está cambiando los nombres que se refieren a); esto es posible, pero en realidad no quería hacer eso.
En sus intentos de cambiar el comportamiento de a_function
, se han hecho dos cosas:
-
En el primer intento, que hacen do_something_expensive un nombre global en su módulo. Sin embargo, a la que llama
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. -
En el segundo ejemplo se cambia lo que se refiere a
a_package.baz.do_something_expensive
, pero no sebar.do_something_expensive
mágicamente ligado a ella. Ese nombre todavía se refiere al objeto de la función que la vista cuando se initilized.
El más simple, pero muy lejos de ser ideales enfoque sería el cambio bar.py
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:
- Redefinir
a_function
para tomar una función como argumento y llamar a que, en lugar de tratar de colarse en el cambio y cuál es la función que está codificado para referirse a, o - Guarde la función que se utilizará en una instancia de una clase; esta es la forma en que hacemos estado mutable en Python.
El uso de variables globales (esto es lo que el cambio de la materia de nivel de módulo de otros módulos es) es un malo que conduce a la confusión, el código imposible de mantener, untestestable, unscalable el flujo de los cuales son difíciles de controlar.
do_something_expensive
en la función a_function()
es sólo una variable dentro del espacio de nombres del módulo apunta a un objeto función. Al volver a definir el módulo que está haciendo en un espacio de nombres diferente.