[python] Monkey Patching (몽키 패칭)

gunny·2025년 2월 13일
0

Python

목록 보기
33/35

Monkey Patching

  • 기존 함수 객체를 새로운 함수 객체로 대체하여 해당 함수가 새로운 함수 객체를 가르키도록 만드는 것
  • 런타임 중에 모듈의 메서드, 속성을 동적으로 수정하는 기법
  • 이 기법을 사용하면 원래 코드(base code)를 직접 수정하지 않고도 동작을 변경할 수 있지만, 예측하기 어려운 부작용을 초래할 수 있어 주의 필요하다.
  • 주로 테스트 목적 등에서 특정 함수를 대체할 때 사용된다.

Monkey Patching 예시 (1)

  • 기존 클래스 메서드를 Monkey Patching 을 이용해 동적으로 변경하는 간단한 예

# 원래 클래스 정의
class Person:
	def greet(self):
    	return "Hello!"
        
# 기존 메서드 실행
p = Person()
print(p.greet()) # 출력 Hello!

# 몽키 패칭을 이용한 새로운 메서드 정의
def new_greet(self):
	return "Hi, there! I am patched."
    

# 기존 greet 메서드를 새로운 new_greet로 교체
Person.greet = new_greet

# 변경된 greet 메서드 실행
print(p.greet()) # 출력 : Hi, there! I am patched.
  • 원래 Person 클래스의 greet() 메서드는 "Hello!"를 반환한다.
  • 하지만 실행 중에 greet() 메서드를 new_greet()로 바꾸면, 새로운 함수로 동작하게 된다.
  • 이제 p.greet()를 실행하면 새로운 메시지가 출력된다.

Monkey Patching 예시 (2)

  • 테스트 환경에서 특정 라이브러리 함수의 동작을 임시로 변경하는 경우

requests 라이브러리 패칭 (API 호출 테스트)

  • API 요청을 보내는 코드를 테스트할 때, 실제 네트워크 요청을 보내지 않고 응답을 조작할 수 있음
import requests

#원래 requests.get() 함수의 동작
def original_request():
	response = requests.get("https://example.com")
    return response.status_code

print(original_request()) # 실제 사이트 요청 -> 200(성공) 또는 기타 상태 코드

# Monkey Patching 적용
def fake_request(url):
	class FakeResponse:
    	status_code = 200 # 항상 200을 반환하는 가짜 응답
    return FakeRespoinse()
    
# requests.get()을 fake_requests로 교체
requests.get = fake_request

# 이제 원래 요청을 실행해도 가짜 응답이 반환됨
print(original_request()) # 출력 : 200
  • requests.get() 을 직접 호출하면 실제 네트워크 요청이 발생합니다.
  • 하지만 몽키 패칭을 통해 requests.getfake_request 함수로 교체하면, 네트워크 요청 없이도 테스트가 가능하다.
    이를 활용하면 API 요청을 테스트할 때 불필요한 네트워크 호출을 막고 응답을 직접 조작할 수 있다.

Monkey Patching 예시 (3)

  • 플러그인 시스템에서는 기본 동작을 유지하면서 특정 기능을 동적으로 변경해야 하는 경우가 많다.
  • 몽키 패칭을 이용하면 애플리케이션 실행 중에 특정 기능을 변경하거나 확장할 수 있다.

기본 로깅 시스템이 있고, 플러그인을 통해 새로운 로깅 기능을 추가하는 방식


# 원래 시스템 코드 (기본 로거)
class Logger:
	def log(self, message):
    	print(f"[LOG] {message}")

# 기존 로깅 시스템 사용
logger = Logger()
logger.log("Hello, world!") # 출력 : [LOG] Hello, world! 
  • 기본적으로 Logger.log()는 단순한 텍스트 로그만 출력한다.

이제 플러그인을 이용해 동작 중 로그를 파일에도 저장하도록 변경해 본다.

# 플러그인 적용 - 기존 log 메서드를 확장하는 새로운 기능

def log_with_file(self, message):
	# 원래 콘솔 출력 기능
    print(f"[LOG] {message}")
    
	# 추가 기능 : 로그를 파일에 저장
    def open("log.txt", "a") as file:
    	file.write(f"{message}\n")

# 몽키 패칭을 사용해 기존 log 메서드 변경
Logger.log = log_with_file

#새로운 기능이 적용된 로거 실행
logger.log("This is a test log.")

