別のモジュールの機能を別の機能に置き換えることができず、私は夢中になります。
次のようなモジュール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!
私は何を間違えていますか?
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.'
うまくいけば、なぜこれが機能しないのか理解できます:-)名前空間に名前をインポートすると、インポートした名前空間の名前の値fromは無関係です。上記のローカルモジュールの名前空間、またはa_package.bazの名前空間でdo_something_expensiveの値を変更するだけです。ただし、barはモジュール名前空間から参照するのではなく、do_something_expensiveを直接インポートするため、その名前空間に書き込む必要があります。
import bar
bar.do_something_expensive = lambda: 'Something really cheap.'
これには本当にエレガントなデコレータがあります: Guido van Rossum:Python-Devリスト:Monkeypatching Idioms 。
dectools パッケージもあります。このコンテキストでも使用できるPyCon 2010がありますが、実際には別の方法になる可能性があります(メソッド宣言レベルでのモンキーパッチ...)あなたがいないところ)
呼び出しにのみパッチを適用し、それ以外の場合は元のコードのままにする場合は、 https://docs.python.org/3/library/unittest.mock.html#patch (since = 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!'
最初のスニペットでは、bar.do_something_expensive
がその時点でa_package.baz.do_something_expensive
が参照する関数オブジェクトを参照するようにします。本当に「monkeypatch」するには、関数自体を変更する必要があります(名前が参照するものだけを変更しています)。これは可能ですが、実際にはそうしたくありません。
a_function
の動作を変更しようとして、2つのことを行いました。
最初の試みでは、do_something_expensiveをモジュール内のグローバル名にします。ただし、名前を解決するためにモジュールを検索しないa_function
を呼び出しているため、同じ関数を引き続き参照しています。
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
を再定義して、関数を引数として使用し、それを呼び出して、参照するようにハードコードされている関数をこっそりと変更しようとするのではなく、またはグローバルを使用する(これは、他のモジュールからモジュールレベルのものを変更することです)悪いことです。
a_function()
関数のdo_something_expensive
は、関数オブジェクトを指すモジュールの名前空間内の単なる変数です。モジュールを再定義すると、別のネームスペースで実行します。