DEV Community

Cover image for Python ทำงานภายในอย่างไร: จากโค้ดสู่ Bytecode
Tawan Shamsanor
Tawan Shamsanor

Posted on • Originally published at aidevthai.com

Python ทำงานภายในอย่างไร: จากโค้ดสู่ Bytecode

<h2>Python ทำงานภายในอย่างไร: จากโค้ดสู่ Bytecode</h2>

<p>คุณเคยสงสัยไหมว่าเมื่อคุณเขียนโค้ด Python แล้วกดรัน (Run) เกิดอะไรขึ้นเบื้องหลัง? หลายคนมักได้ยินว่า Python เป็นภาษา interpreted language แต่ความจริงซับซ้อนกว่านั้นมาก และรู้หรือไม่ว่า <a href="https://aidevthai.com/category/coding/">Python</a> executes code 10x slower than C++ นั่นจึงเป็นเหตุผลว่าทำไมการเข้าใจกลไกภายในของ Python จึงสำคัญ ถ้าคุณอยากเขียนโค้ดที่มีประสิทธิภาพ หรือแก้ไขปัญหาบั๊กแปลกๆ ได้อย่างมืออาชีพ บทความนี้จะพาคุณเจาะลึกการทำงานของ Python ตั้งแต่โค้ดของคุณถูกอ่านไปจนถึงการประมวลผลเป็นคำสั่งจริงๆ</p>

<div class="key-facts" style="background:#fffbeb;border-left:4px solid #f59e0b;padding:16px 20px;margin:20px 0;border-radius:6px;"><strong>Key Facts ที่คนส่วนใหญ่ไม่รู้</strong>
    <ul>
        <li>Python's name comes from Monty Python's Flying Circus, not the snake, decided by Guido van Rossum in 1991</li>
        <li>CPython compiles your code to bytecode with 163 opcodes before execution, not direct interpretation</li>
        <li>Python 3.11 released October 2022 runs 10-60% faster than 3.10 using a specialized adaptive interpreter</li>
    </ul>
</div>

<h3>ทำไมต้องเรียนรู้เรื่องนี้?</h3>
<ul>
    <li><strong>เพิ่มประสิทธิภาพ:</strong> เมื่อเข้าใจกระบวนการ คุณจะสามารถเขียนโค้ดที่รันได้เร็วขึ้น ใช้ทรัพยากรน้อยลง</li>
    <li><strong>แก้ไขปัญหาได้ดีขึ้น:</strong> การเข้าใจว่าโค้ดถูกแปลงเป็นอะไรช่วยให้คุณเดาจุดผิดพลาดได้ง่ายขึ้น โดยเฉพาะปัญหาที่เกี่ยวกับ <a href="https://aidevthai.com/python-libraries-%e0%b8%aa%e0%b8%b3%e0%b8%ab%e0%b8%a3%e0%b8%b1%e0%b8%9a-data-science-%e0%b8%97%e0%b8%b5%e0%b9%88%e0%b8%99%e0%b8%b1%e0%b8%81%e0%b8%9e%e0%b8%b1%e0%b8%92%e0%b8%99%e0%b8%b2%e0%b8%84/">Python Libraries สำหรับ Data Science</a> ที่ซับซ้อน</li>
    <li><strong>เข้าใจภาษา:</strong> ความรู้เชิงลึกเช่นนี้จะเปิดโลกทัศน์ให้คุณมอง Python ไม่ใช่แค่เครื่องมือ แต่เป็นวิศวกรรมที่ซับซ้อน</li>
    <li><strong>พัฒนาทักษะ:</strong> ทำให้คุณเป็นนักพัฒนาที่สามารถคุยเรื่อง Technical ในระดับสูงได้ และไม่แน่คุณอาจจะเจอ <a href="https://aidevthai.com/ai-tools-%e0%b8%97%e0%b8%B5%e0%b9%88%e0%b8%94%e0%b8%B5%e0%b8%97%e0%b8%B5%e0%b9%88%e0%b8%97%e0%b8%B5%e0%b9%88%e0%b8%aa%e0%b8%B8%e0%b8%94%e0%b8%aa%e0%b8%b3%e0%b8%ab%e0%b8%a3%e0%b8%b1%e0%b8%9a%e0%b8%9c%e0%b8%b9%e0%b9%89%e0%b8%9b/">AI Tools ที่ดีที่สุดสำหรับผู้ประกอบการ</a> มาช่วยงานในอนาคต</li>
