CPython Çalışma Zamanında Python Kodunun AST → Bytecode → PVM Yolculuğu
Bir Python geliştiricisi için aşağıdaki soru oldukça önemlidir:
CPython çalışma zamanı ortamında bir
.pydosyası çalıştırıldığında, kaynak kodun AST seviyesinden Bytecode seviyesine dönüştürülmesi ve Python Virtual Machine (PVM) üzerinde yürütülmesi sürecinde ilk gerçekleşen aşama nedir?
Doğru cevap:
Kaynak kodun tokenize edilerek Parse Tree'ye dönüştürülmesi ve ardından AST'nin oluşturulmasıdır.
Bu cevabı gerçekten anlayabilmek için CPython'un bir Python dosyasını nasıl çalıştırdığına derinlemesine bakalım.
Genel Mimari
Bir Python dosyası çalıştırıldığında CPython aşağıdaki adımları izler:
Python Kaynak Kodu (.py)
│
▼
Tokenizer
│
▼
Parser
│
▼
Parse Tree
│
▼
AST
│
▼
Bytecode Compiler
│
▼
Bytecode
│
▼
PVM (Python Virtual Machine)
│
▼
Sonuç
1. Kaynak Kodun Okunması
Örnek Python kodumuz:
x = 10
y = 20
print(x + y)
CPython ilk olarak dosyayı diskten okur.
Bu aşamada elimizde sadece metin vardır.
"x = 10\ny = 20\nprint(x+y)"
Henüz Python neyin değişken, neyin sayı olduğunu bilmez.
2. Tokenization (Lexical Analysis)
İlk gerçek derleme adımı budur.
Tokenizer kaynak kodu küçük anlamlı parçalara ayırır.
Örneğimiz:
x = 10
şuna dönüşür:
NAME x
OP =
NUMBER 10
NEWLINE
Tüm program için:
NAME(x)
=
NUMBER(10)
NAME(y)
=
NUMBER(20)
NAME(print)
(
NAME(x)
+
NAME(y)
)
Bu parçalara Token denir.
Token Nedir?
Token programlama dilinin en küçük anlamlı birimidir.
Örneğin:
if x > 5:
tokenlere ayrılır:
IF
NAME(x)
GREATER_THAN
NUMBER(5)
COLON
3. Parsing
Tokenlar tek başlarına yeterli değildir.
Parser bunların gramer kurallarına uygun olup olmadığını kontrol eder.
Örneğin:
x = 10
geçerli bir ifadedir.
Ama:
= x 10
gramere uymaz.
Parser hata verir:
SyntaxError
Parse Tree Oluşturulması
Parser başarılı olursa bir Parse Tree üretir.
Örneğin:
x = 10
için:
Assignment
├── Variable(x)
└── Number(10)
Bu yapı Python dilinin dilbilgisine bağlıdır.
Çok detaylıdır.
Bu nedenle bir sonraki aşamada sadeleştirilir.
4. AST (Abstract Syntax Tree)
AST = Soyut Sözdizimi Ağacı
Parse Tree'deki gereksiz gramer detayları kaldırılır.
Örnek:
x = 10
AST:
Assign
├── Name(x)
└── Constant(10)
Daha karmaşık örnek:
result = x + y * 2
AST:
Assign
├── Name(result)
└── BinOp(+)
├── Name(x)
└── BinOp(*)
├── Name(y)
└── Constant(2)
AST artık programın mantıksal yapısını temsil eder.
AST'yi Python İçinden Görmek
import ast
code = """
x = 10
y = 20
print(x+y)
"""
tree = ast.parse(code)
print(ast.dump(tree, indent=4))
Çıktı:
Module(
body=[
Assign(...),
Assign(...),
Expr(...)
]
)
CPython'un derleyicisi de tam olarak bu AST üzerinden çalışır.
5. Bytecode Üretimi
AST daha sonra Bytecode Compiler'a gönderilir.
Burada Python'a özgü sanal makine komutları üretilir.
Örnek:
x = 10
print(x)
Bytecode'u Görüntüleme
import dis
def demo():
x = 10
print(x)
dis.dis(demo)
Çıktı benzeri:
LOAD_CONST 10
STORE_FAST x
LOAD_GLOBAL print
LOAD_FAST x
CALL
POP_TOP
RETURN_VALUE
Artık Python kodu yoktur.
Artık Bytecode vardır.
Bytecode Nedir?
Bytecode:
- Makine kodu değildir
- CPU tarafından çalıştırılamaz
- Platform bağımsızdır
- PVM tarafından çalıştırılır
Bir anlamda:
Python Kaynak Kodu
↓
Bytecode
↓
Makine Kodu
şeklinde düşünebiliriz.
6. .pyc Dosyası Oluşturulması
CPython bytecode'u cacheleyebilir.
__pycache__/
altında:
main.cpython-313.pyc
gibi dosyalar oluşur.
Bunlar doğrudan Bytecode içerir.
Ama dikkat:
.pyc = Bytecode
değildir tamamen.
İçinde ayrıca:
- sürüm bilgisi
- timestamp
- metadata
bulunur.
7. Python Virtual Machine (PVM)
Bytecode artık çalıştırılmaya hazırdır.
Devreye PVM girer.
PVM:
LOAD_CONST
STORE_FAST
LOAD_FAST
CALL
RETURN_VALUE
gibi komutları tek tek işler.
PVM Döngüsü
Basitleştirilmiş hali:
while True:
instruction = next_instruction()
execute(instruction)
Gerçekte CPython'un kalbi olan:
ceval.c
dosyasında çalışır.
Temel yapı:
for (;;) {
opcode = NEXTOP();
switch(opcode) {
...
}
}
şeklindedir.
Buna:
Interpreter Loop
veya
Evaluation Loop
denir.
Stack-Based Mimari
PVM bir stack kullanır.
Örnek:
print(2 + 3)
Bytecode:
LOAD_CONST 2
LOAD_CONST 3
BINARY_ADD
PRINT
Stack hareketi:
[]
[2]
[2,3]
[5]
[]
Bu yüzden Python VM'e:
Stack Based Virtual Machine
denir.
Gerçek Süreç Adım Adım
Kod:
x = 10
y = 20
print(x+y)
CPython içinde:
Adım 1
Kaynak kod okunur.
Source Code
↓
Adım 2
Tokenization
NAME
NUMBER
OPERATOR
↓
Adım 3
Parsing
Parse Tree
↓
Adım 4
AST oluşturulur.
Abstract Syntax Tree
↓
Adım 5
Compiler çalışır.
Bytecode
↓
Adım 6
Bytecode oluşturulur.
.pyc
↓
Adım 7
PVM bytecode'u yürütür.
Execution
↓
Adım 8
Program sonucu üretilir.
Mülakat ve Sertifikasyon İçin Kritik Bilgi
Sorudaki ifade:
"AST seviyesinden Bytecode seviyesine dönüştürülmesi ve PVM üzerinde çalıştırılması sürecinde ilk gerçekleşen aşama hangisidir?"
cevaplanırken birçok kişi doğrudan:
AST → Bytecode
dönüşümünü düşünür.
Fakat AST'nin oluşabilmesi için önce:
- Source Code okunur
- Tokenization yapılır
- Parse Tree oluşturulur
- AST oluşturulur
Bu nedenle süreçte ilk gerçekleşen kritik derleme aşaması:
Kaynak kodun tokenize edilmesi, Parse Tree'nin oluşturulması ve ardından AST'nin inşa edilmesidir.
Bu aşama tamamlanmadan Bytecode üretimine geçilemez ve PVM'in çalıştıracağı hiçbir komut oluşamaz.
Özet
CPython'un yürütme zinciri:
.py Source Code
│
▼
Tokenization
│
▼
Parsing
│
▼
Parse Tree
│
▼
AST
│
▼
Bytecode Compiler
│
▼
Bytecode (.pyc)
│
▼
Python Virtual Machine (PVM)
│
▼
Program Execution
Dolayısıyla sorudaki doğru yaklaşım:
İlk olarak kaynak kod tokenize edilir, Parse Tree oluşturulur ve bu yapıdan AST üretilir. Bytecode üretimi ve PVM yürütmesi daha sonraki aşamalardır.