Your Guide to the CPython Source Code

(korean translation)

Gab git.
Gab git.
Nov 2 · 17 min read

파이썬의 어떤 부분들이 마법처럼 보이세요? 예를 들면, dict가 어떤 item을 찾는게 list을 훑어보는 것보다 훨씬 더 빠른 것? generator가 값을 yield할 때마다 변수의 상태를 기억하는 것? 또는 다른 언어처럼 메모리를 할당하지 않아도 되는 이유? CPYthon은 알고보면 가장 인기 있는 Python 런타임 언어로서 사람이 읽을 수 있는 C와 Python 코드로 쓰여져 있습니다. 이 튜토리얼에서는 CPYthon 소스 코드를 살펴보겠습니다. 이 기사는 CPython의 내부적인 개념들, 그것들이 어떻게 작동하는지를 다루려고 합니다.

You’ll learn how to:

  • Read and navigate the source code
  • Compile CPython from source code
  • Navigate and comprehend the inner workings of concepts like lists, dictionaries, and generators
  • Run the test suite
  • Modify or upgrade components of the CPython library to contribute them to future versions

Part 1: Introduction to CPython

console에 Python을 입력하거나 python.org에서 Python 을 설치하고 실행하면CPython을 실행하는 것입니다. CPython은 여러 개발자들이 만든 많은 Python runtime중 하나입니다. 여기서 런타임이란 프로그램이 실행되는 환경을 말합니다. 당신이 들었을지도 모르는 다른 runtime으로는 PyPy, Cython, Jython이 있습니다.

CPython의 특이한 점은 모든 Python runtime에서 사용하는 runtime과 언어적 특성을 공유하고 있든 것입니다. CPython은 Python의 공식적인 reference implementation입니다.

여러분의 컴퓨터에 배포된 Python안에 무엇이 있을지 예상해보세요

  • 파일이나 모듈없이 Python을 치면 대화형 프롬프트가 나타납니다.
  • json 같은 표준 라이브러리에서 built-in module을 가져올 수 있다.
  • pip를 사용하여 외부에서 패키지를 설치할 수 있다.
  • Unittest 라이브러리를 사용하여 테스트를 할 수 있다.

이것들은 CPYthon distribution의 일부입니다. Compiler만 있는 것이 아닙니다.

Note:
This article is written against version 3.8.0b4 of the CPython source code.

What’s in the Source Code?

CPython souce distribution은 대부분의 tool, library, component 들에 있습니다. 먼저 compiler를 살펴보겠습니다.

CPython source code 복사본을 다운로드하기 위해서 git 을 사용해 보겠습니다.

$ git clone https://github.com/python/cpython
$ cd cpython
$ git checkout v3.8.0b4

새로이 다운로드 한 CPython directory 안에는 sub directory들이 있습니다.

cpython/

├── Doc ← Source for the documentation
├── Grammar ← The computer-readable language definition
├── Include ← The C header files
├── Lib ← Standard library modules written in Python
├── Mac ← macOS support files
├── Misc ← Miscellaneous files
├── Modules ← Standard Library Modules written in C
├── Objects ← Core types and the object model
├── Parser ← The Python parser source code
├── PC ← Windows build support files
├── PCbuild ← Windows build support files for older Windows versions
├── Programs ← Source code for the python executable and other binaries
├── Python ← The CPython interpreter source code
└── Tools ← Standalone tools useful for building or extending Python

이제 소스 코드에서 CPYthon을 컴파일해보겠습니다. 이 단계에서는 C 컴파일러와 일부 빌드 도구가 필요하며, 사용 중인 운영 체제에 따라 다를 수 있습니다.

Compiling CPython (macOS)

CPython을 MacOS에 컴파일하는 것은 간단합니다. 먼저 필수 C 컴파일러 툴킷이 필요합니다. Command Line Development Tools 라는 앱을 App Store를 통해 업데이트할 수 있습니다. 터미널에서 설치를 수행해야 합니다.

터미널을 열고 다음과 같은 명령어를 주어 C compiler와 toolkit을 설치하세요.

$ xcode-select --install

이 명령은 Git, Make, GNU C compiler를 포함한 일련의 도구를 다운로드하고 설치하라는 프롬프트와 함께 나타납니다. PyPi.org 웹사이트에서 패키지를 가져오는 데 사용할 OpenSSL의 작업 사본이 필요할 것입니다. 나중에 이 빌드를 사용하여 추가 패키지를 설치하려는 경우 SSL 검증이 필요합니다.

