Python ทำงานภายในอย่างไร: จากโค้ดสู่ Bytecode
คุณเคยสงสัยไหมว่าเมื่อคุณเขียนโค้ด Python แล้วกดรัน (Run) เกิดอะไรขึ้นเบื้องหลัง? หลายคนมักได้ยินว่า Python เป็นภาษา interpreted language แต่ความจริงซับซ้อนกว่านั้นมาก และรู้หรือไม่ว่า Python executes code 10x slower than C++ นั่นจึงเป็นเหตุผลว่าทำไมการเข้าใจกลไกภายในของ Python จึงสำคัญ ถ้าคุณอยากเขียนโค้ดที่มีประสิทธิภาพ หรือแก้ไขปัญหาบั๊กแปลกๆ ได้อย่างมืออาชีพ บทความนี้จะพาคุณเจาะลึกการทำงานของ Python ตั้งแต่โค้ดของคุณถูกอ่านไปจนถึงการประมวลผลเป็นคำสั่งจริงๆ
- Python’s name comes from Monty Python’s Flying Circus, not the snake, decided by Guido van Rossum in 1991
- CPython compiles your code to bytecode with 163 opcodes before execution, not direct interpretation
- Python 3.11 released October 2022 runs 10-60% faster than 3.10 using a specialized adaptive interpreter
ทำไมต้องเรียนรู้เรื่องนี้?
- เพิ่มประสิทธิภาพ: เมื่อเข้าใจกระบวนการ คุณจะสามารถเขียนโค้ดที่รันได้เร็วขึ้น ใช้ทรัพยากรน้อยลง
- แก้ไขปัญหาได้ดีขึ้น: การเข้าใจว่าโค้ดถูกแปลงเป็นอะไรช่วยให้คุณเดาจุดผิดพลาดได้ง่ายขึ้น โดยเฉพาะปัญหาที่เกี่ยวกับ Python Libraries สำหรับ Data Science ที่ซับซ้อน
- เข้าใจภาษา: ความรู้เชิงลึกเช่นนี้จะเปิดโลกทัศน์ให้คุณมอง Python ไม่ใช่แค่เครื่องมือ แต่เป็นวิศวกรรมที่ซับซ้อน
- พัฒนาทักษะ: ทำให้คุณเป็นนักพัฒนาที่สามารถคุยเรื่อง Technical ในระดับสูงได้ และไม่แน่คุณอาจจะเจอ AI Tools ที่ดีที่สุดสำหรับผู้ประกอบการ มาช่วยงานในอนาคต
สิ่งที่ต้องเตรียม
- Python ติดตั้งในเครื่อง: แนะนำเวอร์ชัน 3.8 ขึ้นไป
- VS Code: Integrated Development Environment (IDE) ยอดนิยมสำหรับการเขียนโค้ด
- ความเข้าใจพื้นฐาน Python: รู้จักตัวแปร ฟังก์ชัน และโครงสร้างควบคุม (loops, conditions)
ขั้นตอนโดยละเอียด: Python ทำงานอย่างไร
1. Lexer (Scanner): การแบ่งโค้ดเป็น Token
เมื่อคุณรันไฟล์ .py ของคุณ สิ่งแรกที่ Python ทำคือการอ่านโค้ดทีละตัวอักษรและแบ่งออกเป็น “tokens” หรือหน่วยเล็กๆ ที่มีความหมาย เช่น คำสงวน (keywords), ชื่อตัวแปร (identifiers), ตัวเลข (numbers), เครื่องหมาย (operators) เป็นต้น กระบวนการนี้เรียกว่า Lexing หรือ Scanning โดย Python lexer tokenizes source code into discrete tokens like NAME, NUMBER, NEWLINE using a deterministic finite automaton.
ลองดูตัวอย่างโค้ดง่ายๆ:
def greet(name):
message = "Hello, " + name
return message
Lexer จะแยกโค้ดนี้ออกเป็น tokens ประมาณนี้:
def(KEYWORD)greet(NAME)((OP)name(NAME))(OP):(OP)NEWLINEINDENTmessage(NAME)=(OP)"Hello, "(STRING)+(OP)name(NAME)NEWLINEreturn(KEYWORD)message(NAME)NEWLINEDEDENT
คุณสามารถใช้โมดูล tokenize ใน Python เพื่อดู token เหล่านี้ได้:
import tokenize
from io import BytesIO
code = """
def greet(name):
message = "Hello, " + name
return message
"""
# tokenize ต้องการข้อมูลเป็น bytes
tokens = list(tokenize.tokenize(BytesIO(code.encode('utf-8')).readline))
for token in tokens:
print(f"Type: {tokenize.tok_name[token.type]}, Value: '{token.string}'")
ผลลัพธ์ที่คาดการณ์: คุณจะเห็นลิสต์ของ Token แต่ละตัวพร้อมประเภทและค่าของมัน
2. Parser: การสร้าง Concrete Syntax Tree (CST)
หลังจากที่โค้ดถูกแบ่งเป็น tokens แล้ว tokens เหล่านี้จะถูกส่งไปยัง Parser หน้าที่ของ Parser คือการตรวจสอบว่าลำดับของ tokens นั้นถูกต้องตามไวยากรณ์ (Syntax) ของภาษา Python หรือไม่ และสร้างโครงสร้างข้อมูลที่เรียกว่า Concrete Syntax Tree (CST) ขึ้นมา Python 3.9 ได้เปลี่ยนมาใช้ PEG parser grammar แทนที่ LL(1) ทำให้มีความยืดหยุ่นและรองรับไวยากรณ์ที่ซับซ้อนได้ดีขึ้น
3. AST Generator: การสร้าง Abstract Syntax Tree (AST)
CST ที่ได้จาก Parser จะถูกแปลงเป็น Abstract Syntax Tree (AST) AST คือโครงสร้างต้นไม้ที่แสดงโครงสร้างเชิงตรรกะของโค้ด โดยตัดรายละเอียดที่ไม่จำเป็นออกไป เช่น วงเล็บ หรือเครื่องหมายวรรคตอนบางอย่าง AST จะประกอบด้วย nodes (โหนด) ที่แสดงถึงโครงสร้างต่างๆ ของโปรแกรม เช่น Module, FunctionDef (การนิยามฟังก์ชัน), Assign (การกำหนดค่า) เป็นต้น
คุณสามารถดู AST ของโค้ด Python ได้ด้วยโมดูล ast:
import ast
code = """
def greet(name):
message = "Hello, " + name
return message
"""
tree = ast.parse(code)
print(ast.dump(tree, indent=4))
ผลลัพธ์ที่คาดการณ์: คุณจะเห็นโครงสร้าง AST ในรูปแบบข้อความ ซึ่งแสดงให้เห็นว่าโค้ดของคุณถูกมองเห็นเป็นโครงสร้างฟังก์ชัน การกำหนดตัวแปร และการคืนค่าอย่างไร
4. Symbol Table Builder: การจัดการ Scope ตัวแปร
ก่อนที่จะแปลงเป็น bytecode Python จะต้องเข้าใจว่าตัวแปรแต่ละตัวถูกประกาศและใช้งานที่ไหนบ้าง Symbol table builder จะ travers (สำรวจ) AST เพื่อระบุ Variable scopes (ขอบเขตของตัวแปร) และสร้างตารางสัญลักษณ์ (Symbol Table) ขึ้นมา ตัวแปรจะถูกจัดประเภทเป็น GLOBAL (ตัวแปรทั่วโลก), LOCAL (ตัวแปรภายในฟังก์ชัน), FREE (ตัวแปรที่ถูกอ้างอิงจาก scope นอก) หรือ CELL (ตัวแปรที่ถูกปิดล้อมอยู่ใน closure)
การเข้าใจเรื่อง scope เป็นสิ่งสำคัญในการหลีกเลี่ยงข้อผิดพลาดที่เกี่ยวกับตัวแปร และยังช่วยให้คุณเข้าใจการทำงานของ Affiliate Marketing สำหรับสาย Tech ที่มักมีการเขียนโค้ดเพื่อติดตามค่าต่างๆ
5. Compiler: การแปลง AST เป็น Bytecode
นี่คือขั้นตอนสำคัญที่หลายคนเข้าใจผิดว่า Python ทำงานแบบ “interpreted” โดยตรง แท้จริงแล้ว CPython (Python VM ที่เราใช้กันส่วนใหญ่) จะคอมไพล์ AST ให้เป็น bytecode ก่อน bytecode คือชุดคำสั่งระดับต่ำที่เครื่องเสมือน (Virtual Machine) ของ Python เข้าใจและรันได้ compiler converts AST to bytecode instructions stored in code objects with co_code attribute containing raw bytes.
ไฟล์ .pyc ที่คุณเห็นในโฟลเดอร์ __pycache__ ก็คือ bytecode ที่ถูกคอมไพล์และแคชไว้ เพื่อให้การรันครั้งต่อไปเร็วขึ้น
Python’s garbage collector uses reference counting plus a cycle detector that runs every 700 object allocations by default. นี่คือเหตุผลว่าทำไมคุณไม่ต้องจัดการหน่วยความจำเองเหมือนภาษา C++ แต่ก็อาจมีผลต่อประสิทธิภาพในบางกรณี.
เราสามารถดู bytecode ของฟังก์ชันได้โดยใช้โมดูล dis (disassembler):
import dis
def calculate_sum(a, b):
result = a + b
return result
dis.dis(calculate_sum)
ผลลัพธ์ที่คาดการณ์:
4 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 BINARY_ADD
6 STORE_FAST 2 (result)
5 8 LOAD_FAST 2 (result)
10 RETURN_VALUE
LOAD_FAST 0 (a): โหลดค่าของตัวแปรa(ซึ่งอยู่ที่ตำแหน่ง 0 ใน local variables) ไปไว้บน stackLOAD_FAST 1 (b): โหลดค่าของตัวแปรbไปไว้บน stackBINARY_ADD: ดึงค่าสองค่าบน stack (aและb) มาบวกกัน แล้วเก็บผลลัพธ์กลับไปบน stackSTORE_FAST 2 (result): ดึงค่าบน stack มาเก็บไว้ในตัวแปรresult(ที่ตำแหน่ง 2)LOAD_FAST 2 (result): โหลดค่าของresultไปไว้บน stack อีกครั้งRETURN_VALUE: คืนค่าที่อยู่บนสุดของ stack เป็นผลลัพธ์ของฟังก์ชัน
bytecode เหล่านี้คือคำสั่งที่ Python Virtual Machine (VM) เข้าใจและประมวลผล
6. CPython VM: การโหลดและ Loop ประเมินผล
เมื่อ bytecode ถูกสร้างขึ้นแล้ว CPython VM (ซึ่งเขียนด้วยภาษา C) จะรับหน้าที่ต่อ CPython VM loads bytecode into evaluation loop using a stack-based architecture with VALUE_STACK and BLOCK_STACK. มันจะอ่าน bytecode ทีละคำสั่งและดำเนินการตามนั้น โดยใช้สอง stack หลักๆ:
- Value Stack: ใช้เก็บค่า (เช่น ตัวเลข, สตริง, ผลลัพธ์จากการคำนวณ) ที่ถูกโหลดขึ้นมา หรือเป็นผลลัพธ์จากการดำเนินการ
- Block Stack: ใช้จัดการบล็อกโค้ดต่างๆ เช่น
forloops,try-exceptblocks, หรือ function calls
ถึงแม้ Instagram serves 500 million daily users on Python despite the Global Interpreter Lock limiting thread parallelism แต่ด้วยการจัดการ VM ที่มีประสิทธิภาพและสถาปัตยกรรมแบบ stack-based ช่วยให้จัดการกับการประมวลผลจำนวนมากได้
7. Interpreter: การดำเนินการ Opcodes
ภายใน CPython VM จะมี Interpreter ที่ทำหน้าที่ “แปล” และ “รัน” opcodes เหล่านี้ Interpreter executes opcodes in switch statement, performing operations like LOAD_FAST, BINARY_ADD, CALL_FUNCTION. ทุก opcode มีฟังก์ชันที่สอดคล้องกันในโค้ด C ของ CPython ที่จะบอก VM ว่าต้องทำอะไร ตัวอย่างเช่น:
LOAD_CONST: โหลดค่าคงที่จากตารางค่าคงที่LOAD_GLOBAL: โหลดตัวแปรจาก global scopeCALL_FUNCTION: เรียกใช้ฟังก์ชันSTORE_NAME: เก็บค่าไว้ในตัวแปร
8. Python 3.11+ Adaptive Interpreter: การเพิ่มประสิทธิภาพ
ตั้งแต่ Python 3.11 เป็นต้นมา มีการนำ Adaptive Interpreter มาใช้เพื่อเพิ่มความเร็วในการประมวลผล Python 3.11+ adaptive interpreter monitors hotspots and replaces generic opcodes with specialized versions after 8 executions. หมายความว่า ถ้า interpreter ตรวจพบว่าโค้ดส่วนไหนถูกรันซ้ำๆ (hotspots) มันจะ “ปรับตัวเอง” โดยการแปลง opcodes ทั่วไปให้เป็น opcodes ที่เฉพาะเจาะจงและมีประสิทธิภาพมากกว่าเดิมหลังจากถูกรันไป 8 ครั้ง นี่คือเหตุผลว่าทำไม Python 3.11 จึงรันได้เร็วขึ้น 10-60% เมื่อเทียบกับ 3.10
เทคนิคนี้คล้ายกับการทำ Just-In-Time (JIT) Compilation ในภาษาอื่นๆ ซึ่งช่วยให้ Python มีความเร็วใกล้เคียงกับภาษาที่คอมไพล์แล้วมากขึ้น
ตัวอย่างโค้ดสมบูรณ์
มาดูโค้ดตัวอย่างที่แสดงกระบวนการทำงานแบบรวบรัดกัน
import inspect
import dis
import ast
def calculate_area(length, width):
"""
ฟังก์ชันสำหรับคำนวณพื้นที่สี่เหลี่ยม
"""
area = length * width
return area
# 1. การสร้างและดู AST ของฟังก์ชัน
print("--- Abstract Syntax Tree (AST) ---")
parsed_tree = ast.parse(inspect.getsource(calculate_area))
print(ast.dump(parsed_tree, indent=4))
print("n" + "="*50 + "n")
# 2. การดู Bytecode ของฟังก์ชัน
print("--- Python Bytecode ---")
dis.dis(calculate_area)
print("n" + "="*50 + "n")
# 3. การใช้งานฟังก์ชัน (VM ทำงาน)
print("--- Function Execution ---")
l = 10
w = 5
result = calculate_area(l, w)
print(f"The area of a rectangle with length {l} and width {w} is: {result}")
คำอธิบายโค้ด:
import inspect, dis, ast: นำเข้าโมดูลที่จำเป็นcalculate_area(length, width): ฟังก์ชันตัวอย่างของเราast.parse(inspect.getsource(calculate_area)): ใช้inspect.getsource()เพื่อดึงโค้ดต้นฉบับของฟังก์ชัน จากนั้นใช้ast.parse()เพื่อสร้าง AST และast.dump()เพื่อแสดงผลdis.dis(calculate_area): ใช้โมดูลdisเพื่อ Disassemble ฟังก์ชัน และแสดง bytecode ที่เกี่ยวข้องresult = calculate_area(l, w): เมื่อเรียกใช้ฟังก์ชันนี้ CPython VM จะโหลด bytecode มาประมวลผลตามขั้นตอนที่ได้กล่าวไป
เคล็ดลับและข้อควรระวัง
- หลีกเลี่ยง Global Interpreter Lock (GIL): GIL ทำให้ Python รันได้แค่ 1 thread ต่อ 1 CPU core ในเวลาเดียวกัน หากคุณต้องการประมวลผลแบบขนานสำหรับงานที่เน้น CPU คุณควรใช้
multiprocessingแทนthreadingหรือพิจารณาภาษาอื่นสำหรับงานที่ต้องการประสิทธิภาพสูงจริงๆ - ใช้ Built-in Functions: ฟังก์ชันในตัวของ Python (เช่น
len(),sum()) มักจะถูกเขียนด้วย C ทำให้มีประสิทธิภาพสูงกว่าการเขียนด้วย Python เอง - เข้าใจ List Comprehensions: List comprehensions มักจะเร็วกว่าการใช้ for loop ทั่วไปเพราะมีการจัดการ bytecode ที่ optimized กว่า
- หลีกเลี่ยงการสร้าง Object ซ้ำๆ: ทุกครั้งที่คุณสร้าง object ใหม่ (เช่น สตริง, ลิสต์) Python ต้องจัดสรรหน่วยความจำและอาจมี overhead
- Python 3.11+ เพื่อประสิทธิภาพ: ถ้าเป็นไปได้ ให้อัปเกรดไปใช้ Python เวอร์ชัน 3.11 หรือใหม่กว่า เพื่อรับประโยชน์จาก Adaptive Interpreter
ขั้นตอนต่อไป (Next Steps)
เมื่อ
ปลั๊กอิน WordPress จากเรา: Exit Pop Pro
ป๊อปอัพ exit-intent ที่แจก PDF ฟรี แลกอีเมล — เก็บ subscriber เข้า WordPress ของคุณโดยตรง จ่ายครั้งเดียว $29 ไม่มีค่ารายเดือน ไม่ต้องง้อ SaaS