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?

Foi útil?

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:

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

  2. No segundo exemplo, você muda o que a_package.baz.do_something_expensive refere -se a, mas bar.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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top