본문 바로가기

진리는어디에/Python

Python embedding 3 - 캐싱 된 모듈 다시 로드 하기

Python embedding 1 - Overview
Python embedding 2 - Calling Python function from C/C++ Appilcation

얼마 전 C++코드에서 파이썬 모듈을 로드해 사용 할 수 있는 임베딩(Embedding)이라는 기술에 대해서 포스팅 한적이 있습니다(http://kukuta.tistory.com/83). 그 때 제가 임베딩을 사용한 목적은 '로직이 변경 되었을 때, 재 컴파일이 필요 없이 동적인 로직의 변경이 가능하도록 한다'였습니다만, 파이썬에서는 모듈이 한번 로드 되면 캐싱 되는 기능을 가지고 있더군요.

모듈이 캐싱되어 있다는 것이 보통의 경우에는 성능향상등 각종 이로운 현상을 만들어 주지만, 이번 제가 하려는 경우에는 그다지 도움이 되지 않더군요. 모듈을 변경하고 다시 저장한 후에 해당 모듈을 호출해 주는 C++코드를 호출하면 변경된 모듈이 반영되는 것이 아니라, 이전에 캐싱되어 있는 모듈이 항상 작동하더군요. 결국 재컴파일과 재시작이 필요 없이 로직을 변경 할 수 있는 어플리케이션의 제작은 물건너 가는 듯 싶었습니다.

하지만 뜻이 있는 곳에 길이 있다고, '파이썬 마을(http://bbs.python.or.kr )'라는 곳에서 답변을 찾게 되었습니다(http://bbs.python.or.kr/viewtopic.php?t=23522).
 
간단하게 요약을 하자면 :
 파이썬은 기본적으로 로드된 모듈은 sys.modules 라는 딕셔너리에 이름:경로 형태로 저장 캐싱을 한다고 합니다. 임베딩 사용시, 로드된 객체들을 메모리에서 삭제하기 위해 Py_DECREF()를 이용해 Reference 카운트를 낮추게 되는데, 이 카운트가 0이 되더라도 실질적으로 sys.modules에서는 로딩된 모듈이 언로드 되지 않는다그는 것이 문제의 원인이었습니다.

파이썬 코드에서라면 sys.modules에 직접적으로 접근하여 del 함수 등으로 삭제를 해버리면 되겠지만, 임베딩, 즉 C++에서는 PySys_GetObject() 라는 재밌는 함수[각주:1]를 이용해서 modules에 접근 할 수 있습니다 :

#include <Python.h>

...
PyObject* pSysModules = PySys_GetObject("modules");

파라메터는 특정한 모듈을 가리키는 것이 아니라 문자열 그대로 "modules"를 사용 합니다. sys.modules에 접근한다는 의미입니다. 다른 sys 하위 모듈을 가리키기 위해서는 다른 문자열을 넘겨 주면 되겠지요. 일단 sys.modules에는 접근 했습니다. 그렇다면 이제 해야 할 일은 리로드하고자 하는 모듈(좀 더 정확히 말하자면 리로드를 정상적으로 수행하기 위해 삭제 해야 할 모듈)을 찾아내는 것입니다.
 
모듈을 삭제하는 함수는 PyDict_DelItem() 입니다. 모듈을 삭제하기 위해 특화 되어 있는 함수라기 보다는, 딕셔너리에 있는 데이터를 지워주는 역할을 하는 함수지요. 이 함수는 딕셔너리 함수와 지워질 데이터의 키, 이렇게 두 개의 PyObject 객체를 인자로 받습니다.  위의 코드와 연결 하여 아래의 코드를 살펴 보도록 하지요 :

if(NULL != pSysModules) {
    PyObject* pModuleName = PyString_FromString("NameOfModule"); //삭제하고자 하는 모듈의 이름
    // ..에러 처리 코드 생략..
    int ret = PyDict_DelItem(pSysModules, pModuleName );
    // ..if OK, ret is 0, else -1
}

모듈의 이름을 PyObject로 변환하기 위해서 PySring_FromString함수를 썼다는 사실에 대해 별다른 설명은 하지 않도록 하겠습니다. 궁금하신 분들은 이전의 포스트를 참고 해 주세요. 여기서 주의 깊게 봐야 할 것은 :

  • PySys_GetObject()함수를 이용해 전역적으로 모듈이 등록 되어 있는 딕셔너리를 얻어 왔고,
  • 딕셔너리에서 캐싱 없이 완전한 리로드를 원하는 객체를 지웠다.

는 것입니다. 지워진 객체에 대해서 다시 로딩 요청이 들어 오더라도 이번에는 변경된 모듈을 반영 하겠지요.


Python embedding 4 - PyErr_Print() 를 파일로 남기기
  1. 파이썬 공식 문서에는 아무리 찾아봐도 PySys_GetObject() 함수에 대한 문서나 설명이 없습니다(최소한 2.5버젼까지의 문서에는요). 다만 다음의 링크 http://bugs.python.org/issue1246 를 통해서 해당 함수의 존재 유무는 판단 할 수 있습니다. [본문으로]
유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!