モンキーパッチとは何ですか?


548

サルのパッチまたはサルのパッチとは何ですか?

それはメソッド/演算子のオーバーロードや委譲のようなものですか?

これらに共通点はありますか?


私はグーグルからの定義が有用で最も一般的だと思います:Monkey patching is a technique to add, modify, or suppress the default behavior of a piece of code at runtime without changing its original source code.
チャーリー・パーカー

回答:


522

いいえ、それはそれらのどれとも似ていません。これは、単に実行時に属性を動的に置き換えることです。

たとえば、メソッドを持つクラスを考えてみましょうget_data。このメソッドは(データベースやWeb APIなどで)外部ルックアップを行い、クラス内の他のさまざまなメソッドがそれを呼び出します。ただし、単体テストでは、外部データソースに依存したくないので、get_dataメソッドを、固定データを返すスタブで動的に置き換えます。

Pythonクラスは変更可能であり、メソッドはクラスの属性にすぎないため、好きなだけこれを行うことができます。実際、モジュール内のクラスと関数をまったく同じ方法で置き換えることもできます。

ただし、コメンターが指摘したように、モンキーパッチを適用するときには注意が必要です。

  1. テストロジック以外に何かが呼び出さget_dataれた場合、元のコードではなく、サルがパッチを適用したものも呼び出されます。注意してください。

  2. 変数または属性が存在しget_data、それを置き換えるときに関数も指している場合、このエイリアスはその意味を変更せず、元のを指し続けget_dataます。(なぜですか?Pythonはget_dataクラス内の名前を他の関数オブジェクトに再バインドするだけです。他の名前バインディングはまったく影響を受けません。)


1
@LutzPrecheltは私にとって明確にするために、どういう意味pointing to the original get_data functionですか?誰かがその関数を変更した場合、変数内に関数を格納すると、変数は引き続き古い関数を指しますか?
fabriciorissetto 2017年

3
@fabriciorissetto:通常、Pythonでは関数オブジェクトを変更しません。monkey-patch get_dataを実行すると、名前get_dataをモック関数に再バインドします。プログラム内の別の場所にある他の名前が-former-formerly-known-as- get_dataにバインドされている場合、その他の名前については何も変更されません。
Lutz Prechelt 2017年

1
@LutzPrecheltこれについてもう少し説明してもらえますか?
Calvin Ku

モンキーパッチは、デバッグやデコレータやオブジェクトファクトリの関数で特に役立つと思います。しかし、明示的にはそう...など、「後藤は有害とみなさ」読み、あなたのコードは、コンテキスト・鈍感であることを確認してくださいより良い暗黙的よりであることを覚えている
aoeu256

それで、実行時に新しいコードを挿入できる「eval」関数を使用するのと同じようなものですか?
Wintermute

363

MonkeyPatchは、実行時(通常は起動時)に他のコードを拡張または変更するPythonコードの一部です。

簡単な例は次のようになります:

from SomeOtherProduct.SomeModule import SomeClass

def speak(self):
    return "ook ook eee eee eee!"

SomeClass.speak = speak

出典: Zope wikiのMonkeyPatchページ。


126

サルパッチとは何ですか?

簡単に言うと、モンキーパッチは、プログラムの実行中にモジュールまたはクラスに変更を加えることです。

使用例

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コードにパッチを適用するライブラリを。mockpatchデコレータは、上記を行うよりも発生しやすい小さいエラーになり、コードの複数行を必要とするので、エラーを導入するより多くの機会と思われます。まだコードを確認する必要はありませんmockが、同様の方法でサルのパッチを使用していると思います。)


実際のメソッドへの参照を保存するのは、モンキーパッチャーの負担ですか?たとえば、「ポインタを保持する」ステップを忘れると、どうなりますか?
トミー

3
@Tommy「上書きされた」メソッドへの参照カウントがゼロになると、それはガベージコレクションされ、プロセスの存続期間中に「失われ」ます(または、元のモジュールがリロードされない限り、通常は推奨されません)。
アーロンホール

33

ウィキペディアによると:

Pythonでは、モンキーパッチという用語は、実行時にクラスまたはモジュールを動的に変更することのみを指し、既存のサードパーティコードに、意図したとおりに機能しないバグまたは機能の回避策としてパッチを当てようとする動機があります。


16

まず、サルのパッチは悪質なハックです(私の意見では)。

多くの場合、モジュールまたはクラスレベルのメソッドをカスタム実装で置き換えるために使用されます。

最も一般的な使用例は、元のコードを置き換えることができない場合に、モジュールまたはクラスのバグの回避策を追加することです。この場合、サルのパッチを適用して「間違った」コードを独自のモジュール/パッケージ内の実装に置き換えます。


8
一部のモジュールが同じことをサルパッチングする場合:あなたは運命にあります。
Andreas Jung

49
その力は確かに一般的に危険である可能性がありますが、テストには最適です
dkrikun 2013年

1
最も一般的なユースケースは、実際にはテスト、特にユニットテストです。コードのみをテストしたいので、期待する結果を返すように外部呼び出しにパッチを適用します。
ブロッコリー2017

1
それは悪ではありません。新しい依存関係をフォークして作成するのではなく、新しいリリースが出るまで、他の人のソフトウェアのバグにパッチを当てるために使用します。
ヌレティン

1
モンキーのパッチは、変更可能なではなく、クラス/メソッドの新しいパッチバージョンを返すデコレータ内でのみパッチを適用することにより、変更可能な「コンテキスト依存」のgotoのような「純粋な機能的な方法」で実行できます。多くのC#/ JavaプログラマーはREPL駆動の開発について知らないため、すべてを静的に定義する必要があるIDEでコーディングします。C#/ Javaにはモンキーパッチがなかったため、JavaScript、Smalltalk、Lisp、Pythonなどで見たとき、それらは悪魔であり、静的なIDE駆動の開発慣行に反するものです。
aoeu256

13

モンキーパッチは動的言語でのみ実行できます。Pythonはその良い例です。オブジェクト定義を更新するのではなく、実行時にメソッドを変更することは1つの例です。同様に、実行時に属性(メソッドまたは変数)を追加することは、モンキーパッチと見なされます。これらはソースのないモジュールを操作するときによく行われ、オブジェクト定義を簡単に変更できないようにします。

これは、オブジェクトの定義が実際にどのように動作するかを完全または正確に記述していないことを意味するため、悪いと考えられています。


ただし、モンキーパッチは、既存のオブジェクトまたはクラスを変更するのではなく、デコレータ内のメンバーにパッチが適用されたオブジェクトの新しいバージョンを作成する場合に便利です。
aoeu256

パッチを適用したメンバーの注釈を使用して、パッチのパッチに使用したデコレーターをパッチを適用したメンバーに格納できます。undoメソッドを使用して、関数オブジェクトの元に戻すことができる新しいバージョンを作成する、元に戻すことができるデコレーターがあるとします。デコレーターには、取り消し可能なデコレーターを指すパッチャーフィールドを配置できます。
aoeu256

5

モンキーパッチとは、実行時にクラス内の既存のクラスまたはメソッドを再度開き、動作を変更することです。これは慎重に使用する必要があります。または、本当に必要な場合にのみ使用する必要があります。

Pythonは動的プログラミング言語であるため、クラスは変更可能であるため、クラスを再度開いて変更したり、置き換えたりすることができます。


1

モンキーパッチとは何ですか?モンキーパッチは、実行時にコードの一部の動作を動的に更新するために使用される手法です。

モンキーパッチを使用する理由 実際にソースコードを変更せずに、実行時にライブラリ、モジュール、クラス、またはメソッドの動作を変更または拡張できます。

まとめモンキーパッチは優れた手法であり、Pythonでこれを行う方法を学びました。ただし、前述したように、この方法には固有の欠点があり、慎重に使用する必要があります。

詳細については、[1]を参照してください:https : //medium.com/@nagillavenkatesh1234/monkey-patching-in-python-explained-with-examples-25eed0aea505

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.