Mac OS 에서 OpenSSL을 설치 하는 가장 간단한 방법은 HomeBrew를 사용하는 것입니다. HomeBrew가 이미 설치되어 있는 경우 다음 kein install 명령을 사용하여 CPython의 dependency들을 설치할 수 있습니다.

brew install openssl xz zlib

이제 dependency들이 확보되었으므로 configure script를 실행하여 홈브루가 설치한 위치를 검색하고 디버그 후크를 사용 가능으로 설정하여 SSL support를 사용하도록 설정하십시오. --with-pydebug

$ CPPFLAGS="-I$(brew --prefix zlib)/include" \
LDFLAGS="-L$(brew --prefix zlib)/lib" \
./configure --with-openssl=$(brew --prefix openssl) --with-pydebug

이제 저장소 root에 빌드 프로세스를 자동화하는 데 사용할 수 있는 Makefile이 생성됩니다.. ./configure는 한 번만 실행하면 됩니다. 다음을 실행하여 CPython binary 파일을 만들 수 있습니다.

$ make -j2 -s

여기서에 -j2 는 두 작업을 동시에 실행할 수 있음을 허용하는 옵션입니다. 만일 core를 4개를 가지고 있다면 4로 바꿀 수도 있습니다. -s 옵션은 console에서 실행되는 모든 명령어의 출력을 막을 수 있습니다.

빌드하는 동안 약간의 오류가 발생할 수 있습니다. 또한 모든 패키지를 빌드할 수 없습니다. 예를 들면 _dbm, _sqlite3, _uuid, nis, ossaudiodev, spwd 및 _tkinter는 이 일련의 명령으로 구축할 수 없습니다. 만약 당신이 그 package에 대해서 개발할 계획이 없다면 괜찮습니다. 자세한 사항은 dev guide에서 참고바랍니다.

빌드 하는데 몇 분 정도 소요됩니다. 그리고 python.exe라는 binary 파일을 생성됩니다. 소스 코드를 변경할 때마다 동일한 명령어를 사용하여 빌드해야 합니다. python.exe 이진법은 CPython의 디버그 이진법입니다.

$ ./python.exe
Python 3.8.0b4 (tags/v3.8.0b4:d93605de72, Aug 30 2019, 10:00:03)
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>

Note

MacOS 빌드에는 .exe용 파일 확장명이 있습니다, 이것은 윈도우 binary가 아닙니다. MacOS는 대/소문자를 구분하지 않는 파일 시스템을 가지고 있고, 사람들이 Python/ 디렉토리를 실수로 참조하는 것을 원하지 않았기 때문에, 애매함을 피하기 위해 .exe가 추가되었습니다. 나중에 make install 을 실행하거나 make altinstall를 실행하면 파일 이름을 다시 Python으로 바꾸게 됩니다.

Compiling CPython (Linux)

Linux에서 우선 make, gcc, configure, and pkgconfig 를 설치해야 합니다. Fedora Core, RHEL, CentOS, or other yum 기반 시스템들은 다음과 같은 명령어를 사용하고

sudo yum install yum-utils

Debian, Ubuntu, or 다른 apt 기반 시스템들은 다음과 같은 명령어를 사용합니다.

sudo apt install build-essential

Fedora Core, RHEL, CentOS, or other yum 기반 시스템들의 설치가 끝나면 빌드를 진행합니다.

sudo yum-builddep python3

Debian, Ubuntu, or 다른 apt 기반 시스템들은 다음과 같습니다.

$ sudo apt install libssl-dev zlib1g-dev libncurses5-dev \
libncursesw5-dev libreadline-dev libsqlite3-dev libgdbm-dev \
libdb5.3-dev libbz2-dev libexpat1-dev liblzma-dev libffi-dev

이제 아까와 마찬가지로 configure script를 실행하여 디버그 후크를 사용 가능 하도록 설정합니다. — with-pydebug

$ ./configure --with-pydebug

결과를 확인하여 Open SSL 지원에 대해서 Yes로 되어있는지 확인하고, 되어있지 않다면 OpenSSL용 header를 설치하는 방법에 대한 지침을 확인하셔야 합니다. 이제 생성된 Makefile 을 실행하여 CPython binary를 빌드하세요.

make -j2 -s

빌드 하는데 몇 분 정도 소요됩니다. 그리고 python.exe라는 binary 파일을 생성됩니다. python.exe 이진법은 CPython의 디버그 이진법입니다.

What Does a Compiler Do?

