diff --git a/_images/00_vscode_setting.png b/_images/00_vscode_setting.png new file mode 100644 index 0000000..fe6a4f7 Binary files /dev/null and b/_images/00_vscode_setting.png differ diff --git a/_images/01_eval1.JPG b/_images/01_eval1.JPG new file mode 100644 index 0000000..85f5383 Binary files /dev/null and b/_images/01_eval1.JPG differ diff --git a/_images/01_vscode_setting.png b/_images/01_vscode_setting.png new file mode 100644 index 0000000..1d0e21c Binary files /dev/null and b/_images/01_vscode_setting.png differ diff --git a/_images/02_eval2.JPG b/_images/02_eval2.JPG new file mode 100644 index 0000000..323cfdf Binary files /dev/null and b/_images/02_eval2.JPG differ diff --git a/_images/03_eval3.JPG b/_images/03_eval3.JPG new file mode 100644 index 0000000..02ae3ec Binary files /dev/null and b/_images/03_eval3.JPG differ diff --git a/_images/04_eval4.JPG b/_images/04_eval4.JPG new file mode 100644 index 0000000..62964e5 Binary files /dev/null and b/_images/04_eval4.JPG differ diff --git a/_images/05_eval5.JPG b/_images/05_eval5.JPG new file mode 100644 index 0000000..451938e Binary files /dev/null and b/_images/05_eval5.JPG differ diff --git a/_sources/docs/14_0_c_extension.md b/_sources/docs/14_0_c_extension.md index 5c40b1a..0c6599c 100644 --- a/_sources/docs/14_0_c_extension.md +++ b/_sources/docs/14_0_c_extension.md @@ -1 +1,141 @@ # Python C 확장 개발 가이드 + +## 참고 페이지 +[Real Python Building a Python C Extension Module](https://realpython.com/build-python-c-extension-module/) +[MSDN Python용 C++ 확장 만들기](https://learn.microsoft.com/ko-kr/visualstudio/python/working-with-c-cpp-python-in-visual-studio?view=vs-2022) + + +## 샘플 코드 +저장소 book/c_extension_sample 폴더 + + +## Python C 확장 따라 만들기 +원하는 경로, 원하는 IDE에서 작업을 시작할 수 있습니다 +VSCode 기준으로 작업을 진행합니다 +C/C++ 확장이 설치되어 있어야 편합니다 + +### VSCode 세팅하기 +![VSCode 세팅](../images/14_c_extension/00_vscode_setting.png) +Ctrl + Shift + P 를 눌러 C/C++ Configurations 창을 열어줍니다 + +![VSCode 세팅](../images/14_c_extension/01_vscode_setting.png) +Include path 에 Python이 설치된 폴더를 보면 include 폴더가 있습니다 +해당 경로를 추가해줍니다 + +### c 확장 모듈 기능 작성하기 +이제 간단하게 C API 인 fputs를 python에서 쓸 수 있는 모듈로 개발합니다 +```c +#include + +static PyObject* method_fputs(PyObject* self, PyObject* args) { + char *str, *filename = NULL; + int bytes_copied = -1; + + if(!PyArg_ParseTuple(args, "ss", &str, &filename)) { + return NULL; + } + + FILE *fp = fopen(filename, "w"); + bytes_copied = fputs(str, fp); + fclose(fp); + + return PyLong_FromLong(bytes_copied); +} +``` +PyObject* 를 반환하는 fputs static 함수를 작성하였습니다 +PyObject 타입은 Python 의 객체 타입으로 **11장 객체와 타입**에서도 설명을 하고 있습니다 + +```c +... + char *str, *filename = NULL; + int bytes_copied = -1; + if(!PyArg_ParseTuple(args, "ss", &str, &filename)) { + return NULL; + } +... +``` +PyArg_ParseTuple 함수는 Python 에서 받은 인수 args를 로컬 변수로 대입합니다 +2번째 인자 "ss"는 어떤 타입으로 인수를 파싱할지 설정합니다 (예제에서는 문자열, 문자열로 파싱) +[[c-api arg 관련 공식 문서](https://docs.python.org/3/c-api/arg.html)] +3번째 인자부터는 가변길이 인자로 인수를 대입받을 변수들의 주소값을 차례대로 넘겨줍니다 + +```c + FILE *fp = fopen(filename, "w"); + bytes_copied = fputs(str, fp); + fclose(fp); + + return PyLong_FromLong(bytes_copied); +``` +받은 인수값으로 파일을 열어 fputs를 수행합니다 +그리고 반환값으로 PyLong_FromLong 함수를 통하여 fputs로 쓴 파일 크기를 PyObject로 반환합니다 +(PyLong_FromLong에 대한 부분도 11장 객체와 타입 참조) + +### c 확장 모듈 초기화 함수 작성하기 +위 코드에 이어서 확장 모듈 초기화 부분을 작성합니다 +```c +static PyMethodDef FPutsMethods[] = { + {"fputs", method_fputs, METH_VARARGS, "Python interface for fputs C library function"}, + {NULL, NULL, 0, NULL} +}; +``` +모듈에서 정의된 메서드를 인터프리터에 알려주기위한 PyMethodDef 설정 +**"fputs"** - Python에서 호출되는 이름 +**method_fputs** - 호출되는 C 함수 +**METH_VARARGS** - self와 args를 인자로 받는다는 플래그 +([c-api sstructures](https://docs.python.org/3/c-api/structures.html)) +**마지막 문자열** - 메서드에 대한 docstring + +```c +static struct PyModuleDef fputsmodule = { + PyModuleDef_HEAD_INIT, + "fputs", + "Python interface for the fputs C library function", + -1, + FPutsMethods +}; +``` +PyModuleDef 로 C 확장 모듈 자체에 대한 정보를 저장 ([PyModuleDef 문서](https://docs.python.org/3/c-api/module.html#c.PyModuleDef)) +총 9개의 멤버가 있지만 여기서는 필요한 5개만 초기화합니다 + +**PyModuleDef_HEAD_INIT** - PyModuleDef_Base 유형의 멤버, 하나만 갖도록 권장 +**"fputs"** - Python C 확장 모듈 이름 (import fputs) +**3번째 문자열** - 모듈에 대한 docstring +**-1** - 프로그램 상태 저장에 필요한 메모리 크기, 모듈이 여러 서브 인터프리터에서 사용될 때 유용, 음수 값은 서브 인터프리터를 지원하지 않음을 나타냄 +**FPutsMethods** - 메서드 테이블에 대한 참조 + +```c +PyMODINIT_FUNC PyInit_fputs(void) { + return PyModule_Create(&fputsmodule); +} +``` +Python에서 모듈을 처음 임포트 할 때 PyInit_fputs를 호출하게 합니다 +PyMODINIT_FUNC은 암시적으로 세 가지 작업을 수행 +1. 함수 반환형을 PyObject*로 암시적 설정 +2. 특별한 연결을 선언 +3. 함수를 External "C" 로 선언 (C++의 경우 C++ 컴파일러에 심볼에 대한 이름이 섞이지 않도록 지시) + +PyModule_Create는 PyObject* 형의 새로운 모듈 객체를 반환 +인자로는 이전에 선언해둔 fputsmodule 객체의 주소를 전달 + +### setup.py 작성 +```python +from distutils.core import setup, Extension + +def main(): + setup(name="fputs", + version="1.0.0", + description="Python interface for the fputs C library function", + author="<개발자 이름>", + author_email="your_email@gmail.com", + ext_modules=[Extension("fputs", ["fputsmodule.c"])]) + +if __name__ == "__main__": + main() +``` +Python 패키지를 distutils를 사용하여 모듈을 빌드 할수 있습니다 +내부에서 clang을 사용하여 빌드합니다 + +```shell +python setup.py install +``` +setup.py 작성 후 위 명령어로 실행하면 내가 만든 모듈이 빌드되고 설치됩니다 diff --git a/_sources/docs/7_0_compiler.md b/_sources/docs/7_0_compiler.md index 9e66f68..2e0f22b 100644 --- a/_sources/docs/7_0_compiler.md +++ b/_sources/docs/7_0_compiler.md @@ -165,3 +165,9 @@ PyAST_CompileObject()에 컴파일러 상태와 symtable, AST로 파싱된 모 1. 컴파일러 상태, 심벌 테이블, AST를 제어 흐름 그래프로 변환 2. 논리 오류나 코드 오류를 탐지, 실행 단계를 런타임 예외로부터 보호 + +## 어셈블리 +컴파일 단계가 끝나면 블록 리스트 완성 +각 프레임 블록은 명령 리스트와 다음 블록을 가리키는 포인터를 가짐 +기본 프레임 블록들에 깊이 우선 탐색(DFS)를 실행, 명령들을 단일한 바이트 코드 시퀀스로 병합 + diff --git a/_sources/docs/8_0_eval_loop.md b/_sources/docs/8_0_eval_loop.md index 2847808..bcbfc67 100644 --- a/_sources/docs/8_0_eval_loop.md +++ b/_sources/docs/8_0_eval_loop.md @@ -17,7 +17,7 @@ CPython 에서 코드는 **평가 루프** 라는 개념을 통해 루프(Loop)
-![parse_tree](../images/8_eval_loop/01_eval1.jpg) +![parse_tree](../images/8_eval_loop/01_eval1.JPG) 다음은 이번 장을 통해 살펴볼 개념들입니다.
@@ -40,7 +40,7 @@ CPython 에서 코드는 **평가 루프** 라는 개념을 통해 루프(Loop) -![CST_EXPR](../images/8_eval_loop/02_eval2.jpg) +![CST_EXPR](../images/8_eval_loop/02_eval2.JPG) 위의 PyThreadState는 _ts 의 별칭으로 사용되게 됩니다. 코드를 살펴보게 되면, 고유 식별자, 다른 스레드 상태와 연결된 연결 리스트, 인터프리터의 상태, 재귀 깊이 등을 나타내는 값들로 이루어져 있습니다. @@ -56,17 +56,17 @@ AST 를 거쳐 컴파일된 객체는 프레임 객체에 최종적으로 삽입
다음은 프레임 타입 객체 PyFrameObject 가 가진 프로퍼티입니다. -![Parser_Tokenizer](../images/8_eval_loop/03_eval3.jpg) +![Parser_Tokenizer](../images/8_eval_loop/03_eval3.JPG) 다음은 프로퍼티를 가진 CPython 의 코드 입니다. -![Parser_Tokenizer](../images/8_eval_loop/04_eval4.jpg) +![Parser_Tokenizer](../images/8_eval_loop/04_eval4.JPG) 인터프리터에 의해 만들어진 PyFrameObject 는 초기화 과정을 거치게 되는데, PyEval_EvalCode( ) 라는 함수 안에서 초기화 과정을 거치게 됩니다.
-![Parser_Tokenizer](../images/8_eval_loop/05_eval5.jpg) +![Parser_Tokenizer](../images/8_eval_loop/05_eval5.JPG)

