파이썬에서 원숭이 패치하기

Jay Shaw 2023년6월21일
  1. Monkey Patching에서 동적 언어의 중요성
  2. Python에서 원숭이 패치 구현
  3. Python에서 단위 테스트를 위해 Monkey 패치 사용
  4. 결론
파이썬에서 원숭이 패치하기

사용자에서 데이터베이스로 데이터를 보내는 것과 같이 원하는 결과를 얻기 위해 코드 조각이 작성됩니다. 그러나 코드가 올바르게 실행되는지 또는 버그가 있는지 확인하는 것과 같이 테스트 단계에서 코드를 수정해야 합니다.

원숭이 패치는 코드의 기본 동작이 변경되도록 스텁 또는 유사한 코드 조각을 할당하는 프로세스입니다. 이 기사는 파이썬에서 원숭이 패치를 위한 다양한 방법에 초점을 맞출 것입니다.

Monkey Patching에서 동적 언어의 중요성

Python이 훌륭한 예인 동적 언어만 원숭이 패치에 사용할 수 있습니다. 모든 것을 정의해야 하는 정적 언어에서는 원숭이 패치가 불가능합니다.

예를 들어 원숭이 패치는 개체 설명을 변경하지 않고 런타임 중에 속성(메소드 또는 변수)을 추가하는 방법입니다. 이들은 소스 코드를 사용할 수 없는 모듈로 작업할 때 자주 사용되어 개체 정의를 업데이트하기 어렵습니다.

기존 객체나 클래스를 변경하는 대신 데코레이터 내부에 패치된 멤버를 사용하여 객체의 새 버전을 빌드하는 경우 Python의 Monkey 패치가 도움이 될 수 있습니다.

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 패치 사용

지금까지 Python의 원숭이 패치가 함수에서 실행되는 방법을 배웠습니다. 이 섹션에서는 Python을 사용하여 전역 변수를 원숭이 패치하는 방법을 살펴봅니다.

이 예제를 시연하기 위해 파이프라인이 사용됩니다. 파이프라인을 처음 접하는 독자에게는 기계 학습 모델을 훈련하고 테스트하는 과정입니다.

파이프라인에는 텍스트 또는 이미지와 같은 데이터를 수집하는 교육 모듈과 테스트 모듈의 두 가지 모듈이 있습니다.

이 프로그램이 하는 일은 데이터 디렉터리에서 여러 파일을 검색하기 위해 파이프라인을 만드는 것입니다. test.py 파일에서 프로그램은 단일 파일로 임시 디렉토리를 만들고 해당 디렉토리에서 파일 수를 검색합니다.

단위 테스트를 위한 파이프라인 교육

이 프로그램은 data 디렉토리에 저장된 두 개의 일반 텍스트 파일에서 데이터를 수집하는 파이프라인을 생성합니다. 이 프로세스를 다시 생성하려면 data 폴더가 저장된 상위 디렉터리에 pipeline.pytest.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입니다. 경로는 상위 디렉토리 데이터 내의 파일을 나타냅니다.

DATA_DIR = Path(__file__).parent / "data"

검색해야 하는 문자열 패턴인 하나의 매개변수를 사용하는 collect_files 함수가 생성됩니다.

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

코드의 첫 번째 줄은 pipelinepytest Python 라이브러리를 가져옵니다. 다음으로 test_collect_files라는 test 함수가 생성됩니다.

이 함수에는 임시 디렉토리를 가져오는 데 사용되는 temp_path 매개변수가 있습니다.

def test_collect_files(tmp_path):

파이프라인은 3개의 섹션(Given, When 및 Then)으로 나뉩니다.

기븐 내에서 temp_data_directory라는 이름의 새 변수가 생성됩니다. 이 변수는 data 디렉토리를 가리키는 임시 경로일 뿐입니다. 이는 tmp_path 고정물이 경로 개체를 반환하기 때문에 가능합니다.

다음으로 데이터 디렉토리를 생성해야 합니다. mkdir 기능을 사용하여 수행되며 이 경로 내의 모든 상위 디렉토리가 생성되도록 상위를 true로 설정합니다.

다음으로 이 디렉토리 내에 file1.txt라는 단일 텍스트 파일이 생성된 다음 touch 방법을 사용하여 생성됩니다.

데이터 디렉터리 내의 파일 수를 반환하는 새 변수 expected_length가 생성됩니다. 데이터 디렉토리 내에 하나의 파일만 예상되므로 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 변수에 할당됩니다.

파일 수는 목록의 길이를 반환하고 변수 actual_length에 저장되는 len(files)를 사용하여 가져옵니다.

files = pipeline.collect_files("*.txt")
actual_length = len(files)

Then 섹션에서 assert 문은 expected_lengthactual_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입니다. 이것은 이 시점에서 프로그램이 임시 디렉토리를 사용하지 않기 때문에 발생합니다. 대신 프로그램 시작 시 생성된 실제 데이터 디렉토리를 사용합니다.

데이터 디렉터리 안에 두 개의 파일이 생성되었고 임시 디렉터리 안에는 하나의 파일만 생성되었습니다. test.py 코드는 단일 파일만 저장된 임시 디렉터리 내부의 파일을 확인하기 위해 작성되지만 대신 코드로 인해 원래 디렉터리로 돌아가게 됩니다.

그렇기 때문에 expected_length 변수에 1 값이 지정되지만 actual_length와 비교하면 테스트가 실패합니다.

원숭이 패치를 사용하여 이 문제를 해결하기 위해 전역 변수를 패치할 수 있습니다.

먼저 다음과 같이 monkeypatch 매개변수를 collect_files 함수에 추가해야 합니다.

def test_collect_files(tmp_path, monkeypatch):

이제 해야 할 일은 전역 변수가 원숭이 패치를 사용하여 패치된다는 것입니다.

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의 원숭이 패치에는 파이프라인 모듈 내부의 DATA_DIR 변수에 새 값을 할당할 수 있는 setattr 기능이 있습니다. 그리고 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의 원숭이 패치에 초점을 맞추고 원숭이 패치의 실제 사용에 대해 자세히 설명합니다. 독자는 원숭이 패칭을 쉽게 구현할 수 있습니다.

관련 문장 - Python Unittest