사용자 도구

사이트 도구


project:embeddedpythonboostpython

문서의 이전 판입니다!


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::python::object만으로도 코드는 무척 간결해질 수 있습니다.

자동 레퍼런스 카운팅 테스트

boost 라이브러리야 믿고 쓰면 되는데, 그래도 좀 궁금하더군요. 진짜 자동으로 레퍼런스 카운팅 되는지. 진짜 동작하는지 정말 확인해 보고자 다음과 같은 실험을 해 보았습니다. 좀 바보 같아도 돌다리도 두들겨 보라고. 8-)

  1. Py_INCREF, Py_DECREF는 매크로입니다. 헤더에 선언되어 있으므로 소스를 재컴파일하지 않아도 헤더 파일에 쓰기 권한만 있으면 접근하여 수정할 수 있습니다. 그러므로 여기 접근하여 살짝 변경해 호출이 되는 것 확인할 수 있도로 합니다.
  2. boost::python::object를 생성하여 레퍼런스 카운트를 직접 확인해 봅니다.
  3. 종료될 때 명시적으로 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 &) {
 // 에러 처리
}

보다 편리한 객체 호출 및 리턴 값

객체 선언 값 추출.

파이썬 기본 자료형 다루기

Type Casting

List

Tuple

Dictonary

project/embeddedpythonboostpython.1394100091.txt.gz · 마지막으로 수정됨: 2014/10/09 21:23 (바깥 편집)

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki