Python でのモンキー パッチ
ユーザーからデータベースにデータを送信するなど、目的の結果を達成するためのコードが記述されます。 ただし、コードが正しく実行されるかどうかやバグがないかどうかのチェックなど、テスト フェーズ中にコードを微調整する必要があります。
モンキー パッチは、スタブまたは同様のコードを割り当てて、コードの既定の動作を変更するプロセスです。 この記事では、Python でモンキー パッチを適用するさまざまな方法に焦点を当てます。
モンキーパッチにおける動的言語の重要性
Python が優れた例である動的言語のみが、モンキー パッチに使用できます。 すべてを定義する必要がある静的言語では、モンキーパッチは不可能です。
例として、モンキー パッチは、オブジェクトの説明を変更するのではなく、実行時に属性 (メソッドまたは変数) を追加する方法です。 これらは、ソース コードが利用できないモジュールを操作するときに頻繁に使用され、オブジェクト定義の更新が困難になります。
既存のオブジェクトまたはクラスを変更するのではなく、パッチを適用したメンバーをデコレーター内に使用して新しいバージョンのオブジェクトをビルドする場合、Python でのモンキー パッチが役立ちます。
Python でモンキー パッチを実装する
このプログラムでは、Python でのモンキー パッチのデモンストレーションを行います。 実行時にモンキー パッチを適用するために、新しい装飾されたメソッドにメソッドが割り当てられます。
コード:
import pandas as pd
def word_counter(self):
"""This method will return all the words inside the column that has the word 'tom'"""
return [i for i in self.columns if "tom" in i]
pd.DataFrame.word_counter_patch = word_counter # monkey-patch the DataFrame class
df = pd.DataFrame([list(range(4))], columns=["Arm", "tomorrow", "phantom", "tommy"])
print(df.word_counter_patch())
出力:
"C:\Users\Win 10\main.py"
['tomorrow', 'phantom', 'tommy']
Process finished with exit code 0
コードを分解して、Python でのモンキー パッチ適用を理解しましょう。
コードの最初の行は、プログラムでデータ フレームを作成するために使用されるライブラリ pandas
をインポートします。
import pandas as pd
次に、Python 3 では関数とバインドされていないメソッドの区別はほとんど役に立たないため、バインドされておらず、クラス定義のスコープ外で自由に存在するメソッド定義が確立されます。
def word_counter(self):
"""This method will return all the words inside the column that has the word 'tom'"""
return [i for i in self.columns if "tom" in i]
pd.Dataframe.word_counter
を使用して新しいクラスが作成されます。 次に、新しく作成されたクラスがメソッド word_counter
にアタッチされます。
それが何をするかというと、メソッド word_counter
にデータ フレーム クラスをモンキー パッチすることです。
pd.DataFrame.word_counter_patch = word_counter # monkey-patch the DataFrame class
メソッドがクラスにアタッチされたら、単語を格納するために新しいデータ フレームを作成する必要があります。 このデータ フレームには、オブジェクト変数 df
が割り当てられます。
df = pd.DataFrame([list(range(4))], columns=["Arm", "tomorrow", "phantom", "tommy"])
最後に、データ フレーム df
を渡すことでモンキー パッチ クラスが呼び出され、出力されます。 ここで何が起こるかというと、コンパイラがクラス word_counter_patch
を呼び出すと、モンキー パッチがデータ フレームをメソッド word_counter
に渡します。
動的プログラミング言語ではクラスとメソッドをオブジェクト変数として扱うことができるため、Python のモンキー パッチは、他のクラスを使用するメソッドに適用できます。
print(df.word_counter_patch())
Python での単体テストに Monkey Patch を使用する
ここまでで、Python のモンキー パッチが関数に対してどのように実行されるかを学びました。 このセクションでは、Python を使用してグローバル変数にモンキー パッチを適用する方法について説明します。
この例を示すためにパイプラインが使用されます。 パイプラインを初めて使用する読者にとって、これは機械学習モデルをトレーニングしてテストするプロセスです。
パイプラインには、テキストや画像などのデータを収集するトレーニング モジュールと、テスト モジュールの 2つのモジュールがあります。
このプログラムが行うことは、データ ディレクトリ内の複数のファイルを検索するパイプラインが作成されることです。 test.py
ファイルでは、プログラムは 1つのファイルを含む一時ディレクトリを作成し、そのディレクトリ内のファイルの数を検索します。
単体テスト用のパイプラインをトレーニングする
このプログラムは、ディレクトリ data
内に格納された 2つのプレーン テキスト ファイルからデータを収集するパイプラインを作成します。 このプロセスを再現するには、data
フォルダが保存されている親ディレクトリに pipeline.py
および test.py
Python ファイルを作成する必要があります。
pipeline.py
ファイル:
from pathlib import Path
DATA_DIR = Path(__file__).parent / "data"
def collect_files(pattern):
return list(DATA_DIR.glob(pattern))
コードを分解してみましょう:
pathlib
は Path
としてインポートされ、コード内で使用されます。
from pathlib import Path
これは、データ ファイルの場所を格納するグローバル変数 DATA_DIR
です。 Path
は、親ディレクトリ data 内のファイルを示します。
DATA_DIR = Path(__file__).parent / "data"
関数 collect_files
が作成され、検索に必要な文字列パターンを 1つのパラメーターとして受け取ります。
DATA_DIR.glob
メソッドは、データ ディレクトリ内のパターンを検索します。 メソッドはリストを返します。
def collect_files(pattern):
return list(DATA_DIR.glob(pattern))
この時点でグローバル変数が使用されているため、メソッド collect_files
を正しくテストするにはどうすればよいでしょうか?
パイプライン クラスをテストするためのコードを格納するために、新しいファイル test.py
を作成する必要があります。
test.py
ファイル:
import pipeline
def test_collect_files(tmp_path):
# given
temp_data_directory = tmp_path / "data"
temp_data_directory.mkdir(parents=True)
temp_file = temp_data_directory / "file1.txt"
temp_file.touch()
expected_length = 1
# when
files = pipeline.collect_files("*.txt")
actual_length = len(files)
# then
assert expected_length == actual_length
コードの最初の行は、pipeline
および pytest
Python ライブラリをインポートします。 次に、test_collect_files
という名前の test
関数が作成されます。
この関数には、一時ディレクトリを取得するために使用されるパラメーター temp_path
があります。
def test_collect_files(tmp_path):
パイプラインは、Given、When、Then の 3つのセクションに分かれています。
Given 内で、temp_data_directory
という名前の新しい変数が作成されます。これは、data
ディレクトリを指す一時パスにすぎません。 これは、tmp_path
フィクスチャがパス オブジェクトを返すため可能です。
次に、データ ディレクトリを作成する必要があります。 これは mkdir
関数を使用して行われ、このパス内のすべての親ディレクトリが確実に作成されるように、親が true に設定されます。
次に、このディレクトリ内に file1.txt
という名前の単一のテキスト ファイルが作成され、touch
メソッドを使用して作成されます。
データ ディレクトリ内のファイル数を返す新しい変数 expected_length
が作成されます。 データ ディレクトリ内にはファイルが 1つしかないと予想されるため、値 1
が与えられます。
temp_data_directory = tmp_path / "data"
temp_data_directory.mkdir(parents=True)
temp_file = temp_data_directory / "file1.txt"
temp_file.touch()
expected_length = 1
ここで、プログラムは When セクションに入ります。
pipeline.collect_files
関数が呼び出されると、パターン *.txt
を持つファイルのリストが返されます。ここで、*
は文字列です。 次に、変数 files
に割り当てられます。
ファイルの数は、リストの長さを返す len(files)
を使用して取得され、変数 actual_length
内に保存されます。
files = pipeline.collect_files("*.txt")
actual_length = len(files)
Then セクションの assert
ステートメントは、expected_length
が actual_length
と等しくなければならないことを示しています。 assert
は、与えられたステートメントが真かどうかをチェックするために使用されます。
これで、パイプラインをテストする準備が整いました。 ターミナルに移動し、次のコマンドを使用して test.py
ファイルを実行します。
pytest test.py
テストを実行すると、失敗します。
assert expected_length == actual_length
E assert 1 == 0
test.py:23: AssertionError
=============================== short test summary info ============================================
FAILED test.py::test_collect_files - assert 1 == 0
期待される長さが 1
であるために起こりますが、実際には 2
です。 これは、この時点でプログラムが一時ディレクトリを使用していないために発生します。 代わりに、プログラムの開始時に作成された実際のデータ ディレクトリを使用します。
データ ディレクトリ内には 2つのファイルが作成されましたが、一時ディレクトリ内には 1つのファイルのみが作成されました。 何が起こるかというと、test.py
コードは、1つのファイルのみが格納されている一時ディレクトリ内のファイルをチェックするように記述されていますが、代わりに、コードによって元のディレクトリに戻されます。
そのため、expected_length
変数には 1
の値が与えられますが、actual_length
と比較すると、テストは失敗します。
グローバル変数にパッチを適用して、モンキー パッチを使用してこの問題を解決できます。
最初に、パラメーター monkeypatch
を次のように関数 collect_files
に追加する必要があります。
def test_collect_files(tmp_path, monkeypatch):
ここで行う必要があるのは、monkey パッチを使用してグローバル変数にパッチを適用することです。
def test_collect_files(tmp_path, monkeypatch):
# given
temp_data_directory = tmp_path / "data"
temp_data_directory.mkdir(parents=True)
temp_file = temp_data_directory / "file1.txt"
temp_file.touch()
monkeypatch.setattr(pipeline, "DATA_DIR", temp_data_directory) # Monkey Patch
expected_length = 1
Python のモンキー パッチには関数 setattr
があり、パイプライン モジュール内の DATA_DIR
変数に新しい値を割り当てることができます。 そして、DATA_DIR
の新しい値が temp_data_directory
に割り当てられます。
テストが再度実行されると、グローバル変数にパッチが適用され、代わりに temp_data_directory
が使用されるため、テストに合格します。
platform win32 -- Python 3.10.5, pytest-7.1.2, pluggy-1.0.0
rootdir: C:\Users\Win 10\PycharmProjects\Monkey_Patch
collected 1 item
test.py . [100%]
================================== 1 passed in 0.02s ====================================
まとめ
この記事では、Python でのモンキー パッチに焦点を当て、モンキー パッチの実際の使用法について詳しく説明します。 読者はモンキー パッチを簡単に実装できます。