포스트

프로그래밍 언어와 구현체 — CPython은 왜 컴파일러이자 인터프리터인가

프로그래밍 언어와 구현체 — CPython은 왜 컴파일러이자 인터프리터인가

Python은 인터프리터 언어라고 배웠는데, CPython은 C로 작성되어 있고 내부에서 컴파일까지 수행한다. 그렇다면 CPython은 컴파일러일까, 인터프리터일까?

이 질문은 언어의 규칙, 그 규칙을 실현한 구현체, 코드를 변환하고 실행하는 방식을 구분하면 풀린다.

1. 언어와 구현체는 무엇이 다른가

프로그래밍 언어는 문법과 의미를 정의한다. 구현체(Implementation)는 그 언어로 작성한 프로그램을 실제로 처리하도록 만든 소프트웨어다.

1
2
3
4
Python 언어(문법·의미)
    ├─ CPython: C로 작성된 대표 구현체
    ├─ PyPy: JIT를 활용하는 구현체
    └─ Jython: JVM 위에서 실행하는 구현체

같은 언어라도 구현체에 따라 내부 구조, 성능, 지원 플랫폼과 확장 방식이 달라질 수 있다. 언어 명세가 같다는 사실이 모든 구현체의 세부 동작과 호환성을 보장하지는 않는다.

C도 마찬가지다. GCC와 Clang은 C 코드를 처리하는 서로 다른 컴파일러다. 다만 컴파일러는 언어 구현을 담당하는 구성요소이며, 구현체 전체가 항상 컴파일러 하나만으로 구성되는 것은 아니다.

2. 컴파일러와 인터프리터는 무엇을 구분하는가

두 용어는 언어의 종류보다 처리 방식과 역할을 가리킨다.

구분질문역할
Compiler코드를 어떤 표현으로 바꾸는가?소스 코드 등을 다른 표현으로 변환
Interpreter명령을 누가 실행하는가?프로그램의 명령을 해석하며 실행
VM(Virtual Machine)어떤 추상 실행 기계를 제공하는가?바이트코드 등 특정 명령 체계를 실행할 환경 제공
Runtime실행 중 무엇을 지원하는가?객체·메모리·예외·스케줄링 등 구현별 실행 지원

컴파일 결과가 반드시 기계어일 필요는 없다. 바이트코드나 중간 표현일 수도 있다. 반대로 인터프리터가 소스 코드의 각 줄을 직접 읽어야 하는 것도 아니다. 바이트코드를 해석할 수도 있다.

따라서 컴파일과 인터프리트는 상호 배타적인 언어 분류가 아니다. 한 구현체에서 함께 사용될 수 있다.

3. CPython 내부에서는 무엇이 일어나는가

일반적인 CPython 실행 경로를 단순화하면 다음과 같다.

1
2
3
4
5
6
7
8
9
10
Python Source (.py)
    │ 컴파일
    ▼
Python Bytecode
    │ 실행
    ▼
CPython Virtual Machine
    │ C로 구현된 실행 엔진이 명령 처리
    ▼
프로그램 동작

CPython은 Python 소스를 바이트코드로 컴파일하고, 내부 가상 머신에서 이를 인터프리트한다. 그래서 CPython은 컴파일러 기능과 인터프리터 기능을 포함한 Python 구현체라고 부르는 것이 정확하다.

바이트코드는 실행 중 메모리에 존재할 수 있으며, 항상 .pyc 파일로 저장되는 것은 아니다. .pyc는 주로 import 과정에서 재사용을 위한 캐시로 생성될 수 있다.

CPython이 C로 작성됐다는 뜻

1
2
3
4
5
6
7
CPython 구현 소스(C)
    │ C 컴파일러로 빌드
    ▼
CPython 실행 프로그램(기계어)
    │ Python 코드를 입력받음
    ▼
Python Bytecode 생성·실행

CPython 자체가 C로 작성되었다는 말과 Python 소스가 C 소스로 변환된다는 말은 다르다. 일반적인 CPython 실행은 Python 코드를 C 소스로 변환하지 않는다.

4. 다른 언어 구현과 비교하면

언어구현체·도구 예시대표적인 실행 경로
C / C++GCC, Clang소스 → 네이티브 기계어 → CPU
Rustrustc소스 → 네이티브 기계어 → CPU
GoGo 컴파일러·런타임소스 → 네이티브 기계어 + 런타임 지원
Javajavac, HotSpot JVM소스 → JVM 바이트코드 → 인터프리터/JIT
PythonCPython소스 → 바이트코드 → CPython VM
PythonPyPyPython 실행 + JIT 최적화
JavaScriptV8파싱·바이트코드 실행 + JIT 최적화

표는 대표적인 경로를 단순화한 것이다. 예를 들어 GCC와 Clang은 C뿐 아니라 여러 언어를 지원하고, JavaScript나 Python에도 다른 구현체와 실행 방식이 존재한다.

5. 구현체를 비교할 때 볼 질문

언어 이름만 외우기보다 다음 축을 따라 비교한다.

  1. 규칙: 어떤 언어 명세와 의미를 구현하는가?
  2. 변환: 소스가 AST, 바이트코드, 중간 표현, 기계어 중 무엇으로 바뀌는가?
  3. 실행: CPU가 직접 실행하는가, VM이나 인터프리터가 실행하는가?
  4. 시점: 실행 전에 컴파일하는가, 실행 중 JIT 컴파일하는가?
  5. 지원: 메모리 관리, GC, 예외, 스레드 등은 누가 맡는가?

이 질문으로 CPython, JVM, V8, Go 런타임, WebAssembly 런타임을 같은 좌표에서 비교할 수 있다.

6. 연결해서 읽기

핵심은 Python이라는 언어가 CPython이라는 프로그램과 동일하지 않다는 점이다. 언어는 규칙이고, 구현체는 그 규칙을 실제로 처리하는 소프트웨어다. 컴파일러와 인터프리터는 구현체 안에서 서로 다른 역할을 맡을 수 있다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.