# 출력 : [LOG] This is a test log.
# log.txt 파일에 "This is a test log." 저장됨

Monkey Patching 예시 (4)

  • AI 모델에서 특정 상황에서 추가 데이터 로깅하는 몽키 패칭 예제로, AI 모델의 예측 결과가 특정 임계값을 넘으면 자동으로 추가 데이터를 로깅하도록 몽키 패칭을 적용한다.

<기존 AI 모델 코드>

import random

class AIModel:
	def predict(self, input_data):
    	# 가짜 예측 점수 (0-1 사이의 랜덤값)
        return random.random()
        
# 기존 모델 동작
model = AIModel()
print(model.predict("input")) # 예 : 0.756

이 모델은 단순히 0~1 사이의 랜덤 숫자를 반환하는 가짜 모델

<몽키 패칭 적용 - 특정 임계값 이상이면 자동 로깅>


#  원래 predict 메서드 백업
original_predict = AIModel_predict

# 새로운 로깅 기능 추가
def predict_with_logging(self, input_data):
	result = original_predict(self, input_data) #  기존 예측 수행
    
    if result > 0.8:
    	with open("high_confidence_predictions.txt", "a" as file:
        	file.write(f"Input : {input_data}, Prediction: {result}\n")
     	print(f"Logged high confidence prediction: {result}")
        
     return result
     
 # 몽키 패칭 적용
 AIModel.predict = predict_with_logging
 
 # 테스트 실행
 print(model.predict("test input") # 특정 조건에서 로그 저장
        

[변경된 기능]

  • 기존 predict() 기능은 유지하면서 예측 점수가 0.8 이상이면 자동으로 데이터를 파일에 기록

[활용 예시]

  • AI 모델의 신뢰도 높은 예측만 로그 저장하여 추가 분석
  • 특정 상황에서만 AI 모델의 데이터를 추가적으로 수집

Monkey Patching 예시 (5)

  • 게임 엔진에서 특정 이벤트 발생 시 새로운 기능을 추가하는 몽키 패칭 예제로, 게임 엔진에서 플레이어가 보스를 처치할 때 추가 보상을 지급하는 기능을 동적으로 추가

<기존 게임 엔진 코드>

class Game:
	def boss_defeated(self, player):
    	print(f"{player} has defeated the boss!")
        
# 기본 게임 동작
game = Game()
game.boss_defeated("Player1")

<몽키 패칭 적용 - 보스 처치 시 추가 보상 지급>


# 원래 메서드 백업 
origin_boss_defeated = Game.boss_defeated

# 새로운 기능 추가
def boss_defeated_with_reward(self, player):
	original_Boss_defeated(self, player) # 기본 동작 유지
    print(f"{player} received a legendary item as a reward!") # 추가 기능
    

# 몽키 패칭 적용
Game.boss_defeated = boss_defeated_with_reward

# 테스트 실행
game.boss_defeated("Player1")

# 출력:
# Player1 has defeated the boss!
# Player1 received a legendary item as a reward!

[변경된 기능]

  • 기존 보스 처치 이벤트를 유지하면서 보스 처치 후 자동으로 추가 보상 지급

[활용 예시]

  • 특정 이벤트 발생 시 동적으로 새로운 기능 추가 가능
  • 확장 가능한 모드(Mod) 시스템 구현 가능
  • 게임 내 특정 조건 충족 시 이벤트 트리거 기능 추가 가능

Monkey Patching 장점

  • 몽키패칭을 활용한 플러그인의 시스템은 원본 코드의 수정 없이 동적으로 기능을 확장 가능하다.
  • 플러그인 로드 시 즉시 반영하여 애플리케이션 실행 중에 동작 변경이 가능하다.

즉, 기존 기능을 유지하면서 추가 기능만 구현 가능하다.
애플리케이션 실행 중에 새로운 기능을 추가할 수 있는 것이 장점

Monkey Patching 단점

  • 너무 남용하면 원래 동작을 예측하기 어려워짐

Monkey Patching 활용 예시

  • 테스트 환경에서 특정 함수의 동작을 변경 (API 요청, DB 호출 등)
  • 라이브러리의 특정 기능을 임시로 수정 (디버깅, 성능 개선 등)
  • 동작 중 일부 기능을 동적으로 변경 (플러그인 시스템 등)
  • 게임에서 특정 이벤트 발생 시 새로운 기능 적용
profile
꿈꾸는 것도 개발처럼 깊게

0개의 댓글