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?

¿Fue útil?

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

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:

  1. 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.

  2. En el segundo ejemplo se cambia lo que se refiere a a_package.baz.do_something_expensive, pero no se bar.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.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top