Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

C 확장 모듈을 사용해 Python 클래스에 C 메서드 연결하기

C 확장 모듈에서 새로운 Python 클래스를 정의하는 방법을 소개합니다. 이 기법에서는 클래스의 메서드를 C로 직접 구현하지만, 완성된 클래스는 Python 코드에서 일반 클래스처럼 인스턴스를 생성하고, 서브클래싱(subclassing)하고, 확장할 수 있습니다.

같은 원리를 응용하면 상속(inheritance)을 활용해 기존 Python 클래스를 C로 작성된 메서드로 확장하는 것도 가능합니다. 핵심은 PyClass_New 함수의 첫 번째 인수에 있습니다. 아래 예제에서는 이 인수를 NULL로 전달했는데, 이는 새로 만드는 클래스에 기반(base) 클래스가 없다는 것을 의미합니다. 반대로 이 자리에 기반 클래스들을 담은 튜플(tuple)을 전달하면, 클래스가 Python 소스 코드가 아닌 C 확장 모듈 안에서 정의되었더라도 일반적인 Python 상속 동작이 그대로 적용됩니다.

예제

#include <Python.h>

static PyObject* Foo_init(PyObject *self, PyObject *args)
{
    printf("Foo.__init__ called\n");
    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject* Foo_doSomething(PyObject *self, PyObject *args)
{
    printf("Foo.doSomething called\n");
    Py_INCREF(Py_None);
    return Py_None;
}

static PyMethodDef FooMethods[] =
{
    {"__init__", Foo_init, METH_VARARGS, "doc string"},
    {"doSomething", Foo_doSomething, METH_VARARGS, "doc string"},
    {0, 0},
};

static PyMethodDef ModuleMethods[] = { {0, 0} };

#ifdef __cplusplus
extern "C"
#endif
void initFoo( )
{
    PyMethodDef *def;

    /* 새 모듈과 클래스 객체 생성 */
    PyObject *module = Py_InitModule("Foo", ModuleMethods);
    PyObject *moduleDict = PyModule_GetDict(module);
    PyObject *classDict = PyDict_New( );
    PyObject *className = PyString_FromString("Foo");
    PyObject *fooClass = PyClass_New(NULL, classDict, className);

    PyDict_SetItemString(moduleDict, "Foo", fooClass);
    Py_DECREF(classDict);
    Py_DECREF(className);
    Py_DECREF(fooClass);

    /* 클래스에 메서드 추가 */
    for (def = FooMethods; def->ml_name != NULL; def++) {
        PyObject *func = PyCFunction_New(def, NULL);
        PyObject *method = PyMethod_New(func, NULL, fooClass);
        PyDict_SetItemString(classDict, def->ml_name, method);
        Py_DECREF(func);
        Py_DECREF(method);
    }
}

코드 흐름 살펴보기

위 예제의 동작 과정은 다음과 같습니다.

  1. Py_InitModuleFoo라는 이름의 모듈 객체를 생성합니다.
  2. PyDict_New로 클래스의 네임스페이스 역할을 할 빈 딕셔너리를 만듭니다.
  3. PyClass_New(NULL, classDict, className)을 호출해 기반 클래스 없이 새 클래스 객체를 생성합니다.
  4. 완성된 클래스를 모듈 딕셔너리에 등록하여 Python 쪽에서 접근할 수 있게 합니다.
  5. 메서드 테이블(FooMethods)을 순회하면서 PyCFunction_New로 C 함수를 감싸고, PyMethod_New로 클래스에 바인딩한 뒤 클래스 딕셔너리에 하나씩 추가합니다.

참고 사항

위 코드에 등장하는 PyClass_New, Py_InitModule, PyString_FromString 등은 Python 2 시절의 C API입니다. Python 3에서는 이러한 구형 클래스 API가 제거되었으므로, 실제 프로젝트에서는 PyType_FromSpec을 사용해 타입 객체를 정의하는 최신 방식을 사용해야 합니다. 그럼에도 이 예제는 C 레벨에서 Python 클래스가 내부적으로 어떻게 구성되는지를 보여주는 좋은 학습 자료가 됩니다.