Como um macaco remeve uma função no python?
-
24-09-2019 - |
Pergunta
Estou tendo problemas para substituir uma função de um módulo diferente por outra função e está me deixando louco.
Digamos que eu tenho um módulo bar.py que se parece com o seguinte:
from a_package.baz import do_something_expensive
def a_function():
print do_something_expensive()
E eu tenho outro módulo que se parece com o seguinte:
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()
Eu esperaria obter os resultados:
Something expensive!
Something really cheap.
Something really cheap.
Mas, em vez disso, entendi:
Something expensive!
Something expensive!
Something expensive!
O que estou fazendo errado?
Solução
Pode ajudar a pensar em como funcionam os namespaces do Python: eles são essencialmente dicionários. Então, quando você faz isso:
from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'
Pense assim:
do_something_expensive = a_package.baz['do_something_expensive']
do_something_expensive = lambda: 'Something really cheap.'
Espero que você possa perceber por que isso não funciona então :-) Depois de importar um nome para um espaço para nome, o valor do nome no espaço de nome que você importou a partir de é irrelevante. Você está apenas modificando o valor de do_something_expensive no espaço para nome do módulo local, ou no espaço para nome do a_package.baz, acima. Mas como a barra importa diretamente, em vez de referenciar -o do espaço para nome do módulo, você precisa escrever em seu espaço para nome:
import bar
bar.do_something_expensive = lambda: 'Something really cheap.'
Outras dicas
Há um decorador realmente elegante para isso: Guido van Rossum: Python-Dev List: Monkeypatching Idioms.
Há também o Dectools Pacote, que vi um Pycon 2010, que também pode ser usado nesse contexto, mas isso pode realmente ir para o outro lado (macaco no nível declarativo do método ... onde você não está)
Se você deseja corrigi -lo apenas para sua chamada e deixe o código original que você pode 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!'
No primeiro trecho, você faz bar.do_something_expensive
consulte o objeto de função que a_package.baz.do_something_expensive
refere -se a esse momento. Para realmente "Monkeypatch", que você precisaria alterar a própria função (você está mudando apenas a que os nomes se referem); Isso é possível, mas você realmente não quer fazer isso.
Em suas tentativas de mudar o comportamento de a_function
, você fez duas coisas:
Na primeira tentativa, você faz um nome global Do_something_expensive em seu módulo. No entanto, você está ligando
a_function
, que não parece no seu módulo para resolver nomes, portanto ainda se refere à mesma função.No segundo exemplo, você muda o que
a_package.baz.do_something_expensive
refere -se a, masbar.do_something_expensive
não está magicamente ligado a isso. Esse nome ainda se refere ao objeto de função que procurou quando foi iniciado.
A abordagem mais simples, mas distante-ideal, seria mudar bar.py
dizer
import a_package.baz
def a_function():
print a_package.baz.do_something_expensive()
A solução certa é provavelmente uma das duas coisas:
- Redefinir
a_function
Para assumir uma função como um argumento e chamar assim, em vez de tentar se esgueirar e mudar a que função é codificada para se referir, ou - Armazene a função a ser usada em uma instância de uma classe; É assim que fazemos estado mutável em Python.
Usando globais (é isso que é a mudança de material no nível do módulo de outros módulos) é um coisa ruim Isso leva a um código inacessível, confuso, não testável e indisponível, cujo fluxo é difícil de rastrear.
do_something_expensive
no a_function()
A função é apenas uma variável dentro do espaço para nome do módulo apontando para um objeto de função. Quando você redefine o módulo, está fazendo isso em um espaço para nome diferente.