diff --git a/docs/14_0_c_extension.html b/docs/14_0_c_extension.html index 1f3e5cd..5490be4 100644 --- a/docs/14_0_c_extension.html +++ b/docs/14_0_c_extension.html @@ -353,7 +353,9 @@ `); - + @@ -369,6 +371,22 @@

Python C 확장 개발 가이드

@@ -380,6 +398,145 @@

Python C 확장 개발 가이드

Python C 확장 개발 가이드#

+
+

참고 페이지#

+

Real Python Building a Python C Extension Module
+MSDN Python용 C++ 확장 만들기

+
+
+

샘플 코드#

+

저장소 book/c_extension_sample 폴더

+
+
+

Python C 확장 따라 만들기#

+

원하는 경로, 원하는 IDE에서 작업을 시작할 수 있습니다
+VSCode 기준으로 작업을 진행합니다
+C/C++ 확장이 설치되어 있어야 편합니다

+
+

VSCode 세팅하기#

+

VSCode 세팅
+Ctrl + Shift + P 를 눌러 C/C++ Configurations 창을 열어줍니다

+

VSCode 세팅
+Include path 에 Python이 설치된 폴더를 보면 include 폴더가 있습니다
+해당 경로를 추가해줍니다

+
+
+

c 확장 모듈 기능 작성하기#

+

이제 간단하게 C API 인 fputs를 python에서 쓸 수 있는 모듈로 개발합니다

+
#include <Python.h>
+
+static PyObject* method_fputs(PyObject* self, PyObject* args) {
+    char *str, *filename = NULL;
+    int bytes_copied = -1;
+
+    if(!PyArg_ParseTuple(args, "ss", &str, &filename)) {
+        return NULL;
+    }
+
+    FILE *fp = fopen(filename, "w");
+    bytes_copied = fputs(str, fp);
+    fclose(fp);
+
+    return PyLong_FromLong(bytes_copied);
+}
+
+
+

PyObject* 를 반환하는 fputs static 함수를 작성하였습니다
+PyObject 타입은 Python 의 객체 타입으로 11장 객체와 타입에서도 설명을 하고 있습니다

+
...
+    char *str, *filename = NULL;
+    int bytes_copied = -1;
+    if(!PyArg_ParseTuple(args, "ss", &str, &filename)) {
+        return NULL;
+    }
+...
+
+
+

PyArg_ParseTuple 함수는 Python 에서 받은 인수 args를 로컬 변수로 대입합니다
+2번째 인자 “ss”는 어떤 타입으로 인수를 파싱할지 설정합니다 (예제에서는 문자열, 문자열로 파싱)
+[c-api arg 관련 공식 문서]
+3번째 인자부터는 가변길이 인자로 인수를 대입받을 변수들의 주소값을 차례대로 넘겨줍니다

+
    FILE *fp = fopen(filename, "w");
+    bytes_copied = fputs(str, fp);
+    fclose(fp);
+
+    return PyLong_FromLong(bytes_copied);
+
+
+

받은 인수값으로 파일을 열어 fputs를 수행합니다
+그리고 반환값으로 PyLong_FromLong 함수를 통하여 fputs로 쓴 파일 크기를 PyObject로 반환합니다
+(PyLong_FromLong에 대한 부분도 11장 객체와 타입 참조)

+
+
+

c 확장 모듈 초기화 함수 작성하기#

+

위 코드에 이어서 확장 모듈 초기화 부분을 작성합니다

+
static PyMethodDef FPutsMethods[] = {
+    {"fputs", method_fputs, METH_VARARGS, "Python interface for fputs C library function"},
+    {NULL, NULL, 0, NULL}
+};
+
+
+

모듈에서 정의된 메서드를 인터프리터에 알려주기위한 PyMethodDef 설정
+“fputs” - Python에서 호출되는 이름
+method_fputs - 호출되는 C 함수 +METH_VARARGS - self와 args를 인자로 받는다는 플래그
+(c-api sstructures) +마지막 문자열 - 메서드에 대한 docstring

+
static struct PyModuleDef fputsmodule = {
+    PyModuleDef_HEAD_INIT,
+    "fputs",
+    "Python interface for the fputs C library function",
+    -1,
+    FPutsMethods
+};
+
+
+

PyModuleDef 로 C 확장 모듈 자체에 대한 정보를 저장 (PyModuleDef 문서)
+총 9개의 멤버가 있지만 여기서는 필요한 5개만 초기화합니다

+

PyModuleDef_HEAD_INIT - PyModuleDef_Base 유형의 멤버, 하나만 갖도록 권장
+“fputs” - Python C 확장 모듈 이름 (import fputs)
+3번째 문자열 - 모듈에 대한 docstring
+-1 - 프로그램 상태 저장에 필요한 메모리 크기, 모듈이 여러 서브 인터프리터에서 사용될 때 유용, 음수 값은 서브 인터프리터를 지원하지 않음을 나타냄
+FPutsMethods - 메서드 테이블에 대한 참조

+
PyMODINIT_FUNC PyInit_fputs(void) {
+    return PyModule_Create(&fputsmodule);
+}
+
+
+

Python에서 모듈을 처음 임포트 할 때 PyInit_fputs를 호출하게 합니다
+PyMODINIT_FUNC은 암시적으로 세 가지 작업을 수행

+
    +
  1. 함수 반환형을 PyObject*로 암시적 설정

  2. +
  3. 특별한 연결을 선언

  4. +
  5. 함수를 External “C” 로 선언 (C++의 경우 C++ 컴파일러에 심볼에 대한 이름이 섞이지 않도록 지시)

  6. +
+

PyModule_Create는 PyObject* 형의 새로운 모듈 객체를 반환
+인자로는 이전에 선언해둔 fputsmodule 객체의 주소를 전달

+
+
+

setup.py 작성#

+
from distutils.core import setup, Extension
+
+def main():
+    setup(name="fputs",
+          version="1.0.0",
+          description="Python interface for the fputs C library function",
+          author="<개발자 이름>",
+          author_email="your_email@gmail.com",
+          ext_modules=[Extension("fputs", ["fputsmodule.c"])])
+
+if __name__ == "__main__":
+    main()
+
+
+

Python 패키지를 distutils를 사용하여 모듈을 빌드 할수 있습니다
+내부에서 clang을 사용하여 빌드합니다

+
python setup.py install
+
+
+

setup.py 작성 후 위 명령어로 실행하면 내가 만든 모듈이 빌드되고 설치됩니다

+
+