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

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

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

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

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

더 보기:

  파이썬/C API 레퍼런스 설명서
     파이썬의 C 인터페이스에 대한 자세한 내용은 이 매뉴얼에 있습니다.
     필요한 정보가 많이 있습니다.


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

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

   #define PY_SSIZE_T_CLEAN
   #include <Python.h>

   int
   main(int argc, char *argv[])
   {
       wchar_t *program = Py_DecodeLocale(argv[0], NULL);
       if (program == NULL) {
           fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
           exit(1);
       }
       Py_SetProgramName(program);  /* optional but recommended */
       Py_Initialize();
       PyRun_SimpleString("from time import time,ctime\n"
                          "print('Today is', ctime(time()))\n");
       if (Py_FinalizeEx() < 0) {
           exit(120);
       }
       PyMem_RawFree(program);
       return 0;
   }

The "Py_SetProgramName()" function should be called before
"Py_Initialize()" to inform the interpreter about paths to Python run-
time libraries.  Next, the Python interpreter is initialized with
"Py_Initialize()", followed by the execution of a hard-coded Python
script that prints the date and time.  Afterwards, the
"Py_FinalizeEx()" call shuts the interpreter down, followed by the end
of the program.  In a real program, you may want to get the Python
script from another source, perhaps a text-editor routine, a file, or
a database.  Getting the Python code from a file can better be done by
using the "PyRun_SimpleFile()" function, which saves you the trouble
of allocating memory space and loading the file contents.


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

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

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

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

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

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

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

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

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

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

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

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


1.3. 순수한 내장
================

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

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

   #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]);
       /* Error checking of pName left out */

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

       if (pModule != NULL) {
           pFunc = PyObject_GetAttrString(pModule, argv[2]);
           /* pFunc is a new reference */

           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 reference stolen here: */
                   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);

After initializing the interpreter, the script is loaded using
"PyImport_Import()".  This routine needs a Python string as its
argument, which is constructed using the "PyUnicode_FromString()" data
conversion routine.

   pFunc = PyObject_GetAttrString(pModule, argv[2]);
   /* pFunc is a new reference */

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

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

   pValue = PyObject_CallObject(pFunc, pArgs);

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


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

지금까지 내장된 파이썬 인터프리터는 애플리케이션 자체의 기능에 액세스
할 수 없었습니다. 파이썬 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 EmbMethods[] = {
       {"numargs", emb_numargs, METH_VARARGS,
        "Return the number of arguments received by the process."},
       {NULL, NULL, 0, NULL}
   };

   static PyModuleDef EmbModule = {
       PyModuleDef_HEAD_INIT, "emb", NULL, -1, EmbMethods,
       NULL, NULL, NULL, NULL
   };

   static PyObject*
   PyInit_emb(void)
   {
       return PyModule_Create(&EmbModule);
   }

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

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

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

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

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


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

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


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

파이썬 인터프리터를 응용 프로그램에 내장하기 위해 컴파일러(와 링커)에
적절한 플래그를 찾는 것이 늘 간단하지는 않습니다. 특히, 특히 파이썬이
자신에게 링크된 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'
