C++에서 Python 객체를 다루는 방법
C나 C++ 코드 안에서 Python 객체를 직접 생성하고 조작해야 하는 경우가 있습니다. 이럴 때 Cython을 활용하면 복잡한 C API 호출 없이도 손쉽게 Python 객체를 래핑(wrapping)하고 임베딩(embedding)할 수 있습니다.
아래 예제는 간단한 Python 객체를 정의하고, 이를 C 코드에서 호출하는 전체 과정을 보여줍니다. 여기서는 .c 파일 기준으로 설명하지만, C++도 거의 동일한 절차를 따릅니다(컴파일 시 --cplus 옵션만 추가하면 됩니다).
1. Cython으로 Python 클래스 작성하기
먼저 pycls.pyx 파일에 데이터를 저장하고 출력하는 간단한 클래스를 작성합니다.
class PyClass(object):
def __init__(self):
self.data = []
def add(self, val):
self.data.append(val)
def __str__(self):
return "Data: " + str(self.data)
cdef public object createPyClass():
return PyClass()
cdef public void addData(object p, int val):
p.add(val)
cdef public char* printCls(object p):
return bytes(str(p), encoding='utf-8')여기서 핵심은 public 키워드입니다. 이 키워드를 붙인 함수는 Cython이 컴파일될 때 .h 헤더 파일에 함수 선언을 자동으로 생성해 주기 때문에, 외부 C/C++ 코드에서 바로 호출할 수 있습니다.
createPyClass()— Python 객체를 새로 생성해 반환합니다.addData()— 해당 객체의add()메서드를 호출해 값을 추가합니다.printCls()— 객체의 문자열 표현을 UTF-8로 인코딩된 C 문자열(char*)로 반환합니다.
2. Cython 컴파일로 소스 및 헤더 파일 생성
다음 명령어로 pyx 파일을 컴파일하면 소스 코드(.c)와 함수 선언(.h)이 포함된 두 개의 파일이 생성됩니다.
cython pycls.pyx # C용 cython --cplus pycls.pyx # C++용
3. C 메인 프로그램 작성하기
이제 Python 인터프리터를 초기화하고 위에서 만든 함수들을 호출하는 main.c 파일을 작성합니다.
#include "Python.h" // Python.h는 반드시 가장 먼저 포함해야 합니다.
#include "pycls.h" // Cython이 생성한 헤더 파일.
int main(int argc, char *argv[]){
Py_Initialize(); // Python 인터프리터 초기화
PyInit_pycls(); // 모듈 초기화 (Python 2에서는 initpycls() 사용)
PyObject *obj = createPyClass();
for(int i=0; i<10; i++){
addData(obj, i);
}
printf("%s\n", printCls(obj));
Py_Finalize();
return 0;
}Py_Initialize()로 Python 환경을 시작하고, PyInit_pycls()로 모듈을 등록한 뒤, 일반적인 C 함수를 호출하듯 Python 객체를 다룰 수 있습니다.
4. 컴파일 및 실행 결과 확인
컴파일에 필요한 플래그는 python3.5-config(또는 Python 2의 경우 python-config)에서 얻을 수 있습니다.
gcc pycls.c main.c -L$(python3.5-config --cflags) -I$(python3.5-config --ldflags) -std=c99
컴파일이 완료되면 실행 파일을 실행하여 Python 객체와 상호작용하는 결과를 확인할 수 있습니다.
./a.out Data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
정리
이 예제에서 핵심 역할을 한 것은 Cython과 public 키워드입니다. public 키워드 덕분에 .h 헤더 파일이 자동 생성되어 C/C++ 쪽에서 Python 함수를 손쉽게 호출할 수 있었습니다.
만약 헤더 파일 자동 생성 없이 진행하고 싶다면, Cython으로 일반 Python 확장 모듈만 컴파일한 뒤, 헤더 선언과 초기화 같은 보일러플레이트 코드를 직접 작성하는 방법도 가능합니다. 다만 public 옵션을 사용하는 편이 유지보수 측면에서 훨씬 간편합니다.