문서의 이전 판입니다!
목차
Embedded Python: Boost Python
boost::python은 파이썬 확장(extended python)에 보다 초점을 맞춰져 있는 라이브러리입니다. 완전하게 임베딩을 완전히 지원하지는 않는다고 합니다1). 지원하지 않는 부분들은 직접 C API를 활용하여야 합니다. 그렇지만 그것만으로도 훨씬 더 생산성을 높일 수 있습니다. 사용할 만한 가치는 충분합니다.
Boost Python으로 개선되는 점
자동 레퍼런스 카운팅
PyObject의 레퍼런스 카운팅 개념은 사실 boost::shared_ptr의 개념과 매우 흡사합니다. 다만 C 언어의 한계로 인해 자동으로 객체의 생성, 소멸시 객체가 자동으로 호출되는 함수가 지원되지 않아 결국 그러한 카운팅을 프로그래머가 직접 해야 합니다. 매우 성가시고 불편합니다.
boost::python을 사용하면 boost::python::object
라는 PyObject
의 래퍼를 사용할 수 있습니다. 이를 통해 더이상 프로그래머가 까다로운 레퍼런스 카운팅을 신경쓰지 않아도 됩니다. 변수의 범위를 벗어나면 자동으로 카운터를 내려주고, 다른 레퍼런스에 대입되면 카운터를 올려줍니다.
자동 레퍼런스 카운팅 테스트
boost 라이브러리야 믿고 쓰면 되는데, 그래도 좀 궁금하더군요. 진짜 자동으로 레퍼런스 카운팅 되는지. 진짜 동작하는지 정말 확인해 보고자 다음과 같은 실험을 해 보았습니다. 좀 바보 같아도 돌다리도 두들겨 보라고.
- Py_INCREF, Py_DECREF는 매크로입니다. 헤더에 선언되어 있으므로 소스를 재컴파일하지 않아도 헤더 파일에 쓰기 권한만 있으면 접근하여 수정할 수 있습니다. 그러므로 여기 접근하여 살짝 변경해 호출이 되는 것 확인할 수 있도로 합니다.
- boost::python::object를 생성하여 레퍼런스 카운트를 직접 확인해 봅니다.
- 종료될 때 명시적으로 Py_DECREF를 호출하지 않아도 호출되는지를 확인합니다.
우선 원래의 파이썬 소스의 헤더를 살짝 변경해 보았습니다.
/* object.h */ #include <stdio.h> #define Py_INCREF(op) do { printf("Py_INCREF\n"); ( \ // do-while 삽입 후 'Py_INCREF' 문자열 출력 _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA \ ((PyObject*)(op))->ob_refcnt++); } while(0) #define Py_DECREF(op) \ do { \ if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA \ --((PyObject*)(op))->ob_refcnt != 0) \ _Py_CHECK_REFCNT(op) \ else { printf("_Py_Dealloc\n"); \ // else에 {} 삽입 후 _Py_Dealloc' 문자열 출력 _Py_Dealloc((PyObject *)(op)); } \ } while (0)
이렇게 코드를 변경해 두면, 어느 시점에서 레퍼런스 카운터가 늘어나면 “Py_INCREF
” 문자열이 출력될 것이고, 레퍼런스 카운터가 0이 되면 “_Py_Dealloc
” 문자열이 출력될 것입니다. 그러면 소스 코드를 작성해 봅니다.
- boost_pyhthon.cpp
#include <boost/python.hpp> #include <Python.h> #include <iostream> int main(int argc, char** argv) { Py_Initialize(); //PyObject* naive = PyString_FromString("naive_string"); boost::python::object bpobj( boost::python::handle<>( PyString_FromString("boost::python handling object"))); boost::python::object bpobj_another = bpobj; //if (naive) // Py_XDECREF(naive); Py_Finalize(); return 0; }
boost::python이 생성하는 객체를 하나 만들고, 다른 객체와 자원을 공유해 본 다음 프로그램을 종료합니다. 위 코드에서 우리가 직접 관리하는 PyObject 레퍼런스는 주석 처리를 하였습니다. 결과를 비교해 볼 수 있을 것입니다. 이 코드를 실행하면 다음과 같은 결과가 나옵니다.
Py_INCREF Py_INCREF _Py_Dealloc
첫번째 Py_INCREF
는 문자열 하나가 생성되면서 객체의 카운트가 1이 될 때 출력된 것이라 추측할 수 있습니다. 두번째 Py_INCREF
는 boost::python::object끼리 대입이 되면서 레퍼런스 카운트가 올라간 것이죠. 마지막에 예상대로 객체가 해제되면서 _Py_Dealloc
이 불립니다. 확인이 되었습니다.
Try - Catch를 이용한 예외 처리
C API를 사용하는 경우, 모든 값에 대해 에러 체크를 명시적으로 수행해야 했다. 이렇게 프로그래밍을 짜면 if ~ else의 철장벽이 겹겹이 쌓이게 된다. 2~3중으로 쌓인 if 문 안에서 에러가 나는 경우, 안전하게 에러 처리를 하기가 너무 어렵고, 어쩔 수 없이 goto 문에 의지하는 경우도 종종 발생한다.
boost::python에서는 try - catch 명령을 사용해 예외 처리를 할 수 있다.
if ( Py_SomeFunc != NULL ) { // 운이 좋다면 } else { // 여기서 자원 회수를 비롯한 에러 처리를 해야 한다. }
이렇게.
try { ... } catch (boost::python::error_already_set const &) { // 에러 처리 }
보다 편리한 객체 호출 및 리턴 값
객체 선언 값 추출.