나도 CPython 파헤치기3

이주환·2023년 8월 19일
0

안녕 씨파이썬?

목록 보기
3/3

구성과 입력

👏 원리 설명

코드가 실행 가능한 단계 까지 도달하기의 과정 중 인터프리터가 파이썬을 실행 하는 명령어를 살펴 보자면, 이렇게 처리 할 수 있다

  1. python3 -c
  2. python3 -m
  3. python3 { file }
  4. cat { file } | python3
  5. repl
    ...

이렇게 실행 하는 인터프리터는 아래와 같은 세 가지 요소가 필요하다.
1. 실행 할 모듈
2. 변수 등을 저장할 상태
3. 활성화 된 옵션의 구성

CPython code guide: PEP7

  • 공개 된 함수가 정적 함수가 아니라면 Py 접두사를 붙힌다
  • Py_FatalError 처럼 전역 서비스루틴에는 Py_ 접두사를 붙힌다
    등, ...

이렇게 위와 같은 코드 컨밴션이 존재 하지만 PEP7은 8과 달리 검사 도구가 많지 않다. 그렇기 때문에 코어 개발자가 PEP7을 준수 하는지 매번 확인 해야 한다. 그 중 유일한 자동화 검사 도구 중 smell 을 사용 하여 위에 명시 된 또는, 더 많은 코드 가이드를 검사 할 수 있다.

👏 원리 설명

5.1 구성 상태


코드를 실행 하기 전 CPython 런타임은 사용자 옵션과 구성을 설정한다.

  1. PyPreConfig 딕셔너리 초기화 구성
  2. PyConfig 런타임 구성
  3. CPython 인터프리터에 같이 컴파일된 구성

👉 PyPreConfig, PyConfig 구조체는 Include > cpython > initcofig.h 에서 정의함

5.1.1 딕셔너리 초기화 구성

딕셔너리 초기화 구성은 사용자 환경 또는 운영체제와 관련된 구성이기 때문
에 런타임 구성과 구분된다.

PyPreConfig 의 주요 세 가지 기능

  • 파이썬 메모리 할당자 설정하기
  • LC_CTYPE(locale)을 시스템 또는 사용자 선호 로캘로 구성하기
  • UTF-8 모드 설정하기

PyPreConfig 구조체

  • allocator: PYMEM_ALLOCATOR_MALLOC 같은 값을 사용 해서 메모리 할당자를 선택한다.
  • configure_locale: LC_CTYPE 로켈을 사용자 선호 로켈로 설정한다. 0으로 설정 하면 coerce_c_localecoerce_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이 아니면 CLI args 사용
  • use_environment: 0보다 큰 값이면 환경 변수 사용
  • utf8_mode: 0이 아니면 UTF-8 모드 활성화

PyPreConfig와 연관된 소스 파일 목록

  • Python > initconfig.c : 시스템 환경에서 불러온 구성을 명령줄 플래그와 결합
  • Include > cpython > initconfig.h : 초기화 구성 구조체 정의

5.1.3 런타임 구성 구조체

딕셔너리 초기화 구성 다음 단계로 PyConfig 런타임 구성 구조체는 다음과 같은 값들을 포함한다.

  • 디버그나 최적화 같은 실행 모드 플래그
  • 스크립트 파일이나 stdin, 모듈 등 실행 모드
  • -X <option> 으로 설정 가능한 확장 옵션
  • 런타임 설정을 위한 환경 변수

런타임 구성 데이터는 CPython 런타임 기능의 활성화 여부를 결정한다.

5.1.4 명령줄로 런타임 구성 설정하기

./python.exe -v -c "print('hello world')"

import _frozen_importlib # frozen
import _imp # builtin
import '_thread' # <class '_frozen_importlib.BuiltinImporter'>
import '_warnings' # <class '_frozen_importlib.BuiltinImporter'>
import '_weakref' # <class '_frozen_importlib.BuiltinImporter'>
import '_io' # <class '_frozen_importlib.BuiltinImporter'>
import 'marshal' # <class '_frozen_importlib.BuiltinImporter'>
import 'posix' # <class '_frozen_importlib.BuiltinImporter'>
import '_frozen_importlib_external' # <class '_frozen_importlib.FrozenImporter'>
# installing zipimport hook
...
Python 3.9.17+ (heads/3.9-dirty:38b489bae8, Aug 12 2023, 17:34:53) 
[Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
hello world

상세모드 설정에 대한 우선순위

  1. config > verbose: 기본값이 -1로 소스코드에 하드 코딩 되어있음
  2. PYTHONVERBOSE 환경 변수를 config -> verbose를 설정하는 데 사용
  3. 환경 변수가 없으면 기본 값인 -1을 사용
  4. Python > initconfig.c 의 config_parse_cmdline()은 명시된 명령줄 플래그를 사용 해 모드를 설정함
  5. _Py_GetGlobalVaribalesAsDict()가 값을 전역 변수 Py_VerboseFlag로 복사

이렇게, 모든 PyConfig 값에는 같은 순서와 우선순위가 적용된다.

5.1.5 런타임 플래그 확인하기

CPython 인터프리터의 런타임 플래그는 CPython의 동작들을 끄고 켜는 데 사용하는 고급 기능이다.
파이썬 세션 중에 sys.flags 네임드 튜플로 상세 모드나 메시지 없는 모드 같은 런타임 플래그에 접근이 가능하다.

👏 원리 설명

5.2 빌드 구성


빌드 구성은 최상위 폴더인 pyconfig.h에 정의한다. 이 파일은 macOS, Linux용 빌드 과정 중 ./configure 단계나 왼도우의 build.bat 실행 중에 자동으로 생성 된다.

빌드 구성 확인

./python.exe -m sysconfig

빌드 구성 항목들은 컴파일 시에 결정 되는 값들로 바이너리에 링크 할 추가 모듈 선택에 사용됨.

예를 들어, 디버거와 계측(instrucmentation) 라이브러리, 메모리 할당자는 모두 컴파일 시 결정된다.

  • 세 단계의 구성을 모두 완료 하면 CPython 인터프리터는 입력된 텍스트를 코드로 실행 할 수 있다.

5.3 입력에서 모듈 만들기


코드를 실행하려면 먼저 입력을 모듈로 컴파일 해야 한다.

입력 방식

  • 로컬 파일과 패키지
  • 메모리 파이프나 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 > python_main() 이 실행된다.
  1. 그 다음 CPython은 불러온 코드를 새로 생성 된 Py?Config 인스턴스에 설정된 옵션들과 함께 실행된다.

명령줄 문자열 입력

CPython은 -c 옵션을 사용 해 명령줄에서 작은 파이썬 어플리케이션을 실행 할 수 있다.

마치 이렇게 ./python.exe -c "print(2 ** 2)"

문자열을 입력 받아 실행하는 과정

  1. Modules>main.c>pymain_run_command()가 실행 되며 -c로 전달 된 명령은 C의 wchar_t*타입 인자로 함수에 전달된다.

    • wchar_t* 타입은 UTF-8 문자를 저장 할 수 있기 때문에 CPython에서 저수준 유니코드 데이터를 저장하는 타입으로 사용된다.
  2. pymain_run_command()는 파이썬 바이트열 객체를 PyRun_SimpleStringFlags()로 넘겨 실행

    • PyRun_SimpleStringFlags()는문자열을 파이썬 모듈로 변환 하고 실행
    • 파이썬 모듈을 독립된 모듈로 실행 하려면 __main__ 진입점이 필요하기 때문에 PyRun_SimpleStringFlags() 가 진입점을 자동으로 추가함
    • PyRun_SimpleStringFlags()는 딕셔너리와 모듈을 만든 후 PyRun_StringFlags()를 호출 함
    • PyRun_SimpleStringFlags()는 가짜 파일 이름을 만들고 파이썬 파서를 실행 하여 문자열에서 추상 구문 트리(abstract syntax tree, AST)를 생성 해 모듈로 반환함.

-m 플래그는 모듈 패키지의 진입점(__main__)을 실행한다. 이 때 해당 모듈은 sys.path에서 검색 하는데 이 때 임포트 라이브러리의 검색 매너키즘 덕분에 특정 모듈의 파일 시스템 위치를 기억 할 필요는 없다.

runpy 모듈은 Lib > runpy.py 에 위치한 순수한 파이썬 모듈이다.
python -m <module> 을 실행 하는 것은 python -m runpy <module> 을 실행 하는 것과 같다. 이렇게, runpy 모듈은 운영체제에서 모듈을 찾아 실행하는 프로세스를 추상화 한다.

runpy의 모듈 실행 과정
1. 제공된 모듈 이름을 __import__()로 임포트한다.
2. __name__(모듈 이름)을 __main__이름 공간에 설정한다.
3. __main__ 이름 공간에서 모듈을 실행한다.

  • 파이썬 파일을 실행 할 때 간혹 이런 구문을 본 적 있을 것이다.
    if __name__ == "__main__":
        pass

python test.py 처럼 첫 번째 인자가 파일명이라면 CPython은 파일 핸들을 열어 Python > pythonrun.c 의 PyRun_SimpleFileExFlags()로 핸들을 넘긴다.
이 함수는 세 종류의 파일 경로를 처리 할 수 있다.
1. .pyc 파일 경로면 run_pyc_file()을 호출한다.
2. 스크립트 파일(.py) 경로면 PyRun_FileExFlags()를 호출한다.
3. command | python처럼 파일 경로가 stdin이면 stdin을 파일 핸들로 취급 하고 PyRun_FileExFlags()를 호출한다.

PyRun_FileExFlags()는 파일에서 파이썬 모듈을 생성 하고 run_mod()로 보내 실행한다.

CPython 컴파일러는 스크립트가 호출 될 때 마다 파싱하는 대신, 디스크의 코드 객체 구조체에 컴파일 한 코드를 캐시한다.
그렇기 때문에, .pyc 라는 파일이 컴파일러에 의해 생성 되었다면 파이썬 코드가 수정 되지 않는 이상 빠르게 실행 시킬 수 있는 것이다.

요약


파이썬이 다양한 구성 옵션을 로딩 하는 방식과 코드를 인터프리터에 입력하는 방식들에 대해 공부했다.
파이썬은 이렇게 유연한 입력 방식 덕분에 다양한 어플리케이션에 적합한 언어이다.

  • 명령줄 유틸리티
  • 웹 서버 등 오랫동안 실행되는 네트워크 어플리케이션
  • 짧은 스크립트
profile
안녕하새우

0개의 댓글