私は理解しようとしています、モンキーパッチまたはモンキーパッチとは何ですか?
メソッドや演算子のようなものが過負荷なのか、それとも委任しているのでしょうか。
それはこれらのことと共通点がありますか?
いいえ、それはそれらのことのどれにも似ていません。それは単に実行時に属性を動的に置き換えることです。
たとえば、メソッドget_data
を持つクラスを考えてみましょう。このメソッドは(データベースやWeb APIなどで)外部参照を行い、クラス内の他のさまざまなメソッドがそれを呼び出します。ただし、単体テストでは、外部データソースに依存したくないため、get_data
メソッドを固定データを返すスタブに動的に置き換えます。
Pythonクラスは変更可能で、メソッドはクラスの属性にすぎないため、これは好きなだけ実行できます。実際、モジュール内のクラスや関数をまったく同じ方法で置き換えることもできます。
しかし、 コメンター が指摘しているように、モンキーパッチ時には注意が必要です。
あなたのテストロジック以外の何かがget_data
を同様に呼び出すならば、それはまたオリジナルではなくあなたの猿パッチの代わりを呼び出すでしょう - それは良いことでも悪いことでもありえます。注意してください。
置換するまでにget_data
関数も指す変数または属性が存在する場合、この別名はその意味を変更することはなく、引き続き元のget_data
を指します。 (なぜですか?Pythonはクラス内のget_data
という名前を他の関数オブジェクトに再バインドするだけです。他の名前のバインディングはまったく影響を受けません。)
MonkeyPatchは、実行時(通常は起動時)に他のコードを拡張または変更するPythonコードです。
簡単な例は次のようになります。
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
出典:Zope wikiのMonkeyPatch ページ。
モンキーパッチとは何ですか?
簡単に言うと、サルのパッチはプログラムの実行中にモジュールやクラスに変更を加えることです。
Pandasのドキュメントにモンキーパッチの例があります。
import pandas as pd
def just_foo_cols(self):
"""Get a list of column names containing the string 'foo'
"""
return [x for x in self.columns if 'foo' in x]
pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method
これを打破するには、まずモジュールをインポートします。
import pandas as pd
次に、メソッド定義を作成します。これは、クラス定義の範囲外で、バインドされておらず自由に存在するものです(関数とバインドされていないメソッドは区別されていないので、Python 3ではバインドされていないメソッドは削除されます)。
def just_foo_cols(self):
"""Get a list of column names containing the string 'foo'
"""
return [x for x in self.columns if 'foo' in x]
次に、そのメソッドを使いたいクラスに単純にアタッチします。
pd.DataFrame.just_foo_cols = just_foo_cols # monkey-patch the DataFrame class
そして、クラスのインスタンスでメソッドを使用し、終了したらメソッドを削除できます。
df = pd.DataFrame([list(range(4))], columns=["A","foo","foozball","bar"])
df.just_foo_cols()
del pd.DataFrame.just_foo_cols # you can also remove the new method
名前マングリング(名前に変更を加える、お勧めしません)を名前マングリングを使用している場合は、手動で名前マングルを作成する必要があります。名前マングリングはお勧めしませんので、ここでは説明しません。
たとえば、テストでこの知識をどのように使用できますか?
このような場合は正しい動作を保証する必要があるため、エラーが発生する外部データソースへのデータ取得呼び出しをシミュレートする必要があるとします。この振る舞いを確実にするためにデータ構造をモンキーパッチすることができます。 (だからDaniel Rosemanが提案したのと同じようなメソッド名を使う:)
import datasource
def get_data(self):
'''monkey patch datasource.Structure with this to simulate error'''
raise datasource.DataRetrievalError
datasource.Structure.get_data = get_data
また、このメソッドがエラーを発生させることに依存する動作をテストするときに、正しく実装されていれば、テスト結果にその動作が表示されます。
上記を実行するだけで、プロセスの存続期間中にStructure
オブジェクトが変更されるため、それを回避するためにユニットテストでセットアップとティアダウンを使用することをお勧めします。
def setUp(self):
# retain a pointer to the actual real method:
self.real_get_data = datasource.Structure.get_data
# monkey patch it:
datasource.Structure.get_data = get_data
def tearDown(self):
# give the real method back to the Structure object:
datasource.Structure.get_data = self.real_get_data
(上記は問題ありませんが、mock
ライブラリを使用してコードにパッチを適用することをお勧めします。mock
のpatch
デコレータは、上記よりもエラーが発生しにくく、コード行数が増えるため、導入する機会が増えます。 mock
のコードをまだレビューしていませんが、同様の方法でモンキーパッチを使用していると思います。)
ウィキペディア によると、
Pythonでは、モンキーパッチという用語は実行時にクラスまたはモジュールを動的に変更することのみを意味します。これは、既存のサードパーティコードに、意図したとおりに機能しないバグまたは機能に対する回避策としてパッチを当てることを目的としています。
まず、モンキーパッチは悪意のあるハックです(私の意見では)。
多くの場合、モジュールまたはクラスレベルのメソッドをカスタム実装に置き換えるために使用されます。
最も一般的なユースケースは、元のコードを置き換えることができないときにモジュールまたはクラスのバグに対する回避策を追加することです。この場合は、モンキーパッチによる「間違った」コードを自分のモジュール/パッケージ内の実装に置き換えます。
モンキーパッチは動的言語でしか実行できません。Pythonがその良い例です。オブジェクト定義を更新する代わりに実行時にメソッドを変更することはその一例です;同様に、実行時に属性(メソッドか変数かにかかわらず)を追加することはサルのパッチ適用と見なされます。これらは、あなたがソースを持っていないモジュールを使って作業しているときによく行われ、オブジェクト定義は簡単には変更できません。
これは悪いと考えられています。なぜならそれはオブジェクトの定義がそれが実際にどう振る舞うかを完全にまたは正確に記述しないことを意味するからです。
モンキーパッチは、実行時にクラス内の既存のクラスまたはメソッドを再オープンして動作を変更することです。これは慎重に使用する必要があります。または、必要に応じて使用する必要があります。
Pythonは動的プログラミング言語であるため、クラスは変更可能であるため、クラスを再度開いて変更したり置き換えたりすることさえできます。