프로그래밍 언어와 구현체 — 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 |
| Rust | rustc | 소스 → 네이티브 기계어 → CPU |
| Go | Go 컴파일러·런타임 | 소스 → 네이티브 기계어 + 런타임 지원 |
| Java | javac, HotSpot JVM | 소스 → JVM 바이트코드 → 인터프리터/JIT |
| Python | CPython | 소스 → 바이트코드 → CPython VM |
| Python | PyPy | Python 실행 + JIT 최적화 |
| JavaScript | V8 | 파싱·바이트코드 실행 + JIT 최적화 |
표는 대표적인 경로를 단순화한 것이다. 예를 들어 GCC와 Clang은 C뿐 아니라 여러 언어를 지원하고, JavaScript나 Python에도 다른 구현체와 실행 방식이 존재한다.
5. 구현체를 비교할 때 볼 질문
언어 이름만 외우기보다 다음 축을 따라 비교한다.
- 규칙: 어떤 언어 명세와 의미를 구현하는가?
- 변환: 소스가 AST, 바이트코드, 중간 표현, 기계어 중 무엇으로 바뀌는가?
- 실행: CPU가 직접 실행하는가, VM이나 인터프리터가 실행하는가?
- 시점: 실행 전에 컴파일하는가, 실행 중 JIT 컴파일하는가?
- 지원: 메모리 관리, GC, 예외, 스레드 등은 누가 맡는가?
이 질문으로 CPython, JVM, V8, Go 런타임, WebAssembly 런타임을 같은 좌표에서 비교할 수 있다.
6. 연결해서 읽기
- 프로그램 실행 전체 지도 — 소스 코드에서 CPU까지의 전체 흐름
- 컴파일러와 링커 — 네이티브 코드 생성과 링크
- Runtime과 언어별 실행 모델 — VM, JIT, Runtime의 실행 책임
- JVM 구조와 작동 원리 — 바이트코드 실행의 다른 사례
핵심은 Python이라는 언어가 CPython이라는 프로그램과 동일하지 않다는 점이다. 언어는 규칙이고, 구현체는 그 규칙을 실제로 처리하는 소프트웨어다. 컴파일러와 인터프리터는 구현체 안에서 서로 다른 역할을 맡을 수 있다.