</ul>

<h3>สิ่งที่ต้องเตรียม</h3>
<ul>
    <li><strong>Python ติดตั้งในเครื่อง:</strong> แนะนำเวอร์ชัน 3.8 ขึ้นไป</li>
    <li><strong>VS Code:</strong> Integrated Development Environment (IDE) ยอดนิยมสำหรับการเขียนโค้ด</li>
    <li><strong>ความเข้าใจพื้นฐาน Python:</strong> รู้จักตัวแปร ฟังก์ชัน และโครงสร้างควบคุม (loops, conditions)</li>
</ul>

<h3>ขั้นตอนโดยละเอียด: Python ทำงานอย่างไร</h3>

<h4>1. Lexer (Scanner): การแบ่งโค้ดเป็น Token</h4>
<p>เมื่อคุณรันไฟล์ <code>.py</code> ของคุณ สิ่งแรกที่ 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.</p>
<p>ลองดูตัวอย่างโค้ดง่ายๆ:</p>
<pre><code>
Enter fullscreen mode Exit fullscreen mode

def greet(name):
message = "Hello, " + name
return message

Lexer จะแยกโค้ดนี้ออกเป็น tokens ประมาณนี้:


  • def (KEYWORD)
  • greet (NAME)
  • ( (OP)
  • name (NAME)
  • ) (OP)
  • : (OP)
  • NEWLINE
  • INDENT
  • message (NAME)
  • = (OP)
  • "Hello, " (STRING)
  • + (OP)
  • name (NAME)
  • NEWLINE
  • return (KEYWORD)
  • message (NAME)
  • NEWLINE
  • DEDENT

คุณสามารถใช้โมดูล 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 แต่ละตัวพร้อมประเภทและค่าของมัน

<h4>2. Parser: การสร้าง Concrete Syntax Tree (CST)</h4>
<p>หลังจากที่โค้ดถูกแบ่งเป็น tokens แล้ว tokens เหล่านี้จะถูกส่งไปยัง Parser หน้าที่ของ Parser คือการตรวจสอบว่าลำดับของ tokens นั้นถูกต้องตามไวยากรณ์ (Syntax) ของภาษา Python หรือไม่ และสร้างโครงสร้างข้อมูลที่เรียกว่า Concrete Syntax Tree (CST) ขึ้นมา Python 3.9 ได้เปลี่ยนมาใช้ PEG parser grammar แทนที่ LL(1) ทำให้มีความยืดหยุ่นและรองรับไวยากรณ์ที่ซับซ้อนได้ดีขึ้น</p>

<h4>3. AST Generator: การสร้าง Abstract Syntax Tree (AST)</h4>
<p>CST ที่ได้จาก Parser จะถูกแปลงเป็น Abstract Syntax Tree (AST) AST คือโครงสร้างต้นไม้ที่แสดงโครงสร้างเชิงตรรกะของโค้ด โดยตัดรายละเอียดที่ไม่จำเป็นออกไป เช่น วงเล็บ หรือเครื่องหมายวรรคตอนบางอย่าง AST จะประกอบด้วย nodes (โหนด) ที่แสดงถึงโครงสร้างต่างๆ ของโปรแกรม เช่น Module, FunctionDef (การนิยามฟังก์ชัน), Assign (การกำหนดค่า) เป็นต้น</p>
<p>คุณสามารถดู AST ของโค้ด Python ได้ด้วยโมดูล <code>ast</code>:</p>
<pre><code class="language-python">
Enter fullscreen mode Exit fullscreen mode

import ast

code = """
def greet(name):
message = "Hello, " + name
return message
"""

tree = ast.parse(code)
print(ast.dump(tree, indent=4))

ผลลัพธ์ที่คาดการณ์: คุณจะเห็นโครงสร้าง AST ในรูปแบบข้อความ ซึ่งแสดงให้เห็นว่าโค้ดของคุณถูกมองเห็นเป็นโครงสร้างฟังก์ชัน การกำหนดตัวแปร และการคืนค่าอย่างไร

