CPython에서는 다양한 방식으로 파이썬 코드를 실행할 수 있음
- python -c로 파이썬 문자열을 통해 코드 실행
$ ./python.exe -c "print(3 + 5)"
> 8
- python -m으로 모듈 실행하기
- python <file>로 파이썬 코드가 들어 있는 파일(경로 명시) 실행하기
- cat <file> | python처럼 파이썬 코드를 stdin으로 python에 파이프하기
- REPL에서 한번에 하나씩 명령 실행
- C API를 이용해 파이썬을 임베디드 환경으로 사용하기
인터프리터가 파이썬 코드를 실행하려면 세가지 요소가 필요
- 실행할 모듈(modules)
- 변수 등을 저장할 상태(state)
- 활성화된 옵션 등의 구성(configuration)
5.1 구성 상태
- 파이썬 코드를 실행하기 전 CPython 런타임은 먼저 사용자 옵션과 구성을 설정
- CPython 구성은 세 부분으로 나뉨 (PEP587)
- PyPreConfig 딕셔너리 초기화 구성
- PyConfig 런타임 구성
- CPython 인터프리터에 같이 컴파일된 구성
PyPreConfig와 PyConfig 구조체는 Include>cpython>initconfig.h에서 정의
/* --- PyPreConfig ----------------------------------------------- */
typedef struct {
int _config_init; /* _PyConfigInitEnum value */
/* Parse Py_PreInitializeFromBytesArgs() arguments?
See PyConfig.parse_argv */
int parse_argv;
/* If greater than 0, enable isolated mode: sys.path contains
neither the script's directory nor the user's site-packages directory.
Set to 1 by the -I command line option. If set to -1 (default), inherit
Py_IsolatedFlag value. */
int isolated;
/* If greater than 0: use environment variables.
Set to 0 by -E command line option. If set to -1 (default), it is
set to !Py_IgnoreEnvironmentFlag. */
int use_environment;
/* Set the LC_CTYPE locale to the user preferred locale? If equals to 0,
set coerce_c_locale and coerce_c_locale_warn to 0. */
int configure_locale;
/* Coerce the LC_CTYPE locale if it's equal to "C"? (PEP 538)
Set to 0 by PYTHONCOERCECLOCALE=0. Set to 1 by PYTHONCOERCECLOCALE=1.
Set to 2 if the user preferred LC_CTYPE locale is "C".
If it is equal to 1, LC_CTYPE locale is read to decide if it should be
coerced or not (ex: PYTHONCOERCECLOCALE=1). Internally, it is set to 2
if the LC_CTYPE locale must be coerced.
Disable by default (set to 0). Set it to -1 to let Python decide if it
should be enabled or not. */
int coerce_c_locale;
/* Emit a warning if the LC_CTYPE locale is coerced?
Set to 1 by PYTHONCOERCECLOCALE=warn.
Disable by default (set to 0). Set it to -1 to let Python decide if it
should be enabled or not. */
int coerce_c_locale_warn;
#ifdef MS_WINDOWS
/* If greater than 1, use the "mbcs" encoding instead of the UTF-8
encoding for the filesystem encoding.
Set to 1 if the PYTHONLEGACYWINDOWSFSENCODING environment variable is
set to a non-empty string. If set to -1 (default), inherit
Py_LegacyWindowsFSEncodingFlag value.
See PEP 529 for more details. */
int legacy_windows_fs_encoding;
#endif
/* Enable UTF-8 mode? (PEP 540)
Disabled by default (equals to 0).
Set to 1 by "-X utf8" and "-X utf8=1" command line options.
Set to 1 by PYTHONUTF8=1 environment variable.
Set to 0 by "-X utf8=0" and PYTHONUTF8=0.
If equals to -1, it is set to 1 if the LC_CTYPE locale is "C" or
"POSIX", otherwise it is set to 0. Inherit Py_UTF8Mode value value. */
int utf8_mode;
/* If non-zero, enable the Python Development Mode.
Set to 1 by the -X dev command line option. Set by the PYTHONDEVMODE
environment variable. */
int dev_mode;
/* Memory allocator: PYTHONMALLOC env var.
See PyMemAllocatorName for valid values. */
int allocator;
} PyPreConfig;
5.1.1 딕셔너리 초기화 구성
- 사용자 환경 또는 운영 체제와 관련된 구성이기 때문에 런타임 구성과 구분됨
PyPreConfig 의 세가지 주요 기능
- 파이썬 메모리 할당자 설정
- LC_CTYPE 로캘(locale)을 시스템 또는 사용자 선호 로캘로 구성하기
- UTF-8 모드 설정하기(PEP540)
아래와 같은 int 타입 필드들을 포함
- allocator: PYMEM_ALLOCATOR_MALLOC 같은 값을 사용해 메모리 할당자를 선택
- configure_locale: LC_CTYPE 로캘을 사용자 선호 로캘로 설정. 0으로 설정하면 coerce_c_locale과 coerce_c_locale_warn을 0으로 설정
- coerce_c_locale: 2로 설정하면 C 로캘을 강제로 적용. 1로 설정하면 LC_CTYPE을 읽은 후 강제로 적용할지 결정
- coerce_c_locale_warn: 0이 아니면 C로캘이 강제로 적용될 때 경고가 발생
- dev_mode: 개발 모드를 활성화
- isolated: 격리 모드를 활성화. sys.path에 스크립트 디렉토리와 사용자의 사이트 패키지 디렉토리가 포함되지 않음
- legacy_windows_fs_encoding: 0이 아니면 UTF-8 모드를 비활성화하고 파이썬 파일 시스템 인코딩을 mbcs로 설정(윈도우 전용)
- parse_argv: 0이 아니면 명령줄 인자를 사용
- use_environment: 0보다 큰 값이면 환경 변수를 사용
- utf8_mode_: 0이 아니면 UTF-8모드를 활성화
5.1.2 연관된 소스 파일 목록
PyPreConfig와 연관된 소스 파일 목록
- Python/initconfig.c : 시스템 환경에서 불러온 구성을 명령줄 플래그와 결합
- Include/cpython/initconfig.h : 초기화 구성 구조체를 정의
5.1.3 런타임 구성 구조체
PyConfig 런타임 구성 구조체는 아래 값들을 포함
- ‘디버그’나 ‘최적화’같은 실행 모드 플래그
- 스크립트 파일이나 stdin, 모듈 등 실행 모드
- -X <option>으로 설정 가능한 확장 옵션
- 런타임 설정을 위한 환경 변수
런타임 구성 데이터는 CPython 런타임 기능의 활성화 여부를 결정
5.1.4 명령줄로 런타임 구성 설정하기
- 파이썬은 다양한 명령줄 인터페이스 옵션을 제공하는데 예로 상세 모드(verbose) 가 있고 주로 개발자 대상으로 활용됨
- -v 플래그로 상세모드를 활성화하면 파이썬은 모듈을 로딩할 때마다 화면에 메시지를 출력
$ ./python.exe -v -c "print('hello world')"
...
# installing zipimport hook
import 'time' # <class '_frozen_importlib.BuiltinImporter'>
import 'zipimport' # <class '_frozen_importlib.FrozenImporter'>
# installed zipimport hook
- 사용자 site-packages 디렉토리에 설치된 모듈들과 시스템 호나경의 모든 항목을 임포트하면 수백줄이 출력됨
- 아래는 상세 모드 설정에 대한 우선순위
- config→verbose의 기본값은 -1로 소스 코드에 하드코딩되어 있음
- PYTHONVERBOSE 환경 변수를 config→verbose를 설정하는데 사용
- 환경 변수가 없으면 기본값인 -1을 사용
- Python/initconfig.c의 config_parse_cmdline()은 명시된 명령중 플래그를 사용해 모드를 설정
- _Py_GetGlobalVariablesAsDict() 가 값을 전역 변수 Py_VerboseFlag로 복사
모든 Pyconfig값에는 같은 순서와 우선순위가 적용됨
5.1.5 런타임 플래그 확인하기
- Cpython 인터프리터의 런타임 플래그는 CPython의 동작들을 끄고 켜는데 사용하는 고급 기능
- X 옵션을 이용해 다양한 기능을 활성화할 수 있음
- 파이썬 세션 중에 sys.flags 네임드 튜플로 상세 모드나 메시지 없는(quite)모드 같은 런타임 플래그에 접근할 수 있음
- sys._xoptions 딕셔너리에 모든 -X 플래그를 확인할 수 있음
-X dev -X utf8 플래그 사용: 기본 인코딩으로 utf-8로 설정
./python.exe -X dev -X utf8
Python 3.9.19+ (heads/3.9:a04a0f6585, Mar 25 2024, 08:21:31)
[Clang 15.0.0 (clang-1500.3.9.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 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)
5.2 빌드 구성
- 런타임 구성을 Include/cpython/initconfig.h에서 정의하듯이 빌드 구성은 최상의 폴더의 pyconfig.h에서 정의
- 이 파일은 macOS나 리눅스용 빌드 과정중 ./configure 단계에서 자동으로 생성됨
- 다음 명령으로 빌드 구성을 확인할 수 있음
$ ./python.exe -m sysconfig
Platform: "macosx-14.5-arm64"
Python version: "3.9"
Current installation scheme: "posix_prefix"
Paths:
data = "/usr/local"
include = "/Users/woo-seongchoi/Desktop/SE/cpython-internals/cpython/Include"
platinclude = "/Users/woo-seongchoi/Desktop/SE/cpython-internals/cpython"
...
- 빌드 구성항목들은 컴파일 시에 결졍되는 값으로 바이너리에 링크할 추가 모듈 선택애 사용됨
- 예를 들어 디버거나 계측(instrumentation) 라이브러리, 메모리 할당자는 모두 컴파일 시 결정됨
- 세 단계의 구성(Build configuration, PyPreconfig, PyConfig)을 모두 완료하면 CPython 인터프리터는 입력된 텍스트를 코드로 실행할 수 있음
5.3 입력에서 모듈 만들기
코드를 실행하려면 먼저 입력을 모듈로 컴파일해야 함. 입력 방식은 아래와 같이 여러가지가 있음
- 로컬 파일과 패키지
- 메모리 파이프나 stdin 같은 I/O 스트림
- 문자열
읽어 들인 입력은 파서를 거쳐 컴파일러에 전달됨
유연한 입력 방식을 제공하기 위해 CPython은 소스 코드의 상당 부분을 파서의 입력 처리에 사용됨
5.3.1 연관된 소스 파일 목록
- Lib > runpy.py : 파이썬 모듈을 임포트 하고 실행하는 표준 라이브러리 모듈
- Modules > main.c : 파일이나 모듈, 입력 스트림 같은 외부 코드 실행을 감싸는 함수
- Programs > python.c : 윈도우나, 리눅스, macOS에서 Python의 진입점. 위의 main.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);
}
#endif
- Python > pythonrun.c : 명령줄 입력을 처리하는 내부 C API를 감싸는 함수
5.3.2 입력과 파일 읽기
- CPython은 런타임 구성과 명령줄 인자가 준비디ㅗ면 실행할 코드를 불러옴
- 이 작업은 modules/main.c의 pymain_main()이 실행
main.c에서 pymain_main()
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();
}
- 다음으로 CPython은 불러온 코드를 새로 생성된 PyConfig 인스턴스에 설정된 옵션들과 함께 실행함
5.3.3 명령줄 문자열 입력
- -c 옵션을 사용해 명령줄에서 작은 파이썬 애플리케이션을 실행할 수 있음 (ex) print(2 ** 2))
❯ ./python.exe -c "print(2 ** 2)"
4
- Modules/main.c에서 pymain_run_command()가 실행되며 -c로 전달된 명령은 C의 wchar_t* 타입 인자로 함수에 전달됨
- wchar_t* 타입은 UTF-8 문자를 저장할 수 있기 때문에 CPython에서 저수준 유니코드 데이터를 저장하는 타입으로 사용됨
- Objects/unicodeobject.c의 헬퍼 함수 PyUnicode_FromWideChar()를 이용해 wchar_t*를 파이썬 유니코드 문자열로 변환할 수 있음. 유니코드 문자열→ UTF-8로 인코딩하려면 PyUnicode_AsUTF8String() 을 사용
pymain_run_command() 함수 코드
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();
}
pymain_run_command()는 파이썬 바이트열 객체를 PyRun_SimpleStringFlags()로 넘겨서 실행
static int
pymain_run_command(wchar_t *command, PyCompilerFlags *cf)
{
PyObject *unicode, *bytes;
int ret;
unicode = PyUnicode_FromWideChar(command, -1);
bytes = PyUnicode_AsUTF8String(unicode);
ret = PyRun_SimpleStringFlags(PyBytes_AsString(bytes), cf);
}
Python/pythonrun.c의 PyRun_SimpleStringFlags()는 문자열을 파이썬 모듈로 변환하고 실행
int
PyRun_SimpleStringFlags(const char *command, PyCompilerFlags *flags)
{
PyObject *m, *d, *v;
m = PyImport_AddModule("__main__"); // (1)
if (m == NULL)
return -1;
d = PyModule_GetDict(m);
v = PyRun_StringFlags(command, Py_file_input, d, d, flags); // (2)
if (v == NULL) {
PyErr_Print();
return -1;
}
Py_DECREF(v);
return 0;
}
(1) 파이썬 모듈을 독립된 모듈로 실행하려면 __main__ 진입점이 필요하기 때문에 PyRun_simpleStringFlags()가 진입점을 자동으로 추가
(2) PyRun_simpleStringFlags() 는 딕셔너리와 모듈을 만든 후 PyRun_StringFlags() 를 호출해, 가짜 파일 이름을 만들고 파이썬 파서를 실행해 문자열에서 추상 구문 트리(abstract syntax tree, AST)를 생성해 모듈로 반환
5.3.4 로컬 모듈 입력
- 파이썬의 -m 옵션과 모듈 이름으로 파이썬 명령을 실행할 수도 있음
예시
$ ./python.exe -m unittest
- 위 명령으로 표준 라이브러리의 unittest 모듈을 실행할 수 있음
- -m 옵션은 모듈 패키지의 진입점(__main__)을 실행함. 이 때 해당 모듈은 sys.path에서 검색
- 임포트 라이브러리(importlib)의 검색 메커니즘 덕분에 unittest 모듈의 파일 시스템 위치를 기억할 필요 없음
- CPython은 표준 라이브러리 모듈 runpy를 임포트하고 PyObject_Call()로 해당 모듈을 실행. runpy 모듈은 Lib/runpy.py에 위치한 순수한 파이썬 모듈임.
- 임포트는 Python/import.c의 C API 함수 PyImport_ImportModule()이 담당
- python -m <module> 을 실행하는 것은 python -m runpy <module> 을 실행하는 것과 같음. runpy 모듈은 운영체제에서 모듈을 찾아 실행하는 프로세스를 추상화
runpy는 세단계의 모듈을 실행 …?
1. 제공된 모듈 이름을 __import__()로 임포트
2. __name__(모듈 이름)을 __main__ 이름 공간에서 설정
3. __main__ 이름 공간에서 모듈을 실행
5.3.5 표준 입력 또는 스크립트 파일 입력
- python test.py처럼 python을 실행할 때 첫 번째 인자가 파일명이라면 Cpython은 파일 핸들을 열어 Python/pythonrun.c의 PyRun_SimpleFileExFlags()로 핸들을 넘김
이 함수는 세 종류의 파일 경로를 처리할 수 있음
- .pyc 파일 경로면 run_pyc_file()을 호출
- 스크립트 파일(.py) 경로면 PyRun_FileExFlags()를 호출
- <command> | python 처럼 파일 경로가 stdin이면 stdin을 파일 핸들로 취급하고 PyRun_FileExFlags()를 호출
- stdin이나 스크립트 파일의 경우 CPython은 파일 핸들을 Python/pythonrun.c의 PyRunFileExFlags()로 넘기는데 이는 PyRun_SimpleStringFlags()와 비슷. CPython은 파일 핸들을 PyParser_ASTFromFileObject()로 전달
- PyRun_SimpleStringFlags() 처럼 PyRunFileExFlags()는 파일에서 파이썬 모듈을 생성하고 run_mode()로 보내 실행
5.3.6 컴파일된 바이트 코드 입력
- python을 .pyc 파일 경로와 함께 실행하면 CPython은 파일을 텍스트 파일로 불러와 파싱하는 대신 .pyc 파일에서 디스크에 기록된 코드 객체를 찾음
- PyRun_SimpleFileExFlags()에는 .pyc파일 경로를 처리하는 부분이 있음
- Python/pythonrun.c의 run_pyc_file()은 파일 핸들을 사용해 .pyc 파일에서 코드 객체를 마셜링함
- 마셜링은 파일 내용을 메모리를 복사하여 특정 데이터 구조로 변환하는 것을 의미
- CPython 컴파일러는 스크립트가 호출될 때 마다 파싱하는 대신 디스크의 코드 객체 구조체에 컴파일한 코드를 캐싱
- 메모리에 마셜링된 코드 객체는 Python/ceval.c 를 호출하는 run_eval_code_obj()로 전달되어 실행됨
'Python' 카테고리의 다른 글
[책리뷰] 파이썬 클린 코드 Chapter 3. 좋은 코드의 일반적인 특징 (0) | 2024.11.10 |
---|---|
[책리뷰] CPython 파헤치기 4장. 파이썬 언어와 문법 (0) | 2024.11.10 |
pathlib 모듈 (0) | 2024.10.20 |
[책리뷰] 파이썬 클린 코드 Chapter 2. Pythonic 코드 (2) (0) | 2024.09.29 |
[책리뷰] 파이썬 클린 코드 Chapter 2. Pythonic 코드 (1) (0) | 2024.09.29 |