다른 응용 프로그램에 파이썬 내장하기
************************************

이전 장에서는 파이썬을 확장하는 방법, 즉 C 함수의 라이브러리를 파이썬
에 연결하여 파이썬의 기능을 확장하는 방법에 관해 설명했습니다. 다른 방
법도 가능합니다: 파이썬을 내장시켜 C/C++ 응용 프로그램을 풍부하게 만들
수 있습니다. 내장은 C 나 C++가 아닌 파이썬으로 응용 프로그램의 일부 기
능을 구현하는 능력을 응용 프로그램에 제공합니다. 이것은 여러 목적으로
사용될 수 있습니다; 한 가지 예는 사용자가 파이썬으로 스크립트를 작성하
여 응용 프로그램을 필요에 맞게 조정할 수 있게 하는 것입니다. 일부 기능
을 파이썬으로 작성하기가 더 쉽다면 직접 사용할 수도 있습니다.

파이썬을 내장하는 것은 파이썬을 확장하는 것과 유사합니다만, 아주 같지
는 않습니다. 차이점은, 파이썬을 확장할 때 응용 프로그램의 주 프로그램
은 여전히 파이썬 인터프리터입니다. 반면에 파이썬을 내장하면 주 프로그
램은 파이썬과 아무 관련이 없습니다 --- 대신 응용 프로그램 일부에서 간
혹 파이썬 코드를 실행하기 위해 파이썬 인터프리터를 호출합니다.

그래서 파이썬을 내장한다면, 여러분은 자신의 메인 프로그램을 제공하게
됩니다. 이 메인 프로그램이해야 할 일 중 하나는 파이썬 인터프리터를 초
기화하는 것입니다. 최소한, "Py_Initialize()" 함수를 호출해야 합니다.
파이썬에 명령 줄 인자를 전달하는 선택적 호출이 있습니다. 그런 다음 나
중에 응용 프로그램의 어느 부분에서나 인터프리터를 호출할 수 있습니다.

인터프리터를 호출하는 방법에는 여러 가지가 있습니다: 파이썬 문장을 포
함하는 문자열을 "PyRun_SimpleString()"에 전달하거나, stdio 파일 포인터
와 파일명(에러 메시지에서의 식별만을 위해)을 "PyRun_SimpleFile()"에 전
달할 수 있습니다. 또한, 이전 장에서 설명한 저수준의 연산을 호출하여 파
이썬 객체를 만들고 사용할 수 있습니다.

더 보기:

  Python/C API reference manual
     파이썬의 C 인터페이스에 대한 자세한 내용은 이 매뉴얼에 있습니다.
     필요한 정보가 많이 있습니다.


매우 고수준의 내장
==================

파이썬을 내장하는 가장 간단한 형태는 매우 고수준의 인터페이스를 사용하
는 것입니다. 이 인터페이스는 응용 프로그램과 직접 상호 작용할 필요 없
이 파이썬 스크립트를 실행하기 위한 것입니다. 이것은 예를 들어 파일에
대해 어떤 연산을 수행하는 데 사용될 수 있습니다.

   #define PY_SSIZE_T_CLEAN
   #include <Python.h>

   int
   main(int argc, char *argv[])
   {
       PyStatus status;
       PyConfig config;
       PyConfig_InitPythonConfig(&config);

       /* 선택적이지만 권장됩니다 */
       status = PyConfig_SetBytesString(&config, &config.program_name, argv[0]);
       if (PyStatus_Exception(status)) {
           goto exception;
       }

       status = Py_InitializeFromConfig(&config);
       if (PyStatus_Exception(status)) {
           goto exception;
       }
       PyConfig_Clear(&config);

       PyRun_SimpleString("from time import time,ctime\n"
                          "print('Today is', ctime(time()))\n");
       if (Py_FinalizeEx() < 0) {
           exit(120);
       }
       return 0;

     exception:
        PyConfig_Clear(&config);
        Py_ExitStatusException(status);
   }

참고:

  "#define PY_SSIZE_T_CLEAN" was used to indicate that "Py_ssize_t"
  should be used in some APIs instead of "int". It is not necessary
  since Python 3.13, but we keep it here for backward compatibility.
  See 문자열과 버퍼 for a description of this macro.

"PyConfig.program_name" 설정은 파이썬 런타임 라이브러리에 대한 경로를
인터프리터에게 알리기 위해 "Py_InitializeFromConfig()"보다 먼저 호출되
어야 합니다. 다음으로, 파이썬 인터프리터는 "Py_Initialize()"로 초기화
되고, 날짜와 시간을 인쇄하는 하드 코딩된 파이썬 스크립트가 실행됩니다.
그런 다음, "Py_FinalizeEx()" 호출이 인터프리터를 종료하고 프로그램이
끝납니다. 실제 프로그램에서는 파이썬 스크립트를 다른 소스(아마도 텍스
트 편집기 루틴, 파일 또는 데이터베이스)에서 가져올 수 있습니다. 파일에
서 파이썬 코드를 얻는 것은 "PyRun_SimpleFile()" 함수를 사용하면 더 잘
할 수 있는데, 메모리 공간을 할당하고 파일 내용을 로드하는 번거로움을
덜어줍니다.


매우 고수준 내장을 넘어서: 개요
===============================

고수준 인터페이스는 응용 프로그램에서 임의의 파이썬 코드를 실행할 수
있는 능력을 제공하지만, 최소한 데이터 값을 교환하는 것이 꽤 번거롭습니
다. 그러길 원한다면 저수준의 호출을 사용해야 합니다. 더 많은 C 코드를
작성해야 하는 대신, 거의 모든 것을 달성할 수 있습니다.

파이썬을 확장하는 것과 파이썬을 내장하는 것은 다른 의도에도 불구하고
꽤 똑같은 활동이라는 점에 유의해야 합니다. 이전 장에서 논의된 대부분
주제는 여전히 유효합니다. 이것을 보시려면, 파이썬에서 C로의 확장 코드
가 실제로 하는 일을 생각해보십시오:

1. 데이터값을 파이썬에서 C로 변환하고,

2. 변환된 값을 사용하여 C 루틴으로 함수 호출을 수행하고,

3. 그 호출에서 얻은 데이터값을 C에서 파이썬으로 변환합니다.

파이썬을 내장할 때, 인터페이스 코드는 다음을 수행합니다:

1. 데이터값을 C에서 파이썬으로 변환하고,

2. 변환된 값을 사용하여 파이썬 인터페이스 루틴으로 함수 호출을 수행하
   고,

3. 그 호출에서 얻은 데이터 값을 파이썬에서 C로 변환합니다.

보시다시피, 데이터 변환 단계가 언어 간 전송의 다른 방향을 수용하기 위
해 단순히 교환됩니다. 유일한 차이점은 두 데이터 변환 간에 호출하는 루
틴입니다. 확장할 때는 C 루틴을 호출하고, 내장할 때는 파이썬 루틴을 호
출합니다.

이 장에서는 파이썬에서 C로 데이터를 변환하는 방법과 그 반대로 데이터를
변환하는 방법에 관해서는 설명하지 않습니다. 또한, 참조의 올바른 사용과
에러를 다루는 것을 이해하고 있다고 가정합니다. 이러한 측면은 인터프리
터를 확장하는 것과 다르지 않으므로, 이전 장에서 필요한 정보를 참조할
수 있습니다.


순수한 내장
===========

첫 번째 프로그램은 파이썬 스크립트에 있는 함수를 실행하는 것을 목표로
합니다. 매우 고수준의 인터페이스에 관한 절에서와같이, 파이썬 인터프리
터는 애플리케이션과 직접 상호 작용하지 않습니다 (하지만 다음 절에서 바
뀔 것입니다).

파이썬 스크립트에서 정의된 함수를 실행하는 코드는 다음과 같습니다:

   #define PY_SSIZE_T_CLEAN
   #include <Python.h>

   int
   main(int argc, char *argv[])
   {
       PyObject *pName, *pModule, *pFunc;
       PyObject *pArgs, *pValue;
       int i;

       if (argc < 3) {
           fprintf(stderr,"Usage: call pythonfile funcname [args]\n");
           return 1;
       }

       Py_Initialize();
       pName = PyUnicode_DecodeFSDefault(argv[1]);
       /* pName의 에러 검사가 생략되었습니다 */

       pModule = PyImport_Import(pName);
       Py_DECREF(pName);

       if (pModule != NULL) {
           pFunc = PyObject_GetAttrString(pModule, argv[2]);
           /* pFunc는 새로운 참조입니다 */

           if (pFunc && PyCallable_Check(pFunc)) {
               pArgs = PyTuple_New(argc - 3);
               for (i = 0; i < argc - 3; ++i) {
                   pValue = PyLong_FromLong(atoi(argv[i + 3]));
                   if (!pValue) {
                       Py_DECREF(pArgs);
                       Py_DECREF(pModule);
                       fprintf(stderr, "Cannot convert argument\n");
                       return 1;
                   }
                   /* 여기에서 pValue 참조를 훔칩니다: */
                   PyTuple_SetItem(pArgs, i, pValue);
               }
               pValue = PyObject_CallObject(pFunc, pArgs);
               Py_DECREF(pArgs);
               if (pValue != NULL) {
                   printf("Result of call: %ld\n", PyLong_AsLong(pValue));
                   Py_DECREF(pValue);
               }
               else {
                   Py_DECREF(pFunc);
                   Py_DECREF(pModule);
                   PyErr_Print();
                   fprintf(stderr,"Call failed\n");
                   return 1;
               }
           }
           else {
               if (PyErr_Occurred())
                   PyErr_Print();
               fprintf(stderr, "Cannot find function \"%s\"\n", argv[2]);
           }
           Py_XDECREF(pFunc);
           Py_DECREF(pModule);
       }
       else {
           PyErr_Print();
           fprintf(stderr, "Failed to load \"%s\"\n", argv[1]);
           return 1;
       }
       if (Py_FinalizeEx() < 0) {
           return 120;
       }
       return 0;
   }

이 코드는 "argv[1]"를 사용하여 파이썬 스크립트를 로드하고, "argv[2]"에
서 명명된 함수를 호출합니다. 정수 인자는 "argv" 배열의 남은 값들입니다
. 이 프로그램을 컴파일하고 링크하면 (완성된 실행 파일을 **call**이라고
부릅시다), 다음과 같은 파이썬 스크립트를 실행하는 데 사용합니다:

   def multiply(a,b):
       print("Will compute", a, "times", b)
       c = 0
       for i in range(0, a):
           c = c + b
       return c

그러면 결과는 다음과 같아야 합니다:

   $ call multiply multiply 3 2
   Will compute 3 times 2
   Result of call: 6

프로그램이 기능보다 상당히 큰 편이지만, 대부분 코드는 파이썬과 C 사이
의 데이터 변환과 에러 보고를 위한 것입니다. 파이썬 내장과 관련된 흥미
로운 부분은 다음처럼 시작합니다

   Py_Initialize();
   pName = PyUnicode_DecodeFSDefault(argv[1]);
   /* Error checking of pName left out */
   pModule = PyImport_Import(pName);

인터프리터를 초기화한 후, 스크립트는 "PyImport_Import()"를 사용하여 로
드됩니다. 이 루틴은 인자로 파이썬 문자열을 요구하는데,
"PyUnicode_DecodeFSDefault()" 데이터 변환 루틴을 사용하여 구성됩니다.

   pFunc = PyObject_GetAttrString(pModule, argv[2]);
   /* pFunc는 새로운 참조입니다 */

   if (pFunc && PyCallable_Check(pFunc)) {
       ...
   }
   Py_XDECREF(pFunc);

일단 스크립트가 로드되면, 우리가 찾고 있는 이름이
"PyObject_GetAttrString()"를 사용하여 검색됩니다. 이름이 존재하고, 반
환된 객체가 콜러블이면, 그것이 함수라고 안전하게 가정할 수 있습니다.
그런 다음 프로그램은 인자의 튜플을 일반적인 방법으로 구성하여 진행합니
다. 그런 다음 파이썬 함수 호출은 이렇게 이루어집니다:

   pValue = PyObject_CallObject(pFunc, pArgs);

함수가 반환되면, "pValue"는 "NULL"이거나 함수의 반환 값에 대한 참조를
포함합니다. 값을 검토한 후 참조를 해제해야 합니다.


내장된 파이썬을 확장하기
========================

지금까지 내장된 파이썬 인터프리터는 애플리케이션 자체의 기능에 액세스
할 수 없었습니다. 파이썬 API는 내장된 인터프리터를 확장함으로써 이것을
허용합니다. 즉, 내장된 인터프리터는 응용 프로그램에서 제공하는 루틴으
로 확장됩니다. 복잡하게 들리지만, 그렇게 나쁘지는 않습니다. 잠시 응용
프로그램이 파이썬 인터프리터를 시작한다는 것을 잊어버리십시오. 대신,
응용 프로그램을 서브 루틴의 집합으로 간주하고, 일반 파이썬 확장을 작성
하는 것처럼 파이썬에서 해당 루틴에 액세스할 수 있도록 연결 코드를 작성
하십시오. 예를 들면:

   static int numargs=0;

   /* Return the number of arguments of the application command line */
   static PyObject*
   emb_numargs(PyObject *self, PyObject *args)
   {
       if(!PyArg_ParseTuple(args, ":numargs"))
           return NULL;
       return PyLong_FromLong(numargs);
   }

   static PyMethodDef emb_module_methods[] = {
       {"numargs", emb_numargs, METH_VARARGS,
        "Return the number of arguments received by the process."},
       {NULL, NULL, 0, NULL}
   };

   static struct PyModuleDef emb_module = {
       .m_base = PyModuleDef_HEAD_INIT,
       .m_name = "emb",
       .m_size = 0,
       .m_methods = emb_module_methods,
   };

   static PyObject*
   PyInit_emb(void)
   {
       return PyModuleDef_Init(&emb_module);
   }

위의 코드를 "main()" 함수 바로 위에 삽입하십시오. 또한,
"Py_Initialize()"에 대한 호출 전에 다음 두 문장을 삽입하십시오:

   numargs = argc;
   PyImport_AppendInittab("emb", &PyInit_emb);

이 두 줄은 "numargs" 변수를 초기화하고, "emb.numargs()" 함수를 내장된
파이썬 인터프리터가 액세스할 수 있도록 만듭니다. 이러한 확장을 통해,
파이썬 스크립트는 다음과 같은 작업을 수행할 수 있습니다

   import emb
   print("Number of arguments", emb.numargs())

실제 응용 프로그램에서, 이 방법은 응용 프로그램의 API를 파이썬에 노출
합니다.


C++로 파이썬 내장하기
=====================

파이썬을 C++ 프로그램에 내장하는 것도 가능합니다; 이것이 어떻게 수행되
는지는 사용된 C++ 시스템의 세부 사항에 달려 있습니다; 일반적으로 C++로
메인 프로그램을 작성하고, C++ 컴파일러를 사용하여 프로그램을 컴파일하
고 링크해야 합니다. C++을 사용하여 파이썬 자체를 다시 컴파일할 필요는
없습니다.


유닉스 계열 시스템에서 컴파일과 링크하기
========================================

파이썬 인터프리터를 응용 프로그램에 내장하기 위해 컴파일러(와 링커)에
적절한 플래그를 찾는 것이 늘 간단하지는 않습니다. 특히, 특히 파이썬이
자신에게 링크된 C 동적 확장(".so" 파일)으로 구현된 라이브러리 모듈을
로드해야 하기 때문입니다.

필요한 컴파일러와 링커 플래그를 찾으려면, 설치 절차의 일부로 생성된
"python*X.Y*-config" 스크립트를 실행할 수 있습니다 ("python3-config"
스크립트도 사용 가능할 수 있습니다). 이 스크립트에는 여러 옵션이 있으
며, 다음과 같은 것들은 여러분에 직접 유용할 것입니다:

* "pythonX.Y-config --cflags"는 컴파일 할 때의 권장 플래그를 제공합니
  다:

     $ /opt/bin/python3.11-config --cflags
     -I/opt/include/python3.11 -I/opt/include/python3.11 -Wsign-compare  -DNDEBUG -g -fwrapv -O3 -Wall

* "pythonX.Y-config --ldflags --embed"는 링크 할 때의 권장 플래그를 제
  공합니다:

     $ /opt/bin/python3.11-config --ldflags --embed
     -L/opt/lib/python3.11/config-3.11-x86_64-linux-gnu -L/opt/lib -lpython3.11 -lpthread -ldl  -lutil -lm

참고:

  여러 파이썬 설치 간의 (특히 시스템 파이썬과 여러분이 직접 컴파일한
  파이썬 간의) 혼란을 피하려면, 위의 예와 같이 "python*X.Y*-config"의
  절대 경로를 사용하는 것이 좋습니다.

이 절차가 여러분을 위해 작동하지 않는다면 (모든 유닉스 계열 플랫폼에서
작동하는 것은 보장되지 않습니다; 하지만, 버그 보고를 환영합니다), 동적
링크에 관한 시스템의 설명서를 읽는 것과/이나 파이썬의 "Makefile"과 (그
위치를 찾으려면 "sysconfig.get_makefile_filename()"를 사용하십시오) 컴
파일 옵션을 검사해야 합니다. 이때, "sysconfig" 모듈은 여러분이 결합하
려는 구성 값을 프로그래밍 방식으로 추출하는 데 유용한 도구입니다. 예를
들어:

   >>> import sysconfig
   >>> sysconfig.get_config_var('LIBS')
   '-lpthread -ldl  -lutil'
   >>> sysconfig.get_config_var('LINKFORSHARED')
   '-Xlinker -export-dynamic'
