From 317f496911b3787399eff2adac4b2848905fe968 Mon Sep 17 00:00:00 2001 From: "Sangchun Ha (Patrick)" Date: Thu, 18 Apr 2024 10:56:36 +0900 Subject: [PATCH] =?UTF-8?q?3=EC=A3=BC=EC=B0=A8=20Jupyter=20Book=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=A0=95=EB=A6=AC=20(#46)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- book/_config.yml | 2 +- book/docs/5_0_config_and_input.md | 1077 ++++++++++---------- book/images/5_config_and_input/mermaid.png | Bin 0 -> 21080 bytes 3 files changed, 562 insertions(+), 517 deletions(-) create mode 100644 book/images/5_config_and_input/mermaid.png diff --git a/book/_config.yml b/book/_config.yml index 6ea7d97..e31f655 100644 --- a/book/_config.yml +++ b/book/_config.yml @@ -41,4 +41,4 @@ html: sphinx: config: html_js_files: - - https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js \ No newline at end of file + - https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js diff --git a/book/docs/5_0_config_and_input.md b/book/docs/5_0_config_and_input.md index 92dc792..9b5e4ea 100644 --- a/book/docs/5_0_config_and_input.md +++ b/book/docs/5_0_config_and_input.md @@ -1,4 +1,5 @@ # 5. 구성과 입력 + 이번 챕터를 통해 다음 내용을 배울 수 있습니다. 1. CPython 인터프리터의 빌드부터 파이썬 코드 실행까지의 과정에 대한 코드 레벨의 이해 @@ -6,6 +7,7 @@ 3. 인터프리터의 입력에서 모듈을 만드는 과정 ## 5.0 CPython Interpreter Execution Overview + 인터프리터가 실행되는 흐름은 다음과 같습니다. ![](../images/5_config_and_input/interpreter_overview.png) 이 때, 세 가지 요소가 중요합니다. @@ -16,207 +18,225 @@ 세 가지 요소로 넘어가기 전에, 인터프리터가 실행되는 과정에 대한 이해를 높여보겠습니다. 인터프리터의 생성부터 실행까지의 과정을 코드 레벨로 정리하면 다음과 같습니다. -1. 인터프리터 생성 (= `3.컴파일하기`) +
+1. 인터프리터 생성 (= 3.컴파일하기) - ```mermaid - graph LR - A("`CPython Source Code`") --> |Build|C("`CPython Interpreter`") - ``` - - 책에서는 CPython Source Code를 compile 해서 인터프리터를 생성한다고 표현하기도 하고, build한다고도 표현합니다. **Build** 과정에는 **Compile** 과정이 포함되어 있기 때문에, 굳이 구분하지 않고 혼용해서 사용하는 것으로 보입니다. - - 또한, 빌드 결과물인 **CPython Binary**와 **executable interpreter**(작동하는 인터프리터)를 같은 용어로 사용하고 있습니다. **CPython Interpreter**로 이해해도 될 듯 합니다. **“CPython 소스 코드를 빌드해서 파이썬 인터프리터를 생성했구나”** 라고 생각하면 되겠습니다. - - 인터프리터 파일은 윈도우, 맥 모두 **python.exe** 파일로 생성됩니다. 확장자는 **Makefile**의 `BUILDEXE` 변수로 변경할 수 있습니다. - - ```makefile - # Executable suffix (.exe on Windows and Mac OS X) - EXE= - BUILDEXE= .exe - ``` - - - 인터프리터를 빌드할 때 사용되는 소스 코드는 **Programs/python.c** 입니다. 따라서, 인터프리터를 실행할 때 시작되는 entrypoint file은 **Programs/python.c 입니다.** - - ```makefile - # Default target - all: build_all - ``` - - ```makefile - build_all: check-clean-src $(BUILDPYTHON) oldsharedmods sharedmods gdbhooks \ - Programs/_testembed python-config - ``` - - ```makefile - # Build the interpreter - $(BUILDPYTHON): Programs/python.o $(LIBRARY) $(LDLIBRARY) $(PY3LIBRARY) $(EXPORTSYMS) - $(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(BLDLIBRARY) $(LIBS) $(MODLIBS) $(SYSLIBS) - ``` - - ```makefile - Programs/python.o: $(srcdir)/Programs/python.c - $(MAINCC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/python.c - ``` - -2. 인터프리터 시작: 운영 체제에 따른 main 선택 (`./python.exe`) - - **Programs/python.c** - - ```c - ```c - /* Minimal main program -- everything is loaded from the library */ - - #include "Python.h" - - #ifdef MS_WINDOWS - int - wmain(int argc, wchar_t **argv) - { - return Py_Main(argc, argv); +![img](../images/5_config_and_input/mermaid.png) + +- 책에서는 CPython Source Code를 compile 해서 인터프리터를 생성한다고 표현하기도 하고, build한다고도 표현합니다. **Build** 과정에는 **Compile** 과정이 포함되어 있기 때문에, 굳이 구분하지 않고 혼용해서 사용하는 것으로 보입니다. +- 또한, 빌드 결과물인 **CPython Binary**와 **executable interpreter**(작동하는 인터프리터)를 같은 용어로 사용하고 있습니다. **CPython Interpreter**로 이해해도 될 듯 합니다. **“CPython 소스 코드를 빌드해서 파이썬 인터프리터를 생성했구나”** 라고 생각하면 되겠습니다. +- 인터프리터 파일은 윈도우, 맥 모두 **python.exe** 파일로 생성됩니다. 확장자는 **Makefile**의 `BUILDEXE` 변수로 변경할 수 있습니다. + +```makefile +# Executable suffix (.exe on Windows and Mac OS X) +EXE= +BUILDEXE= .exe +``` + +- 인터프리터를 빌드할 때 사용되는 소스 코드는 **Programs/python.c** 입니다. 따라서, 인터프리터를 실행할 때 시작되는 entrypoint file은 **Programs/python.c 입니다.** + +```makefile +# Default target +all: build_all +``` + +```makefile +build_all: check-clean-src $(BUILDPYTHON) oldsharedmods sharedmods gdbhooks \ + Programs/_testembed python-config +``` + +```makefile +# Build the interpreter +$(BUILDPYTHON): Programs/python.o $(LIBRARY) $(LDLIBRARY) $(PY3LIBRARY) $(EXPORTSYMS) + $(LINKCC) $(PY_CORE_LDFLAGS) $(LINKFORSHARED) -o $@ Programs/python.o $(BLDLIBRARY) $(LIBS) $(MODLIBS) $(SYSLIBS) +``` + +```makefile +Programs/python.o: $(srcdir)/Programs/python.c + $(MAINCC) -c $(PY_CORE_CFLAGS) -o $@ $(srcdir)/Programs/python.c +``` + +
+ +
+2. 인터프리터 시작: 운영 체제에 따른 main 선택 (./python.exe) + +**Programs/python.c** + +```c +/* Minimal main program -- everything is loaded from the library */ + +#include "Python.h" + +#ifdef MS_WINDOWS +int +wmain(int argc, wchar_t **argv) +{ + return Py_Main(argc, argv); +} +#else +int +main(int argc, char **argv) +{ + return Py_BytesMain(argc, argv); // macOS +} +#endif + +``` + +- macOS는 `Py_BytesMain` 함수로 진입합니다. + +
+ +
+3. Py_BytesMain: argument 포함시키기 + +**Modules/main.c: L723-L732** + +```c +int +Py_BytesMain(int argc, char **argv) +{ + _PyArgv args = { + .argc = argc, + .use_bytes_argv = 1, + .bytes_argv = argv, + .wchar_argv = NULL}; + return pymain_main(&args); +} +``` + +
+ +
+4. pymain_main: 인터프리터 전체 동작 흐름 + +**Modules/main.c: L695-L708** + +```c +static int +pymain_main(_PyArgv *args) +{ + PyStatus status = pymain_init(args); + if (_PyStatus_IS_EXIT(status)) { + pymain_free(); + return status.exitcode; } - #else - int - main(int argc, char **argv) - { - return Py_BytesMain(argc, argv); // macOS + if (_PyStatus_EXCEPTION(status)) { + pymain_exit_error(status); } - #endif - - ``` - - macOS는 `Py_BytesMain` 함수로 진입합니다. -3. Py_BytesMain: argument 포함시키기 - - **Modules/main.c: L723-L732** - + return Py_RunMain(); +} +``` + +- `pymain_init()`의 리턴인 `PyStatus`는 `OK`, `ERROR`, `EXIT` 3가지 타입을 갖습니다. + + **Include/cpython/initconfig.h: L10-L19** + ```c - int - Py_BytesMain(int argc, char **argv) - { - _PyArgv args = { - .argc = argc, - .use_bytes_argv = 1, - .bytes_argv = argv, - .wchar_argv = NULL}; - return pymain_main(&args); - } + typedef struct { + enum { + _PyStatus_TYPE_OK=0, + _PyStatus_TYPE_ERROR=1, + _PyStatus_TYPE_EXIT=2 + } _type; + const char *func; + const char *err_msg; + int exitcode; + } PyStatus; ``` - -4. pymain_main: 인터프리터 전체 동작 흐름 - - **Modules/main.c: L695-L708** - - ```c - static int - pymain_main(_PyArgv *args) - { - PyStatus status = pymain_init(args); - if (_PyStatus_IS_EXIT(status)) { - pymain_free(); - return status.exitcode; - } - if (_PyStatus_EXCEPTION(status)) { - pymain_exit_error(status); - } - - return Py_RunMain(); + +- 타입이 `OK`인 경우에만 `Py_RunMain()` 함수가 실행됩니다. + +
+ +
+5. pymain_init: runtime configuration 설정 (5.1 ~ 5.2) + +**Modules/main.c: L33-L75** + +```c +static PyStatus +pymain_init(const _PyArgv *args) +{ + PyStatus status; + + status = _PyRuntime_Initialize(); + if (_PyStatus_EXCEPTION(status)) { + return status; } - ``` - - - `pymain_init()`의 리턴인 `PyStatus`는 `OK`, `ERROR`, `EXIT` 3가지 타입을 갖습니다. - - **Include/cpython/initconfig.h: L10-L19** - - ```c - typedef struct { - enum { - _PyStatus_TYPE_OK=0, - _PyStatus_TYPE_ERROR=1, - _PyStatus_TYPE_EXIT=2 - } _type; - const char *func; - const char *err_msg; - int exitcode; - } PyStatus; - ``` - - - 타입이 `OK`인 경우에만 `Py_RunMain()` 함수가 실행됩니다. -5. pymain_init: runtime configuration 설정 (5.1 ~ 5.2) - - **Modules/main.c: L33-L75** - - ```c - static PyStatus - pymain_init(const _PyArgv *args) - { - PyStatus status; - - status = _PyRuntime_Initialize(); - if (_PyStatus_EXCEPTION(status)) { - return status; - } - - PyPreConfig preconfig; - PyPreConfig_InitPythonConfig(&preconfig); - - status = _Py_PreInitializeFromPyArgv(&preconfig, args); - if (_PyStatus_EXCEPTION(status)) { - return status; - } - - PyConfig config; - PyConfig_InitPythonConfig(&config); - - /* pass NULL as the config: config is read from command line arguments, - environment variables, configuration files */ - if (args->use_bytes_argv) { - status = PyConfig_SetBytesArgv(&config, args->argc, args->bytes_argv); - } - else { - status = PyConfig_SetArgv(&config, args->argc, args->wchar_argv); - } - if (_PyStatus_EXCEPTION(status)) { - goto done; - } - - status = Py_InitializeFromConfig(&config); - if (_PyStatus_EXCEPTION(status)) { - goto done; - } - status = _PyStatus_OK(); - - done: - PyConfig_Clear(&config); + + PyPreConfig preconfig; + PyPreConfig_InitPythonConfig(&preconfig); + + status = _Py_PreInitializeFromPyArgv(&preconfig, args); + if (_PyStatus_EXCEPTION(status)) { return status; } - ``` - -6. Py_RunMain: 파이썬 코드 실행 (5.3) - - **Modules/main.c: L672:L692** - - ```c - int - Py_RunMain(void) - { - int exitcode = 0; - - pymain_run_python(&exitcode); - - if (Py_FinalizeEx() < 0) { - /* Value unlikely to be confused with a non-error exit status or - other special meaning */ - exitcode = 120; - } - - pymain_free(); - - if (_Py_UnhandledKeyboardInterrupt) { - exitcode = exit_sigint(); - } - - return exitcode; + + PyConfig config; + PyConfig_InitPythonConfig(&config); + + /* pass NULL as the config: config is read from command line arguments, + environment variables, configuration files */ + if (args->use_bytes_argv) { + status = PyConfig_SetBytesArgv(&config, args->argc, args->bytes_argv); } - ``` + else { + status = PyConfig_SetArgv(&config, args->argc, args->wchar_argv); + } + if (_PyStatus_EXCEPTION(status)) { + goto done; + } + + status = Py_InitializeFromConfig(&config); + if (_PyStatus_EXCEPTION(status)) { + goto done; + } + status = _PyStatus_OK(); + +done: + PyConfig_Clear(&config); + return status; +} +``` + +
+ +
+6. Py_RunMain: 파이썬 코드 실행 (5.3) + +**Modules/main.c: L672:L692** + +```c +int +Py_RunMain(void) +{ + int exitcode = 0; + + pymain_run_python(&exitcode); + + if (Py_FinalizeEx() < 0) { + /* Value unlikely to be confused with a non-error exit status or + other special meaning */ + exitcode = 120; + } + + pymain_free(); + + if (_Py_UnhandledKeyboardInterrupt) { + exitcode = exit_sigint(); + } + + return exitcode; +} +``` + +
## 5.1 Configuration State (구성 상태) + 파이썬 코드를 실행하기 전에, CPython 런타임은 먼저 **configuration of the runtime**과 **user-provided options**을 설정합니다. Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 587](https://peps.python.org/pep-0587/)). @@ -226,6 +246,7 @@ Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 5 3. The **compiled(=build) configuration** of the CPython Interpreter ### Preinitialization Configuration + **preinitialization configuration**은 operating system 혹은 user environment와 관련된 설정이기 때문에, runtime configuration과 구분됩니다. 다음은 `PyPreConfig`의 세 가지 주요 기능입니다. @@ -234,7 +255,7 @@ Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 5 - [2] LC_CTYPE 로캘(locale)을 system 또는 user-preferred 로캘로 설정하기 > ⭐ **로캘(Locale)이란?** > - 특정 지역, 국가, 또는 문화권에서 사용하는 언어, 통화, 날짜 및 시간 형식과 같은 **지역 설정**을 의미함. - > - 로캘의 정보는 일반적으로 **언어 코드**와 **국가/지역 코드의 조합**으로 표현된다. (ex> **`en-US`**는 미국 영어, **`ko-KR`**은 대한민국 한국어를 나타냄) + > - 로캘의 정보는 일반적으로 **언어 코드**와 **국가/지역 코드의 조합**으로 표현된다. (ex> `en-US`는 미국 영어, `ko-KR`은 대한민국 한국어를 나타냄) - [3] UTF-8 모드 설정하기 ([PEP 540](https://peps.python.org/pep-0540/)) > ⭐ **인코딩** > - 파이썬은 3.7부터 로캘 설정과 상관 없이 UTF-8을 기본 인코딩으로 사용함 @@ -251,11 +272,11 @@ Configuration of the runtime은 세 부분으로 나뉘어 있습니다. ([PEP 5 - `parse_argv`: 0이 아니면 명령줄 인자(command-line arguments)를 사용합니다. - `use_environment`: 0보다 큰 값이면 환경 변수를 사용합니다. - `utf8_mode`: 0이 아니면 UTF-8 모드를 활성화합니다. -- +-
(참고) PyPreConfig 구조체 - - **Include/cpython/initconfig.h: L45-L125** + + **Include/cpython/initconfig.h: L45-L125** ```c /* --- PyPreConfig ----------------------------------------------- */ @@ -354,7 +375,7 @@ Configuration의 두 번째 단계는 **runtime configuration**이다. `PyConfig - **Runtime flags** for modes like `debug` and `optimized` - The **mode of execution**, such as a `script file`, `stdin`, or `module` -- +
-X option 으로 설정 가능한 확장 옵션(extended options) @@ -394,7 +415,7 @@ Configuration의 두 번째 단계는 **runtime configuration**이다. `PyConfig ```
- + - Environment variables for runtime settings ### Setting Runtime Configuration with the Command Line @@ -422,13 +443,12 @@ import zipimport # builtin 모든 `PyConfig` 값에는 같은 순서와 우선순위가 적용됩니다. ![](../images/5_config_and_input/config.png) - ### Viewing Runtime Flags CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CPython의 특정 동작을 제어하는데 사용되는 고급 기능이다. 파이썬 세션 중에, `sys.flags` 네임드 튜플을 통해 runtime flags에 접근할 수 있습니다. - 플래그 사용 X - + ```bash ./python.exe @@ -438,9 +458,9 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP >>> import sys; sys.flags sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=0, hash_randomization=1, isolated=0, dev_mode=False, utf8_mode=0, int_max_str_digits=-1) ``` - + - -X dev 플래그 사용 - + ```bash ./python.exe -X dev @@ -450,10 +470,10 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP >>> import sys; sys.flags sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=0, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=0, int_max_str_digits=-1) ``` - - - `dev_mode`가 `True`로 변경됨. + + - `dev_mode`가 `True`로 변경됨. - -X dev -X utf8 플래그 사용 - + ```bash ./python.exe -X dev -X utf8 @@ -463,19 +483,19 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP >>> import sys; sys.flags sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=0, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=1, int_max_str_digits=-1) ``` - - - `utf8_mode`가 `1`로 변경됨. + + - `utf8_mode`가 `1`로 변경됨. - -X dev -q 플래그 사용 - + ```bash ./python.exe -X dev -q >>> import sys; sys.flags sys.flags(debug=0, inspect=0, interactive=0, optimize=0, dont_write_bytecode=0, no_user_site=0, no_site=0, ignore_environment=0, verbose=0, bytes_warning=0, quiet=1, hash_randomization=1, isolated=0, dev_mode=True, utf8_mode=0, int_max_str_digits=-1) ``` - - - `-q` (=quiet) 모드라서 파이썬 버전이 출력되지 않음. - - `quiet`이 `1`로 변경됨. + + - `-q` (=quiet) 모드라서 파이썬 버전이 출력되지 않음. + - `quiet`이 `1`로 변경됨. ## 5.2 Build Configuration (빌드 구성) @@ -484,30 +504,29 @@ CPython 인터프리터는 많은 runtime flags가 있습니다. 플래그는 CP 다음 명령어로 build configuration을 확인할 수 있습니다. - `./python.exe -m sysconfig` - + ```bash Platform: "macosx-14.4-arm64" Python version: "3.9" Current installation scheme: "posix_prefix" Paths: - data = "/usr/local" - include = "/Users/donghee/Projects/cpython-internals/cpython/Include" - platinclude = "/Users/donghee/Projects/cpython-internals/cpython" - platlib = "/usr/local/lib/python3.9/site-packages" - platstdlib = "/usr/local/lib/python3.9" - purelib = "/usr/local/lib/python3.9/site-packages" - scripts = "/usr/local/bin" - stdlib = "/usr/local/lib/python3.9" + data = "/usr/local" + include = "/Users/donghee/Projects/cpython-internals/cpython/Include" + platinclude = "/Users/donghee/Projects/cpython-internals/cpython" + platlib = "/usr/local/lib/python3.9/site-packages" + platstdlib = "/usr/local/lib/python3.9" + purelib = "/usr/local/lib/python3.9/site-packages" + scripts = "/usr/local/bin" + stdlib = "/usr/local/lib/python3.9" Variables: - ABIFLAGS = "d" - AC_APPLE_UNIVERSAL_BUILD = "0" - AIX_BUILDDATE = "0" - AIX_GENUINE_CPLUSPLUS = "0" - ... + ABIFLAGS = "d" + AC_APPLE_UNIVERSAL_BUILD = "0" + AIX_BUILDDATE = "0" + AIX_GENUINE_CPLUSPLUS = "0" + ... ``` - build configuration은 5가지의 key로 구성되어 있습니다. @@ -518,7 +537,7 @@ build configuration은 5가지의 key로 구성되어 있습니다. - Variables - (참고) CPython을 컴파일 하는 과정 중, `./configure` 스크립트를 실행할 때 포함시켰던 옵션들을 확인할 수 있습니다. `CPPFLAGS`, `LDFLAGS`는 build configuration의 Variables에 포함되어 있습니다. - + ```bash ... CONFIGURE_CPPFLAGS = "-I/opt/homebrew/opt/zlib/include -I/opt/homebrew/opt/xz/include" @@ -526,44 +545,46 @@ build configuration은 5가지의 key로 구성되어 있습니다. CONFIGURE_LDFLAGS = "-L/opt/homebrew/opt/zlib/lib -L/opt/homebrew/opt/xz/lib" ... ``` + + - (참고) `error: 'lzma.h' file not found` 에러로 인해 `./configure` 스크립트를 실행할 때, `xz` 라이브러리를 추가하고 빌드했기 때문에 `xz` 라이브러리에 대한 경로가 포함되어 있습니다. + - 사용했던 스크립트 + + ```bash + # xz 경로 추가 + CPPFLAGS="-I$(brew --prefix zlib)/include -I$(brew --prefix xz)/include" \ + LDFLAGS="-L$(brew --prefix zlib)/lib -L$(brew --prefix xz)/lib" \ + ./configure --with-openssl=$(brew --prefix openssl) --with-pydebug - - (참고) `error: 'lzma.h' file not found` 에러로 인해 `./configure` 스크립트를 실행할 때, `xz` 라이브러리를 추가하고 빌드했기 때문에 `xz` 라이브러리에 대한 경로가 포함되어 있습니다. - - 사용했던 스크립트 - - ```bash - # xz 경로 추가 - CPPFLAGS="-I$(brew --prefix zlib)/include -I$(brew --prefix xz)/include" \ - LDFLAGS="-L$(brew --prefix zlib)/lib -L$(brew --prefix xz)/lib" \ - ./configure --with-openssl=$(brew --prefix openssl) --with-pydebug - - # 빌드 - make -j2 -s - ``` - + # 빌드 + make -j2 -s + ``` Build configuration 항목들은 컴파일 시에(=CPython Interpreter 생성할 때) 결정되는 값으로, 바이너리에 링크할 추가 모듈 선택에 사용됩니다. 예를 들어 디버거와 계측(instrumentation) 라이브러리, 메모리 할당자는 모두 컴파일 시 결정됩니다. 세 단계의 구성(Build Configuration, PyPreConfig, PyConfig)을 모두 완료하면, CPython Interpreter는 입력된 텍스트를 코드로 실행할 수 있게 됩니다. ## 5.3 입력에서 모듈 만들기 + ![](../images/5_config_and_input/input.png) 코드를 실행하려면 먼저 입력을 모듈로 컴파일해야 합니다. - 입력방식 - - 로컬 파일과 패키지 - - 메모리 파이프나 stdin 같은 I/O 스트림 - - 문자열 + - 로컬 파일과 패키지 + - 메모리 파이프나 stdin 같은 I/O 스트림 + - 문자열 이렇게 읽어 들인 입력은 파서를 거쳐 컴파일러로 전달 됩니다. 이렇게 유연한 입력 방식을 제공하기 위해 CPython은 CPython 소스 코드의 상당 분량을 파서의 입력 처리에 사용합니다. ### 연관된 소스 파일 목록 + - Lib > runpy.py : 파이썬 모듈을 임포트 하고 실행하는 표준 라이브러리 모듈 - Modules > main.c : 파일이나 모듈, 입력 스트림 같은 외부 코드 실행을 감싸는 함수 - Programs > python.c : 윈도우나, 리눅스, macOS에서 Python의 진입점. 위의 `main.c` 를 감싸는 역할만 맡음. - Python > pythonrun.c : 명령줄 입력을 처리하는 내부 C API를 감싸는 함수 ### 입력과 파일 읽기 + 1. CPython은 런타임 구성과 명령줄 인자가 준비되면 실행할 코드를 불러옵니다. - 이 작업은 `Modules > main.c > pymain_main()` 에서 실행됩니다. - CPython은 어떤 파이썬 코드를 실행할지 결정하고 이 코드를 메모리에 로드합니다. @@ -573,150 +594,164 @@ Build configuration 항목들은 컴파일 시에(=CPython Interpreter 생성할 ### 명령줄 문자열 입력 > ⭐ **-c 옵션을 사용해 명령줄에서 파이썬 애플리케이션을 실행하는 경우** +> > - python -c “print(2 ** 2)” -- Modules > main.c > pymain_run_command() - - ```c - static int - pymain_run_command(wchar_t *command, PyCompilerFlags *cf) - { - PyObject *unicode, *bytes; - int ret; - - unicode = PyUnicode_FromWideChar(command, -1); - if (unicode == NULL) { - goto error; - } - - if (PySys_Audit("cpython.run_command", "O", unicode) < 0) { - return pymain_exit_err_print(); - } - - bytes = PyUnicode_AsUTF8String(unicode); - Py_DECREF(unicode); - if (bytes == NULL) { - goto error; - } - - ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), cf); - Py_DECREF(bytes); - return (ret != 0); - - error: - PySys_WriteStderr("Unable to decode the command from the command line:\n"); +> + +
+ Modules > main.c > pymain_run_command() + + ```c +static int +pymain_run_command(wchar_t *command, PyCompilerFlags *cf) +{ + PyObject *unicode, *bytes; + int ret; + + unicode = PyUnicode_FromWideChar(command, -1); + if (unicode == NULL) { + goto error; + } + + if (PySys_Audit("cpython.run_command", "O", unicode) < 0) { return pymain_exit_err_print(); } - - ``` - + + bytes = PyUnicode_AsUTF8String(unicode); + Py_DECREF(unicode); + if (bytes == NULL) { + goto error; + } + + ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), cf); + Py_DECREF(bytes); + return (ret != 0); + +error: + PySys_WriteStderr("Unable to decode the command from the command line:\n"); + return pymain_exit_err_print(); +} + +``` + +
+ 1. Modules > main.c에서 pymain_run_command()가 실행되며 -c로 전달된 명령은 C의 wchar_t* 타입 인자로 함수에 전달됩니다. - wchar_t* 타입은 UTF-8 문자를 저장할 수 있기 때문에 CPython에서 저수준 유니코드 데이터를 저장하는 타입으로 사용됩니다. 2. PyUnicode_FromWideChar()를 이용해 wchar_t*를 파이썬 유니코드 문자열 객체로 변환할 수 있습니다. 3. pymain_run_command()는 파이썬 바이트열 객체를 PyRun_SimpleStringFlags()로 넘겨서 실행합니다. 4. PyRun_SimpleStringFlags()는 문자열을 **파이썬 모듈**로 변환하고 실행합니다. -5. 파이썬 모듈을 독립된 모듈로 실행하려면 __main__ 진입점이 필요하기 때문에 PyRun_SimpleStringFlags()가 진입점을 자동으로 추가합니다. -6. 또한 PyRun_SimpleStringFlags()는 가짜 파일 이름을 만들고 파이썬 파서를 실행해서, 문자열에서 추상 구문 트리(abstract syntax tree, AST)를 생성해 모듈로 반환한다. +5. 파이썬 모듈을 독립된 모듈로 실행하려면 **main** 진입점이 필요하기 때문에 PyRun_SimpleStringFlags()가 진입점을 자동으로 추가합니다. +6. 또한 PyRun_SimpleStringFlags()는 가짜 파일 이름을 만들고 파이썬 파서를 실행해서, 문자열에서 추상 구문 트리(abstract syntax tree, AST)를 생성해 모듈로 반환한다. ### 로컬 모듈 입력 +> > ⭐ **-m 옵션을 사용해 모듈을 실행하는 경우** +> > - python -m unittest -1. -m 플래그는 모듈 패키지의 진입점(__main__) 을 실행합니다. +> +1. -m 플래그는 모듈 패키지의 진입점(**main**) 을 실행합니다. - 임포트 라이브러리(importlib)의 검색 매커니즘 덕분에 특정 모듈의 파일 시스템 위치를 기억할 필요는 없습니다. - - Lib > runpy.py > _get_module_details() - - ```python - def _get_module_details(mod_name, error=ImportError): - if mod_name.startswith("."): - raise error("Relative module names not supported") - pkg_name, _, _ = mod_name.rpartition(".") - if pkg_name: - # Try importing the parent to avoid catching initialization errors - try: - __import__(pkg_name) - except ImportError as e: - # If the parent or higher ancestor package is missing, let the - # error be raised by find_spec() below and then be caught. But do - # not allow other errors to be caught. - if e.name is None or (e.name != pkg_name and - not pkg_name.startswith(e.name + ".")): - raise - # Warn if the module has already been imported under its normal name - existing = sys.modules.get(mod_name) - if existing is not None and not hasattr(existing, "__path__"): - from warnings import warn - msg = "{mod_name!r} found in sys.modules after import of " \ - "package {pkg_name!r}, but prior to execution of " \ - "{mod_name!r}; this may result in unpredictable " \ - "behaviour".format(mod_name=mod_name, pkg_name=pkg_name) - warn(RuntimeWarning(msg)) - - try: - spec = importlib.util.find_spec(mod_name) - except (ImportError, AttributeError, TypeError, ValueError) as ex: - # This hack fixes an impedance mismatch between pkgutil and - # importlib, where the latter raises other errors for cases where - # pkgutil previously raised ImportError - msg = "Error while finding module specification for {!r} ({}: {})" - if mod_name.endswith(".py"): - msg += (f". Try using '{mod_name[:-3]}' instead of " - f"'{mod_name}' as the module name.") - raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex - if spec is None: - raise error("No module named %s" % mod_name) - if spec.submodule_search_locations is not None: - if mod_name == "__main__" or mod_name.endswith(".__main__"): - raise error("Cannot use package as __main__ module") - try: - pkg_main_name = mod_name + ".__main__" - return _get_module_details(pkg_main_name, error) - except error as e: - if mod_name not in sys.modules: - raise # No module loaded; being a package is irrelevant - raise error(("%s; %r is a package and cannot " + - "be directly executed") %(e, mod_name)) - loader = spec.loader - if loader is None: - raise error("%r is a namespace package and cannot be executed" - % mod_name) +
Lib > runpy.py > _get_module_details() + + ```python + def _get_module_details(mod_name, error=ImportError): + if mod_name.startswith("."): + raise error("Relative module names not supported") + pkg_name, _, _ = mod_name.rpartition(".") + if pkg_name: + # Try importing the parent to avoid catching initialization errors try: - code = loader.get_code(mod_name) + __import__(pkg_name) except ImportError as e: - raise error(format(e)) from e - if code is None: - raise error("No code object available for %s" % mod_name) - return mod_name, spec, code - - ``` - - - importlib.util.find_spec(mod_name) - - ```python - import importlib.util - importlib.util.find_spec("unittest") - - # Output - # ModuleSpec( - # name='unittest', - # loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fe01263bd30>, - # origin='/home/patrick/miniconda3/envs/py3.10/lib/python3.10/unittest/__init__.py', - # submodule_search_locations=['/home/patrick/miniconda3/envs/py3.10/lib/python3.10/unittest'], - # ) - - importlib.util.find_spec("itertools") - - # Output - # ModuleSpec( - # name='itertools', - # loader=, - # origin='built-in' - # ) - ``` - + # If the parent or higher ancestor package is missing, let the + # error be raised by find_spec() below and then be caught. But do + # not allow other errors to be caught. + if e.name is None or (e.name != pkg_name and + not pkg_name.startswith(e.name + ".")): + raise + # Warn if the module has already been imported under its normal name + existing = sys.modules.get(mod_name) + if existing is not None and not hasattr(existing, "__path__"): + from warnings import warn + msg = "{mod_name!r} found in sys.modules after import of " \ + "package {pkg_name!r}, but prior to execution of " \ + "{mod_name!r}; this may result in unpredictable " \ + "behaviour".format(mod_name=mod_name, pkg_name=pkg_name) + warn(RuntimeWarning(msg)) + + try: + spec = importlib.util.find_spec(mod_name) + except (ImportError, AttributeError, TypeError, ValueError) as ex: + # This hack fixes an impedance mismatch between pkgutil and + # importlib, where the latter raises other errors for cases where + # pkgutil previously raised ImportError + msg = "Error while finding module specification for {!r} ({}: {})" + if mod_name.endswith(".py"): + msg += (f". Try using '{mod_name[:-3]}' instead of " + f"'{mod_name}' as the module name.") + raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex + if spec is None: + raise error("No module named %s" % mod_name) + if spec.submodule_search_locations is not None: + if mod_name == "__main__" or mod_name.endswith(".__main__"): + raise error("Cannot use package as __main__ module") + try: + pkg_main_name = mod_name + ".__main__" + return _get_module_details(pkg_main_name, error) + except error as e: + if mod_name not in sys.modules: + raise # No module loaded; being a package is irrelevant + raise error(("%s; %r is a package and cannot " + + "be directly executed") %(e, mod_name)) + loader = spec.loader + if loader is None: + raise error("%r is a namespace package and cannot be executed" + % mod_name) + try: + code = loader.get_code(mod_name) + except ImportError as e: + raise error(format(e)) from e + if code is None: + raise error("No code object available for %s" % mod_name) + return mod_name, spec, code + + ``` + +
+ +
+ importlib.util.find_spec(mod_name) + + ```python + import importlib.util + importlib.util.find_spec("unittest") + + # Output + # ModuleSpec( + # name='unittest', + # loader=<_frozen_importlib_external.SourceFileLoader object at 0x7fe01263bd30>, + # origin='/home/patrick/miniconda3/envs/py3.10/lib/python3.10/unittest/__init__.py', + # submodule_search_locations=['/home/patrick/miniconda3/envs/py3.10/lib/python3.10/unittest'], + # ) + + importlib.util.find_spec("itertools") + + # Output + # ModuleSpec( + # name='itertools', + # loader=, + # origin='built-in' + # ) + ``` + +
+ 2. runpy를 임포트하고 PyObject_Call()로 해당 모듈을 실행합니다. runpy 모듈은 Lib > runpy.py에 위치한 순수한 파이썬 모듈입니다. - `python -m `을 실행하는 것은 `python -m runpy `을 실행하는 것과 같습니다. - Lib > runpy.py - sys.argv[0]은 스크립트의 이름, sys.argv[1]은 모듈 이름이 들어갑니다. - + ```python if __name__ == "__main__": # Run the module specified as the next command line argument @@ -726,193 +761,203 @@ Build configuration 항목들은 컴파일 시에(=CPython Interpreter 생성할 del sys.argv[0] # Make the requested module sys.argv[0] _run_module_as_main(sys.argv[0]) ``` - + 3. runpy는 세 단계로 모듈을 실행합니다. - 1. 제공된 모듈 이름을 __import__()로 임포트 합니다. - - __import__() 함수는 파이썬의 내장 함수 중 하나입니다. 함수는 모듈을 동적으로 로드하고, 모듈 객체를 반환합니다. - + 1. 제공된 모듈 이름을 **import**()로 임포트 합니다. + - **import**() 함수는 파이썬의 내장 함수 중 하나입니다. 함수는 모듈을 동적으로 로드하고, 모듈 객체를 반환합니다. + ```python mod_name = 'math' mod = __import__(mod_name) print(mod.sqrt(16)) ``` - - 2. __name__ (모듈 이름)을 __main__ 이름 공간에 설정합니다. - 3. __main__ 이름 공간에서 모듈을 실행합니다. + + 2. **name** (모듈 이름)을 **main** 이름 공간에 설정합니다. + 3. **main** 이름 공간에서 모듈을 실행합니다. ### 표준 입력 또는 스크립트 파일 입력 +> > ⭐ **python test.py** - 관련 코드 - - PyRun_SimpleFileExFlags() - - ```c - int - PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit, - PyCompilerFlags *flags) +
+ PyRun_SimpleFileExFlags() + + ```c + int + PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit, + PyCompilerFlags *flags) + { + PyObject *filename_obj = PyUnicode_DecodeFSDefault(filename); + if (filename_obj == NULL) { - PyObject *filename_obj = PyUnicode_DecodeFSDefault(filename); - if (filename_obj == NULL) - { - return -1; - } - int res = pyrun_simple_file(fp, filename_obj, closeit, flags); - Py_DECREF(filename_obj); - return res; + return -1; } - ``` - - - pyrun_simple_file() - - ```c - static int - pyrun_simple_file(FILE *fp, PyObject *filename, int closeit, - PyCompilerFlags *flags) + int res = pyrun_simple_file(fp, filename_obj, closeit, flags); + Py_DECREF(filename_obj); + return res; + } + ``` + +
+ +
+ pyrun_simple_file() + + ```c + static int + pyrun_simple_file(FILE *fp, PyObject *filename, int closeit, + PyCompilerFlags *flags) + { + PyObject *m, *d, *v; + int set_file_name = 0, ret = -1; + + m = PyImport_AddModule("__main__"); + if (m == NULL) + return -1; + Py_INCREF(m); + d = PyModule_GetDict(m); + if (PyDict_GetItemString(d, "__file__") == NULL) { - PyObject *m, *d, *v; - int set_file_name = 0, ret = -1; - - m = PyImport_AddModule("__main__"); - if (m == NULL) - return -1; - Py_INCREF(m); - d = PyModule_GetDict(m); - if (PyDict_GetItemString(d, "__file__") == NULL) - { - if (PyDict_SetItemString(d, "__file__", filename) < 0) - { - goto done; - } - if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) - { - goto done; - } - set_file_name = 1; - } - - int pyc = maybe_pyc_file(fp, filename, closeit); - if (pyc < 0) + if (PyDict_SetItemString(d, "__file__", filename) < 0) { goto done; } - - if (pyc) + if (PyDict_SetItemString(d, "__cached__", Py_None) < 0) { - FILE *pyc_fp; - /* Try to run a pyc file. First, re-open in binary */ - if (closeit) - { - fclose(fp); - } - - pyc_fp = _Py_fopen_obj(filename, "rb"); - if (pyc_fp == NULL) - { - fprintf(stderr, "python: Can't reopen .pyc file\n"); - goto done; - } - - if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) - { - fprintf(stderr, "python: failed to set __main__.__loader__\n"); - ret = -1; - fclose(pyc_fp); - goto done; - } - v = run_pyc_file(pyc_fp, d, d, flags); - } - else - { - /* When running from stdin, leave __main__.__loader__ alone */ - if (PyUnicode_CompareWithASCIIString(filename, "") != 0 && - set_main_loader(d, filename, "SourceFileLoader") < 0) - { - fprintf(stderr, "python: failed to set __main__.__loader__\n"); - ret = -1; - goto done; - } - v = pyrun_file(fp, filename, Py_file_input, d, d, - closeit, flags); - } - flush_io(); - if (v == NULL) - { - Py_CLEAR(m); - PyErr_Print(); goto done; } - Py_DECREF(v); - ret = 0; - done: - if (set_file_name) - { - if (PyDict_DelItemString(d, "__file__")) - { - PyErr_Clear(); - } - if (PyDict_DelItemString(d, "__cached__")) - { - PyErr_Clear(); - } - } - Py_XDECREF(m); - return ret; + set_file_name = 1; } - - ``` - - - pyrun_file() - - ```c - static PyObject * - pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals, - PyObject *locals, int closeit, PyCompilerFlags *flags) + + int pyc = maybe_pyc_file(fp, filename, closeit); + if (pyc < 0) + { + goto done; + } + + if (pyc) { - PyArena *arena = PyArena_New(); - if (arena == NULL) + FILE *pyc_fp; + /* Try to run a pyc file. First, re-open in binary */ + if (closeit) { - return NULL; + fclose(fp); } - - mod_ty mod; - int use_peg = _PyInterpreterState_GET()->config._use_peg_parser; - if (use_peg) + + pyc_fp = _Py_fopen_obj(filename, "rb"); + if (pyc_fp == NULL) { - mod = PyPegen_ASTFromFileObject(fp, filename, start, NULL, NULL, NULL, - flags, NULL, arena); + fprintf(stderr, "python: Can't reopen .pyc file\n"); + goto done; } - else + + if (set_main_loader(d, filename, "SourcelessFileLoader") < 0) { - mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0, - flags, NULL, arena); + fprintf(stderr, "python: failed to set __main__.__loader__\n"); + ret = -1; + fclose(pyc_fp); + goto done; } - - if (closeit) + v = run_pyc_file(pyc_fp, d, d, flags); + } + else + { + /* When running from stdin, leave __main__.__loader__ alone */ + if (PyUnicode_CompareWithASCIIString(filename, "") != 0 && + set_main_loader(d, filename, "SourceFileLoader") < 0) { - fclose(fp); + fprintf(stderr, "python: failed to set __main__.__loader__\n"); + ret = -1; + goto done; } - - PyObject *ret; - if (mod != NULL) + v = pyrun_file(fp, filename, Py_file_input, d, d, + closeit, flags); + } + flush_io(); + if (v == NULL) + { + Py_CLEAR(m); + PyErr_Print(); + goto done; + } + Py_DECREF(v); + ret = 0; + done: + if (set_file_name) + { + if (PyDict_DelItemString(d, "__file__")) { - ret = run_mod(mod, filename, globals, locals, flags, arena); + PyErr_Clear(); } - else + if (PyDict_DelItemString(d, "__cached__")) { - ret = NULL; + PyErr_Clear(); } - PyArena_Free(arena); - - return ret; } - - ``` - + Py_XDECREF(m); + return ret; + } + + ``` + +
+ +
+ pyrun_file() + + ```c + static PyObject * + pyrun_file(FILE *fp, PyObject *filename, int start, PyObject *globals, + PyObject *locals, int closeit, PyCompilerFlags *flags) + { + PyArena *arena = PyArena_New(); + if (arena == NULL) + { + return NULL; + } + + mod_ty mod; + int use_peg = _PyInterpreterState_GET()->config._use_peg_parser; + if (use_peg) + { + mod = PyPegen_ASTFromFileObject(fp, filename, start, NULL, NULL, NULL, + flags, NULL, arena); + } + else + { + mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0, + flags, NULL, arena); + } + + if (closeit) + { + fclose(fp); + } + + PyObject *ret; + if (mod != NULL) + { + ret = run_mod(mod, filename, globals, locals, flags, arena); + } + else + { + ret = NULL; + } + PyArena_Free(arena); + + return ret; + } + + ``` + +
+ 1. Python > pythonrun.c의 PyRun_SimpleFileExFlags()를 호출합니다. 2. PyRun_SimpleFileExFlags() - .pyc 파일 경로면 run_pyc_file()을 호출합니다. - 스크립트 파일(.py) 경로면 PyRun_FileExFlags()를 호출합니다. - - | python처럼 파일 경로가 stdin이면 stdin을 파일 핸들로 취급하고 PyRun_FileExFlags()를 호출합니다. + - python처럼 파일 경로가 stdin이면 stdin을 파일 핸들로 취급하고 PyRun_FileExFlags()를 호출합니다. 3. PyRun_FileExFlags()는 파일에서 파이썬 모듈을 생성하고 run_mod()로 보내 실행합니다. ### 컴파일된 바이트 코드 입력 @@ -926,7 +971,7 @@ Build configuration 항목들은 컴파일 시에(=CPython Interpreter 생성할 ## References - CPython 파헤치기 내용 정리 블로그 - - [https://velog.io/@1eejuhwany/나도-CPython-파헤치기3](https://velog.io/@1eejuhwany/%EB%82%98%EB%8F%84-CPython-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B03) + - [https://velog.io/@1eejuhwany/나도-CPython-파헤치기3](https://velog.io/@1eejuhwany/%EB%82%98%EB%8F%84-CPython-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B03) - 파이썬 모듈 - - [https://docs.python.org/3.9/tutorial/modules.html](https://docs.python.org/3.9/tutorial/modules.html) - - [https://realpython.com/python-modules-packages](https://realpython.com/python-modules-packages/) \ No newline at end of file + - [https://docs.python.org/3.9/tutorial/modules.html](https://docs.python.org/3.9/tutorial/modules.html) + - [https://realpython.com/python-modules-packages](https://realpython.com/python-modules-packages/) diff --git a/book/images/5_config_and_input/mermaid.png b/book/images/5_config_and_input/mermaid.png new file mode 100644 index 0000000000000000000000000000000000000000..4b3dbab24c63a1117c4ad8d3e0fed834c8a3d8d0 GIT binary patch literal 21080 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