<h4>4. Symbol Table Builder: การจัดการ Scope ตัวแปร</h4>
<p>ก่อนที่จะแปลงเป็น bytecode Python จะต้องเข้าใจว่าตัวแปรแต่ละตัวถูกประกาศและใช้งานที่ไหนบ้าง Symbol table builder จะ travers (สำรวจ) AST เพื่อระบุ Variable scopes (ขอบเขตของตัวแปร) และสร้างตารางสัญลักษณ์ (Symbol Table) ขึ้นมา ตัวแปรจะถูกจัดประเภทเป็น GLOBAL (ตัวแปรทั่วโลก), LOCAL (ตัวแปรภายในฟังก์ชัน), FREE (ตัวแปรที่ถูกอ้างอิงจาก scope นอก) หรือ CELL (ตัวแปรที่ถูกปิดล้อมอยู่ใน closure)</p>
<p>การเข้าใจเรื่อง scope เป็นสิ่งสำคัญในการหลีกเลี่ยงข้อผิดพลาดที่เกี่ยวกับตัวแปร และยังช่วยให้คุณเข้าใจการทำงานของ <a href="https://aidevthai.com/affiliate-marketing-%e0%b8%aa%e0%b8%b3%e0%b8%ab%e0%b8%a3%e0%b8%b1%e0%b8%9a%e0%b8%AA%e0%b8%b2%e0%b8%a2-tech-%e0%b9%80%e0%b8%a3%e0%b8%b4%e0%b9%88%e0%b8%a1%e0%b8%95%e0%b9%89%e0%b8%99%e0%b8%a2/">Affiliate Marketing สำหรับสาย Tech</a> ที่มักมีการเขียนโค้ดเพื่อติดตามค่าต่างๆ</p>

<h4>5. Compiler: การแปลง AST เป็น Bytecode</h4>
<p>นี่คือขั้นตอนสำคัญที่หลายคนเข้าใจผิดว่า 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.</p>
<p>ไฟล์ <code>.pyc</code> ที่คุณเห็นในโฟลเดอร์ <code>__pycache__</code> ก็คือ bytecode ที่ถูกคอมไพล์และแคชไว้ เพื่อให้การรันครั้งต่อไปเร็วขึ้น</p>

<blockquote style="border-left:4px solid #6366f1;padding:12px 20px;background:#f5f3ff;font-style:italic;">
    Python's garbage collector uses reference counting plus a cycle detector that runs every 700 object allocations by default. นี่คือเหตุผลว่าทำไมคุณไม่ต้องจัดการหน่วยความจำเองเหมือนภาษา C++ แต่ก็อาจมีผลต่อประสิทธิภาพในบางกรณี.
</blockquote>

<p>เราสามารถดู bytecode ของฟังก์ชันได้โดยใช้โมดูล <code>dis</code> (disassembler):</p>
<pre><code class="language-python">
Enter fullscreen mode Exit fullscreen mode

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) ไปไว้บน stack
  • LOAD_FAST 1 (b): โหลดค่าของตัวแปร b ไปไว้บน stack
  • BINARY_ADD: ดึงค่าสองค่าบน stack (a และ b) มาบวกกัน แล้วเก็บผลลัพธ์กลับไปบน stack
  • STORE_FAST 2 (result): ดึงค่าบน stack มาเก็บไว้ในตัวแปร result (ที่ตำแหน่ง 2)
  • LOAD_FAST 2 (result): โหลดค่าของ result ไปไว้บน stack อีกครั้ง
  • RETURN_VALUE: คืนค่าที่อยู่บนสุดของ stack เป็นผลลัพธ์ของฟังก์ชัน

bytecode เหล่านี้คือคำสั่งที่ Python Virtual Machine (VM) เข้าใจและประมวลผล

<h4>6. CPython VM: การโหลดและ Loop ประเมินผล</h4>
<p>เมื่อ bytecode ถูกสร้างขึ้นแล้ว CPython VM (ซึ่งเขียนด้วยภาษา C) จะรับหน้าที่ต่อ CPython VM loads bytecode into evaluation loop using a stack-based architecture with VALUE_STACK and BLOCK_STACK. มันจะอ่าน bytecode ทีละคำสั่งและดำเนินการตามนั้น โดยใช้สอง stack หลักๆ:</p>
<ul>
    <li><strong>Value Stack:</strong> ใช้เก็บค่า (เช่น ตัวเลข, สตริง, ผลลัพธ์จากการคำนวณ) ที่ถูกโหลดขึ้นมา หรือเป็นผลลัพธ์จากการดำเนินการ</li>
    <li><strong>Block Stack:</strong> ใช้จัดการบล็อกโค้ดต่างๆ เช่น <code>for</code> loops, <code>try-except</code> blocks, หรือ function calls</li>
</ul>
<p>ถึงแม้ Instagram serves 500 million daily users on Python despite the Global Interpreter Lock limiting thread parallelism แต่ด้วยการจัดการ VM ที่มีประสิทธิภาพและสถาปัตยกรรมแบบ stack-based ช่วยให้จัดการกับการประมวลผลจำนวนมากได้</p>

<h4>7. Interpreter: การดำเนินการ Opcodes</h4>
<p>ภายใน CPython VM จะมี Interpreter ที่ทำหน้าที่ "แปล" และ "รัน" opcodes เหล่านี้ Interpreter executes opcodes in switch statement, performing operations like LOAD_FAST, BINARY_ADD, CALL_FUNCTION. ทุก opcode มีฟังก์ชันที่สอดคล้องกันในโค้ด C ของ CPython ที่จะบอก VM ว่าต้องทำอะไร ตัวอย่างเช่น:</p>
<ul>
    <li><code>LOAD_CONST</code>: โหลดค่าคงที่จากตารางค่าคงที่</li>
    <li><code>LOAD_GLOBAL</code>: โหลดตัวแปรจาก global scope</li>
    <li><code>CALL_FUNCTION</code>: เรียกใช้ฟังก์ชัน</li>
    <li><code>STORE_NAME</code>: เก็บค่าไว้ในตัวแปร</li>
</ul>

<h4>8. Python 3.11+ Adaptive Interpreter: การเพิ่มประสิทธิภาพ</h4>
<p>ตั้งแต่ 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</p>
<p>เทคนิคนี้คล้ายกับการทำ Just-In-Time (JIT) Compilation ในภาษาอื่นๆ ซึ่งช่วยให้ Python มีความเร็วใกล้เคียงกับภาษาที่คอมไพล์แล้วมากขึ้น</p>

<h3>ตัวอย่างโค้ดสมบูรณ์</h3>
<p>มาดูโค้ดตัวอย่างที่แสดงกระบวนการทำงานแบบรวบรัดกัน</p>
<pre><code class="language-python">
Enter fullscreen mode Exit fullscreen mode

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 มาประมวลผลตามขั้นตอนที่ได้กล่าวไป
<h3>เคล็ดลับและข้อควรระวัง</h3>
<ul>
    <li><strong>หลีกเลี่ยง Global Interpreter Lock (GIL):</strong> GIL ทำให้ Python รันได้แค่ 1 thread ต่อ 1 CPU core ในเวลาเดียวกัน หากคุณต้องการประมวลผลแบบขนานสำหรับงานที่เน้น CPU คุณควรใช้ <code>multiprocessing</code> แทน <code>threading</code> หรือพิจารณาภาษาอื่นสำหรับงานที่ต้องการประสิทธิภาพสูงจริงๆ</li>
    <li><strong>ใช้ Built-in Functions:</strong> ฟังก์ชันในตัวของ Python (เช่น <code>len()</code>, <code>sum()</code>) มักจะถูกเขียนด้วย C ทำให้มีประสิทธิภาพสูงกว่าการเขียนด้วย Python เอง</li>
    <li><strong>เข้าใจ List Comprehensions:</strong> List comprehensions มักจะเร็วกว่าการใช้ for loop ทั่วไปเพราะมีการจัดการ bytecode ที่ optimized กว่า</li>
    <li><strong>หลีกเลี่ยงการสร้าง Object ซ้ำๆ:</strong> ทุกครั้งที่คุณสร้าง object ใหม่ (เช่น สตริง, ลิสต์) Python ต้องจัดสรรหน่วยความจำและอาจมี overhead</li>
    <li><strong>Python 3.11+ เพื่อประสิทธิภาพ:</strong> ถ้าเป็นไปได้ ให้อัปเกรดไปใช้ Python เวอร์ชัน 3.11 หรือใหม่กว่า เพื่อรับประโยชน์จาก Adaptive Interpreter</li>
</ul>

<h3>ขั้นตอนต่อไป (Next Steps)</h3>
<p>เมื่อ
Enter fullscreen mode Exit fullscreen mode

Originally published on AI Dev Thai. Daily AI tutorials, coding guides, and tech insights in Thai.

Top comments (0)