82

別のモジュールの関数を別の関数に置き換えるのに問題があり、頭がおかしくなっています。

次のようなモジュール bar.py があるとします。

from a_package.baz import do_something_expensive

def a_function():
    print do_something_expensive()

そして、次のような別のモジュールがあります。

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()

結果が得られると思います:

Something expensive!
Something really cheap.
Something really cheap.

しかし、代わりに私はこれを取得します:

Something expensive!
Something expensive!
Something expensive!

私は何を間違っていますか?

4

5 に答える 5

93

Python の名前空間がどのように機能するかを考えると役立つかもしれません: それらは本質的に辞書です。したがって、これを行うと:

from a_package.baz import do_something_expensive
do_something_expensive = lambda: 'Something really cheap.'

次のように考えてください。

do_something_expensive = a_package.baz['do_something_expensive']
do_something_expensive = lambda: 'Something really cheap.'

これが機能しない理由を理解していただければ幸いです:-) 名前を名前空間にインポートすると、インポート元の名前空間の名前の値無関係になります。上記のローカル モジュールの名前空間または a_package.baz の名前空間で do_something_expensive の値を変更するだけです。しかし、bar は do_something_expensive をモジュールの名前空間から参照するのではなく、直接インポートするため、その名前空間に書き込む必要があります。

import bar
bar.do_something_expensive = lambda: 'Something really cheap.'
于 2010-03-03T22:19:45.843 に答える
21

これには本当にエレガントなデコレータがあります:Guido van Rossum:Python-Devリスト:MonkeypatchingIdioms

PyCon 2010を見たdectoolsパッケージもあります。これは、このコンテキストでも使用できる可能性がありますが、実際には逆になる可能性があります(メソッド宣言レベルでのモンキーパッチ...あなたがいない場合)

于 2010-03-03T22:20:30.123 に答える
10

呼び出し用にのみパッチを適用し、それ以外の場合は元のコードを残したい場合は、https://docs.python.org/3/library/unittest.mock.html#patchを使用できます(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!'
于 2014-09-24T11:51:49.837 に答える
3

最初のスニペットでは、その時点でbar.do_something_expensive参照している関数オブジェクトを参照しますa_package.baz.do_something_expensive。本当に「monkeypatch」するには、関数自体を変更する必要があります (名前が参照するものを変更するだけです)。これは可能ですが、実際にはそうしたくありません。

の動作を変更しようとして、次のa_function2 つのことを行いました。

  1. 最初の試行では、do_something_expensive をモジュール内のグローバル名にします。a_functionただし、名前を解決するためにモジュールを調べない を呼び出しているため、同じ関数を参照しています。

  2. 2 番目の例a_package.baz.do_something_expensiveでは、参照先を変更していますが、bar.do_something_expensive魔法のように関連付けられていません。その名前は、初期化されたときに検索した関数オブジェクトを参照しています。

最も単純だが理想からはほど遠いアプローチは、次のように変更bar.pyすることです。

import a_package.baz

def a_function():
    print a_package.baz.do_something_expensive()

正しい解決策は、おそらく次の 2 つのいずれかです。

  • a_function関数を引数として取り、それを呼び出すように再定義する。
  • クラスのインスタンスで使用する関数を格納します。これが、Python で変更可能な状態を行う方法です。

グローバルの使用 (これは、他のモジュールからモジュール レベルのものを変更することです) は、フローを追跡するのが困難な、保守不能、混乱、テスト不能、スケーラブルでないコードにつながる悪いことです。

于 2010-03-03T22:27:17.473 に答える
0

do_something_expensive関数内a_function()は、関数オブジェクトを指すモジュールの名前空間内の単なる変数です。モジュールを再定義すると、別の名前空間で実行します。

于 2010-03-04T11:56:26.000 に答える