PyPy 임베딩 (지원 중단됨) ========================= PyPy는 `cffi`_ 사용과 Python이 C보다 더 나은 언어라는 철학에 기반을 둔, 매우 최소한이면서도 매우 특이한 임베딩 인터페이스를 가지고 있습니다. 이는 `uwsgi`_ 프로젝트의 Roberto De Ioris와 협력하여 개발되었습니다. `PyPy uwsgi plugin`_\ 은 임베딩 API 사용의 좋은 예시입니다. **참고**: ``--shared`` 옵션으로 컴파일된 PyPy, 즉 ``libpypy*-c.so`` 또는 ``pypy*-c.dll`` 파일이 있는 PyPy가 필요합니다. 이는 기본값입니다. .. note:: 이 페이지에서 설명하는 인터페이스는 하위 호환성을 위해 유지됩니다. PyPy 4.1부터는 CPython과 PyPy 모두에서 작동하는 더 간단한 접근 방식을 제공하는 CFFI의 `native embedding support,`__\ 를 대신 사용하는 것이 권장됩니다. .. __: https://cffi.readthedocs.org/en/latest/embedding.html 결과로 생성된 공유 라이브러리는 매우 적은 수의 함수만 노출합니다. 이들은 v7.3.8에서 제거되었으나 `is still available`_\ 한 ``PyPy.h``\ 에 정의되어 있습니다. 몇 가지 원칙만 따른다면, 필요한 모든 작업을 수행하기에 충분합니다. API는 다음과 같습니다: .. function:: void rpython_startup_code(void); 이것은 다른 무언가를 호출하기 전에 (한 번) 호출해야 하는 함수입니다. 이 함수는 RPython/PyPy 가비지 컬렉터를 초기화하고 필요한 여러 시작 코드를 수행합니다. 이 함수는 실패할 수 없습니다. .. function:: int pypy_setup_home(char* home, int verbose); 이 함수는 주어진 "PyPy 홈 디렉터리"부터 시작하여 PyPy 표준 라이브러리를 검색합니다. 인자는 다음과 같습니다: * ``home``: pypy 디렉터리 내 실행 파일 경로(.so 이름일 수 있으며, 임의로 지어내도 됩니다). 표준 라이브러리를 찾는 데 사용되며, ``sys.executable``\ 로도 설정됩니다. PyPy 5.5부터는 ``libpypy-c.so/dylib/dll``\ 이 이 디렉터리 안에 있기만 하다면, 여기에 그냥 NULL을 지정하면 됩니다. * ``verbose``: 0이 아니면 stderr에 오류 메시지를 출력합니다 함수는 성공 시 0을, 실패 시 -1을 반환하며, 라이브러리를 찾을 때까지 여러 번 호출될 수 있습니다. .. function:: void pypy_init_threads(void); 스레드를 초기화합니다. 스레드가 관련된 경우에만 호출하면 됩니다. *pypy_setup_home() 이후에 호출되어야 합니다* .. function:: int pypy_execute_source(char* source); ``source``\ 인자로 주어진 Python 소스 코드를 실행합니다. 예외가 발생하면 Python 트레이스백을 stderr에 출력하고 1을 반환하며, 그렇지 않으면 0을 반환합니다. 소스 코드에서 직접 오류 처리를 해야 합니다. GIL을 획득합니다. 참고: 이것은 *한 번만* 또는 많아야 몇 번만 호출되도록 의도된 것입니다. 아래의 `More complete example `_\ 를 참고하십시오. PyPy <= 2.6.0에서는 globals 딕셔너리가 여러 번의 호출에 걸쳐 *재사용*\ 되어, 잠재적으로 이상한 결과(예: 객체가 너무 일찍 소멸하는 경우)를 초래할 수 있습니다. PyPy 2.6.1 이상에서는 매 호출마다 새로운 전역(globals) 딕셔너리를 얻습니다(다만 이 경우 모든 전역 딕셔너리는 ``sys._pypy_execute_source``\ 에서 영원히 살아 있는 상태로 유지됩니다). .. function:: int pypy_execute_source_ptr(char* source, void* ptr); .. note:: 2014년 6월 PyPy 2.3.1에서 추가됨 위와 마찬가지이지만, 소스 스코프에 ``void*``\ 가 Python int로 인코딩되는 ``c_argument``\ 라는 매직 인자를 등록한다는 점이 다릅니다. .. function:: void pypy_thread_attach(void); PyPy 외부에서 초기화된 스레드를 사용하는 애플리케이션의 경우, 이 스레드를 추적하도록 PyPy GC에 알리기 위해 이 함수를 호출해야 합니다. 이 함수 자체는 스레드 안전하지 않으므로, 뮤텍스 가드(guard)로 보호해야 합니다. 최소 예제 --------- 이 API는 예를 들어 CPython C API보다 훨씬 더 최소화되어 있어서, 처음에는 할 수 있는 게 많지 않다고 생각하기 쉽습니다. 하지만 요령은 모든 로직을 Python으로 작성하고 `cffi`_\ 콜백을 통해 노출하는 것입니다. 작은 C 프로그램을 작성합니다: .. code-block:: c #include "PyPy.h" #include #include static char source[] = "print 'hello from pypy'"; int main(void) { int res; rpython_startup_code(); /* Before PyPy 5.5, you may need to say e.g. "/opt/pypy/bin" instead * of NULL. */ res = pypy_setup_home(NULL, 1); if (res) { printf("Error setting pypy home!\n"); return 1; } res = pypy_execute_source((char*)source); if (res) { printf("Error calling pypy_execute_source!\n"); } return res; } 이제 이를 ``x.c``\ 로 저장하고 컴파일한 다음, (리눅스에서) 다음 명령으로 실행하면:: $ gcc -g -o x x.c -lpypy-c -L/opt/pypy/bin -I/opt/pypy/include $ LD_LIBRARY_PATH=/opt/pypy/bin ./x hello from pypy OSX에서 바이너리에 링크하려면, 다음과 같은 명령으로 바이너리의 rpath를 설정해야 합니다.:: gcc -o x x.c -lpypy-c -L. -Wl,-rpath -Wl,@executable_path ./x hello from pypy .. _yeokja-doc-ea658d6847f93669-target-4784: 더 완전한 예제 -------------- .. note:: 여기서는 CFFI 1.4에서 콜백을 수행하는 새로운 방법인 ``extern "Python"``\ 을 사용하지 않는다는 점에 유의하십시오. 이는 이 예제들이 API 모드가 아닌 ABI 모드를 사용하기 때문이며, ABI 모드에서는 여전히 ``ffi.callback()``\ 을 사용해야 합니다. ``extern "Python"``\ 을 임베딩(embedding)이라는 개념과 통합하는 작업이 진행 중입니다(그리고 이는 궁극적으로 여기서 설명한 것보다 더 나은 임베딩 방법으로 이어질 것으로 예상되며, 이는 CPython과 PyPy 모두에서 동일하게 잘 작동할 것입니다). 보통은 단순히 소스를 실행하는 것 이상의 작업이 필요합니다. 다음은 완전한 형태를 갖춘 예제이며, 자세한 내용은 cffi 문서를 참고하시기 바랍니다. 다소 길긴 하지만, PyPy 임베딩 인터페이스로 할 수 있는 작업의 요점을 잘 보여줍니다: .. code-block:: python # file "interface.py" import cffi ffi = cffi.FFI() ffi.cdef(''' struct API { double (*add_numbers)(double x, double y); }; ''') # Better define callbacks at module scope, it's important to # keep this object alive. @ffi.callback("double (double, double)") def add_numbers(x, y): return x + y def fill_api(ptr): global api api = ffi.cast("struct API*", ptr) api.add_numbers = add_numbers .. code-block:: c /* C example */ #include "PyPy.h" #include #include struct API { double (*add_numbers)(double x, double y); }; struct API api; /* global var */ int initialize_api(void) { static char source[] = "import sys; sys.path.insert(0, '.'); " "import interface; interface.fill_api(c_argument)"; int res; rpython_startup_code(); res = pypy_setup_home(NULL, 1); if (res) { fprintf(stderr, "Error setting pypy home!\n"); return -1; } res = pypy_execute_source_ptr(source, &api); if (res) { fprintf(stderr, "Error calling pypy_execute_source_ptr!\n"); return -1; } return 0; } int main(void) { if (initialize_api() < 0) return 1; printf("sum: %f\n", api.add_numbers(12.3, 45.6)); return 0; } 다음 명령으로 컴파일하고 실행할 수 있습니다:: $ gcc -g -o x x.c -lpypy-c -L/opt/pypy/bin -I/opt/pypy/include $ LD_LIBRARY_PATH=/opt/pypy/bin ./x sum: 57.900000 보시다시피, 우리가 한 일은 이 특정한 경우에 필요한 커스텀 API를 담는 ``struct API``\ 를 만드는 것입니다. 이 구조체는 Python에 의해 채워져 함수 포인터를 담게 되며, 이 함수 포인터는 이후 C 쪽에서 호출됩니다. C 쪽에서 채워지고 Python 쪽에서 호출되는 다른 함수 포인터들이나, 함수 포인터가 아닌 필드들을 두는 것도 가능합니다. 기본적으로 양쪽은 여러분의 API를 정의하는 이 하나의 C 구조체를 통해 통신합니다. pypy_home 찾기 -------------- **PyPy >= 5.5(2016년 10월 출시)를 실행 중이라면 보통 이 절을 건너뛰어도 됩니다** 함수 pypy_setup_home()은 첫 번째 매개변수로 표준 라이브러리의 위치를 추측할 수 있는 파일의 경로를 받습니다. 더 정확히 말하면, ``lib-python``\ 과 ``lib_pypy``\ 를 찾을 때까지 마지막 구성 요소를 제거하려고 시도합니다. 현재 이 경로를 찾는 "깔끔한" 방법(pkg-config가 떠오릅니다)은 없습니다. 다음과 같은 (GNU 전용) 방법(*dl*\ 을 링크하는 것을 잊지 마세요)을 시도해볼 수 있습니다. 이는 ``libpypy-c.so``\ 가 표준 라이브러리 디렉터리 안에 있다고 가정합니다. (어쨌든 대략적으로는 이래야 하며, 그렇지 않으면 ``pypy`` 프로그램 자체가 실행되지 않을 것입니다.) .. code-block:: c #if !(_GNU_SOURCE) #define _GNU_SOURCE #endif #include #include #include // caller should free returned pointer to avoid memleaks // returns NULL on error char* guess_pypyhome(void) { // glibc-only (dladdr is why we #define _GNU_SOURCE) Dl_info info; void *_rpython_startup_code = dlsym(0,"rpython_startup_code"); if (_rpython_startup_code == 0) { return 0; } if (dladdr(_rpython_startup_code, &info) != 0) { const char* lib_path = info.dli_fname; char* lib_realpath = realpath(lib_path, 0); return lib_realpath; } return 0; } 스레딩 ------ pthreads를 사용하고 싶은 경우, 여러분이 생성한 각 스레드에서(단, 메인 스레드는 제외) ``pypy_thread_attach``\ 를 호출하고, 메인 스레드에서 ``pypy_init_threads``\ 를 호출해야 합니다. .. _`cffi`: https://cffi.readthedocs.org/ .. _`uwsgi`: https://uwsgi-docs.readthedocs.org/en/latest/ .. _`PyPy uwsgi plugin`: https://uwsgi-docs.readthedocs.org/en/latest/PyPy.html .. _`how to compile PyPy`: getting-started.html .. _`is still available`: pypy.h.html