컴파일러의 목적은 언어를 다른 언어로 변경하는 것입니다. 변역기라고 생각하면 됩니다. 대부분의 컴파일러는 시스템에서 바로 실행시킬 수 있는 저급 기계어로 컴파일 할 것입니다. 다른 컴파일러들은 virtual machine에 의해 실행될 중간 언어로 컴파일합니다. 컴파일러를 선택할 때 결정해야 할 중요한 점은 system portability requirements입니다. 예를 들면 Java 및 .NET CLR은 컴파일된 코드가 여러 시스템 아키텍처에서 이동할 수 있도록 중간 언어로 컴파일됩니다. C, Go, C++, Pascal은 작업한 것과 유사한 시스템에서만 동작하는 low-level 실행 파일로 컴파일됩니다.

Python application은 일반적으로 source code로 배포되기 때문에 Python runtime의 역할은 Python source code를 변환하는 것이고 한번에 실행하는 것입니다. 내부적으로 CPython runtime은 code를 컴파일 합니다. Python을 가장 많이 오해하는 부분이 Python이 interpreter언어라는 것입니다. 실제로는 컴파일 언어입니다.

Python code는 기계어로 컴파일되지 않습니다. CPython이 이해할 수 있는 bytecode라고 불리는 중간 레벨 언어로 컴파일 됩니다. 이 컴파일 code는 실행 시 캐시되어 숨겨진 directory에 .pyc 파일로 저장됩니다. 만약에 같은 Python application을 소스코드 변경없이 두번 실행하면 .pyc 파일 덕에 더 빠르게 실행됩니다.

Why Is CPython Written in C and Not Python?

CPython의 C는 C 프로그래밍 언어를 참조하고 있습니다. CPython의 컴파일러는 순수 C언어로 작성되었습니다. 그러나 대부분의 라이브러리 모듈은 순수 Python이거나 C와 Python이 혼재되어 작성되었습니다.

그렇다면 왜 CPython은 Python이 아니라 C로 작성되었을까요? 답은 컴파일러가 작동하는 방법에 있습니다. 컴파일러는 두가지 유형이 있습니다.

  1. self-host compiler: Go 컴파일러와 같이 컴파일하는 언어로 작성된 컴파일러
  2. Source-to-Source Compiler는 컴파일러를 이미 가지고 있는 다른 언어로 작성된 컴파일러

만약 처음부터 새로운 프로그래밍 언어를 만드는 거라면, 당신은 당신의 컴파일러를 컴파일하기 위해 실행 가능한 응용프로그램이 필요합니다. 따라서 어떤 것이든 실행하기 위해서 컴파일러가 필요하기 때문에, 새로운 언어가 개발되면, 그것들은 종종 더 오래되고 더 확립된 언에서 가져와서 사용하게 됩니다.

좋은 예로 Go 프로그래밍 언어가 있습니다. 처음 Go 컴파일러는 C로 작성되었습니다. 이후 Go를 컴파일할 수 있게 되자, Go에서 컴파일러를 다시 작성하였습니다.

CPython은 C 의 유산들을 가지고 있습니다. ssl 모듈이나 소켓 모듈과 같은 많은 표준 라이브러리 모듈이 낮은 수준의 운영 체제 API에 접근하기 위해 C로 쓰여졌습니다. 네트워크 소켓 생성, 파일 시스템과의 작업 또는 디스플레이와의 상호작용을 위한 윈도 및 리눅스 커널의 API 역시 C로 작성되었습니다.

물론 PyPy라고 불리는 Python으로 쓰여진 Python 컴파일러도 있습니다.

The Python Language Specification

CPython 소스 코드에 포함되는 것은 Python 언어의 정의입니다. 이것은 Python interpreter가 사용하는 reference specification 입니다. 여기서 reference specification이란 사람과 기계가 읽을 수 있는 format을 말합니다. specification 안에는 Python언어, 허용된 사항, 그리고 각 문장이 어떻게 행동해야 하는지에 대한 상세한 설명이 있습니다.

Documentation

Doc/reference directory안에 위치한 파일은 재구축된 Python 언어의 기능에 대한 설명들이 있습니다.

cpython/Doc/reference
|
├── compound_stmts.rst
├── datamodel.rst
├── executionmodel.rst
├── expressions.rst
├── grammar.rst
├── import.rst
├── index.rst
├── introduction.rst
├── lexical_analysis.rst
├── simple_stmts.rst
└── toplevel_components.rst

compound statements에 대한 설명서인 complex_stmts.rst 내부에서 설명을 정의하는 간단한 예를 볼 수 있습니다.

with statement 은 Python에서 다양하게 사용될 수 있는데, 간단히 인스턴스나 중첩된 code block을 생성할 수 있습니다.

with x():
...

키워드로 결괏값을 변수에 할당할 수도 있습니다.

with x() as y:
...

comma로 context managers를 쉼표로 묶을 수도 있습니다.

with x() as y, z() as jk:
...

다음은 Python 언어를 컴퓨터가 읽을 수 있는 문서를 살펴볼 것입니다.

Grammar

문서에는 인간이 읽을 수 있는 언어 사양이 포함되어 있으며, 기계가 읽을 수 있는 사양은 Grammar/Grammar라는 단일 파일에 수록되어 있습니다.

문법 파일은 BNF(Backus-Naur Form)라는 문맥으로 작성됩니다. BNF는 파이썬에 특정되지 않으며, 다른 많은 언어에서 grammer의 표기법으로 종종 사용됩니다.

프로그래밍 언어에서의 문법 구조 개념은 1950년대 Syntactic Structures에 대한 노암 Ch스키의 연구에서 영감을 받은 것입니다. Python의 문법 파일은 정규 표현 구문과 함께 확장 BNF(Extended-BNF) 규격을 사용합니다. 따라서 문법 파일에서는 다음과 같이 사용할 수 있습니다.

  • * for repetition
  • + for at-least-once repetition
  • [] for optional parts
  • | for alternatives
  • () for grouping

with 문을 grammer file에서 찾고 싶다면 80번째 줄에서 with 문에 대한 정의를 볼 수 있습니다.

with_stmt: 'with' with_item (',' with_item)*  ':' suite
with_item: test ['as' expr]

인용문 안의 모든 것은 문자열 리터럴이며, 이것이 키워드를 정의하는 방법입니다. 따라서 with_stmt는 다음과 같이 지정되어야 합니다.

  1. with로 시작하라.
  2. 다음으로 with_item이 오고 as는 option이다.
  3. 하나 이상의 항목이 오면 , 로 구분하라.
  4. : 끝내라.
  5. 이하 suit

이 두 줄에는 몇 가지 다른 정의가 언급되어 있습니다.

  • suit는 하나 이상의 문장이 있는 코드 블록을 말합니다.
  • test는 평가되는 간단한 문장을 말합니다.
  • expr 은 간단한 표현식을 말합니다.

만약 최근에 문법이 어떻게 사용되는지에 대한 예를 보고 싶다면, PEP 572, this Git commit 을 참고하세요.

Using pgen

문법 파일 자체는 파이썬 컴파일러에 의해 결코 사용되지 않습니다. 대신 pgen이라는 tool로 만들어진 parser table을 사용합니다. pgen은 문법 파일을 읽고 그것을 parser table 로 변환합니다. 만일 문법 파일을 변경하고서 파서 테이블을 재생성하려면 Python을 다시 컴파일해야 합니다.

pgen이 작동하는 것을 보기 위해서, Python 문법의 일부를 바꿔보겠습니다. 51줄 전후로pass statement의 정의를 볼 수 있습니다.

pass_stmt: 'pass'>>>>>>>>>>>>>>>>>>>>>>>>>>>>pass_stmt: 'pass' | 'proceed'

앞서 말한대로 grammer 파일의 수정본을 반영하기 위해서 rebuild 할 필요가 있습니다. macOS와 Linux에서 regen-grammar를 실행하여 변경된 문법 파일에 대해 pgen을 실행하면 됩니다. 윈도우에는 공식적으로 지원되는 pgen 실행 방법이 없지만, 내 포크를 복제하고 build.bat — regen을 PCBuild 디렉토리에서 실행할 수 있습니다.

아래와 유사한 출력이 표시되어야 하며, 새 Include/graminit.h 및 Python/graminit.c 파일이 생성되었음을 표시합니다.

# Regenerate Doc/library/token-list.inc from Grammar/Tokens
# using Tools/scripts/generate_token.py
...
python3 ./Tools/scripts/update_file.py ./Include/graminit.h ./Include/graminit.h.new
python3 ./Tools/scripts/update_file.py ./Python/graminit.c ./Python/graminit.c.new

재생성된 parser table을 사용하여 CPython을 다시 컴파일하고 새 구문을 확인하십시오. 이전에 운영 체제에 사용한 것과 동일한 컴파일 단계를 거쳐야 합니다. 코드가 성공적으로 컴파일되면 새로운 CPython binary를 실행하고 REPL을 시작할 수 있습니다. REPL에서 이제 함수를 정의해 볼 수 있으며, 암호문을 사용하는 대신 Python 문법으로 컴파일한 proceed키워드를 사용해 보세요.

Python 3.8.0b4 (tags/v3.8.0b4:d93605de72, Aug 30 2019, 10:00:03) 
[Clang 10.0.1 (clang-1001.0.46.4)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def example():
... proceed
...
>>> example()

Tokens

Gab git.

Written by

Gab git.

hello world!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade