# Python Programming 101 (พื้นฐานการเขียนโปรแกรมไพธอน) บทนี้มีวัตถุประสงค์เพื่อปูพื้นฐานภาษา **Python** ตั้งแต่เริ่มต้น เนื้อหาถูกออกแบบมาให้ครอบคลุมโครงสร้างภาษาที่สำคัญ ตั้งแต่การประกาศตัวแปร การตัดสินใจ การวนซ้ำ ฟังก์ชัน ไปจนถึงการเขียนโปรแกรมเชิงวัตถุ (OOP) เพื่อเตรียมความพร้อมสำหรับการเขียนโปรแกรมคอมพิวเตอร์และหุ่นยนต์ ## Variables and Data Types (ตัวแปรและชนิดข้อมูล) การเขียนโปรแกรมคือการสั่งให้คอมพิวเตอร์จัดการกับ "ข้อมูล" (Data) ดังนั้นสิ่งแรกที่ต้องสร้างคือ "ตัวแปร" (Variable) ตัวแปรเปรียบเสมือน **"กล่อง"** ที่มีการแปะป้ายชื่อกำกับ ภายในกล่องสามารถบรรจุข้อมูลต่างๆ ลงไปได้ และเมื่อต้องการใช้งานข้อมูลนั้น สามารถทำได้โดยการเรียกชื่อที่ระบุไว้บนป้ายหน้ากล่อง ภาษา Python เป็นภาษาแบบ **Dynamic Typing** หมายความว่า ผู้เขียนโปรแกรมสามารถสร้างตัวแปรขึ้นมาได้ทันทีโดยไม่ต้องระบุชนิดข้อมูลล่วงหน้า ตัวแปลภาษา (Interpreter) จะตรวจสอบชนิดของข้อมูลให้เองอัตโนมัติขณะทำงาน ### Common Data Types (ชนิดข้อมูลที่พบบ่อย) ในการเขียนโปรแกรมทั่วไปและงานหุ่นยนต์ เราจะพบเจอข้อมูลหลักๆ อยู่ 4 ชนิด ได้แก่: * **Integer (int):** จำนวนเต็ม คือตัวเลขที่ไม่มีจุดทศนิยม ใช้สำหรับนับจำนวนสิ่งของ ลำดับ หรือค่าคงที่ที่ไม่ต้องการความละเอียด เช่น จำนวนล้อ, รหัสประจำตัว (ID) * **Float (float):** จำนวนจริง คือตัวเลขที่มีจุดทศนิยม ใช้สำหรับข้อมูลที่ต้องการความละเอียดสูง เช่น ค่าที่อ่านได้จากเซนเซอร์, พิกัด (x, y), หรือความเร็วของหุ่นยนต์ * **String (str):** ข้อความ คือชุดตัวอักษรที่นำมาเรียงต่อกัน ต้องเขียนอยู่ภายในเครื่องหมายคำพูด (Quotes) เสมอ เช่น ชื่อหุ่นยนต์, ข้อความแจ้งเตือน (Log) * **Boolean (bool):** ค่าความจริงทางตรรกศาสตร์ มีเพียง 2 ค่าเท่านั้นคือ `True` (จริง) และ `False` (เท็จ) ใช้สำหรับการตัดสินใจของหุ่นยนต์ เช่น "เจอกำแพงหรือไม่?", "แบตเตอรี่หมดหรือยัง?" #### ตัวอย่างการประกาศตัวแปรและการตรวจสอบชนิดข้อมูล ```{code-block} python :linenos: # 1. Integer: Whole numbers robot_id = 101 wheel_count = 4 # 2. Float: Decimal numbers battery_voltage = 24.5 pi_value = 3.14159 # 3. String: Text inside quotes robot_name = "Batly_R1" status_msg = 'System Ready' # 4. Boolean: Logic values (Capital T/F) is_active = True has_error = False # Print values to the terminal print(robot_name) print(battery_voltage) # Check data types using type() function print(type(robot_id)) # print(type(battery_voltage)) # print(type(robot_name)) # print(type(is_active)) # ``` ### Reserved Words (คำสงวน) ภาษา Python มีคำศัพท์เฉพาะที่เรียกว่า Keywords ซึ่งถูกสงวนไว้สำหรับใช้งานในระบบ ห้ามนำคำเหล่านี้มาตั้งเป็นชื่อตัวแปร ชื่อฟังก์ชัน หรือชื่อคลาส โดยเด็ดขาด สามารถสังเกตได้เมื่อพิมพ์คำเหล่านี้ใน VS Code ตัวอักษรจะเปลี่ยนสี (เช่น เปลี่ยนเป็นสีม่วงหรือน้ำเงิน) เพื่อเตือนว่าเป็นคำสั่งเฉพาะ ตารางด้านล่างแสดงคำสงวนทั้งหมดใน Python 3 ที่พบบ่อย: ```{list-table} :widths: 25 25 25 25 :header-rows: 1 * - **Logic & Values** - **Control Flow** - **Structure** - **Others** * - `True` - `if`, `elif`, `else` - `def` - `import`, `from`, `as` * - `False` - `for`, `while` - `class` - `return` * - `None` - `break` - `with` - `pass` * - `and`, `or`, `not` - `continue` - `lambda` - `try`, `except` * - `in`, `is` - `yield` - `global` - `raise`, `finally` * - - - - `del`, `assert` ``` **Pro Tip:** หากมีการตั้งชื่อตัวแปรซ้ำกับคำสงวน Python จะแจ้ง Error ว่า `SyntaxError: invalid syntax` #### ตัวอย่างสิ่งที่ห้ามทำ ```{code-block} python :linenos: # BAD: Using reserved words as variable names class = "My Robot" # Syntax Error! global = 100 # Syntax Error! # BAD: Overwriting built-in functions (Not a syntax error, but causes bugs) # Don't use words like: print, list, str, int, sum, min, max sum = 10 + 20 print(sum) # Later in the code... numbers = [1, 2, 3] total = sum(numbers) # This will CRASH because 'sum' is now just an integer! ``` ## String Operations (การจัดการข้อความ) ข้อมูลในการเขียนโปรแกรมไม่ได้มีเพียงตัวเลข แต่ยังรวมถึง "ข้อความ" (Text) ด้วย ไม่ว่าจะเป็นชื่อหุ่นยนต์, สถานะการทำงาน (เช่น "Ready", "Error"), หรือข้อความที่ต้องการแสดงบนหน้าจอ ในภาษา Python ข้อมูลประเภทข้อความเรียกว่า String ซึ่งมีความยืดหยุ่นในการใช้งานสูง โดยมีเทคนิคที่ควรทราบดังนี้ ### การสร้างและกำหนดค่า (Declaration) Python รองรับการสร้างข้อความโดยใช้เครื่องหมาย "ฟันหนู" (Quote) ได้ 3 รูปแบบ ซึ่งแต่ละแบบมีประโยชน์แตกต่างกัน: * **Single Quote (')** และ **Double Quote ("):** ใช้งานเหมือนกัน ใช้สำหรับข้อความสั้นๆ บรรทัดเดียว โดยทั่วไปนิยมเลือกใช้แบบใดแบบหนึ่งให้เป็นมาตรฐานเดียวกันทั้งโปรแกรม * **Triple Quote (''' หรือ """):** ใช้สำหรับข้อความที่มีความยาวหลายบรรทัด โดย Python จะเก็บการเว้นวรรคและการขึ้นบรรทัดใหม่ไว้ทั้งหมด เหมาะสำหรับเขียนคำอธิบายยาวๆ #### ตัวอย่างการประกาศตัวแปรข้อความ ```{code-block} python :linenos: # 1. Single Quote (Most common) str1 = 'String Type 1' # 2. Double Quote (Useful if the string contains a single quote ') str2 = "String Type 2" # 3. Triple Quote (For multi-line strings) str3 = '''This is a long text that spans across multiple lines.''' print(str3) ``` ### การจัดรูปแบบข้อความ (String Formatting) หัวข้อนี้มีความสำคัญอย่างยิ่งสำหรับการเขียนโปรแกรมหุ่นยนต์ เนื่องจากบ่อยครั้งที่โปรแกรมจำเป็นต้อง "แทรกค่าตัวแปร" ลงไปในข้อความ เช่น การแสดงค่าระดับแบตเตอรี่ หรือค่าพิกัดปัจจุบันจากเซนเซอร์ ภาษา Python มีวิธีจัดการการแสดงผลข้อความหลายวิธี ในที่นี้จะขอยกตัวอย่าง 3 วิธีหลักที่พบได้บ่อย โดยแนะนำให้ฝึกใช้วิธี f-string เป็นมาตรฐาน เนื่องจากกระชับและอ่านเข้าใจได้ง่ายที่สุด * **แบบพื้นฐาน (Comma-separated):** ใช้เครื่องหมายลูกน้ำ (,) คั่นระหว่างข้อความและตัวแปรภายในคำสั่ง print() วิธีนี้พิมพ์ง่ายที่สุดและเหมาะกับการดูค่าเร็วๆ แต่มีข้อจำกัดคือไม่สามารถจัดรูปแบบความสวยงามหรือปัดทศนิยมได้ * **แบบเก่า (.format()):** ใช้วงเล็บปีกกา {} เป็นช่องว่าง แล้วระบุตัวแปรตามหลัง วิธีนี้อาจเกิดความสับสนหากมีตัวแปรจำนวนมาก * **แบบใหม่ (f-string):** เพียงใส่ตัวอักษร f ไว้หน้าเครื่องหมาย Quote จะสามารถระบุชื่อตัวแปรลงใน {} ได้ทันที * **เทคนิคพิเศษ:** สามารถกำหนดรูปแบบตัวเลขได้ เช่น {variable:.3f} เพื่อแสดงทศนิยม 3 ตำแหน่ง ซึ่งมีประโยชน์มากสำหรับการแสดงค่าจากเซนเซอร์ #### ตัวอย่างการแทรกตัวแปรลงในข้อความ ```{code-block} python :linenos: # Define variables name = "Batly" # String battery = 85 # Integer pos_x = 1.5501564 # Float (Long decimal) # --- Method 1: Basic Print (Comma-separated) --- # ตัวแปรจะถูกนำมาต่อกันโดยมีช่องว่างคั่นให้อัตโนมัติ print("Basic Style: Robot", name, "Battery", battery, "% and X-Position", pos_x) # --- Method 2: Old Style (.format) --- # ตัวแปรจะถูกนำไปแทนที่ใน {} ตามลำดับ print("Old Style: Robot {}, Battery {} % and X-Position {}".format(name, battery, pos_x)) # --- Method 3: New Style (f-string) --- ** Recommended ** # สังเกตตัว f ด้านหน้า สามารถใส่ชื่อตัวแปรลงใน {} ได้เลย # หมายเหตุ: .3f ช่วยปัดเศษให้เหลือทศนิยม 3 ตำแหน่งโดยอัตโนมัติ print(f"New Style: Robot {name}, Battery {battery} % and X-Position {pos_x:.3f}") ``` ## Input and Output (การรับและแสดงผลข้อมูล) การสื่อสารระหว่างโปรแกรมและผู้ใช้งาน (User Interface) ในระดับพื้นฐาน ทำได้ผ่านการรับค่าจากคีย์บอร์ดและการแสดงผลทางหน้าจอ โดย Python มีฟังก์ชันมาตรฐานเตรียมไว้ให้ใช้งาน 2 คำสั่งหลัก ได้แก่ ### การรับข้อมูล (Input) ใช้คำสั่ง `input()` เพื่อรับข้อมูลจากผู้ใช้งาน โดยโปรแกรมจะหยุดรอจนกว่าผู้ใช้งานจะพิมพ์ข้อความและกดปุ่ม Enter **ข้อควรระวัง:** ข้อมูลที่ได้รับจากฟังก์ชัน `input()` จะมีชนิดข้อมูลเป็น **String** (ข้อความ) เสมอ แม้ว่าผู้ใช้งานจะพิมพ์ตัวเลขลงไปก็ตาม หากต้องการนำค่าไปคำนวณทางคณิตศาสตร์ จำเป็นต้องทำการ **Type Casting** (การแปลงชนิดข้อมูล) ก่อนเสมอ ### การแสดงผล (Output) ใช้คำสั่ง `print()` เพื่อแสดงข้อความหรือค่าของตัวแปรออกทางหน้าจอ (Terminal) #### ตัวอย่างการรับค่าและการแปลงชนิดข้อมูล (Type Casting) ```{code-block} python :linenos: # 1. Standard Input (Returns string) # The variable 'name' will be of type user_name = input("Enter your name: ") # 2. Input with Type Casting (String -> Integer) # Since input() returns a string, we must wrap it with int() for calculation user_age = int(input("Enter your age: ")) # 3. Input with Type Casting (String -> Float) # Converting to float for decimal numbers user_height = float(input("Enter your height (m): ")) # Display the results using f-string print(f"User: {user_name}, Age: {user_age}, Height: {user_height:.2f} m") # Verify data types print(f"Type of age variable: {type(user_age)}") # print(f"Type of height variable: {type(user_height)}") # ``` ## Operators (ตัวดำเนินการ) ตัวดำเนินการ (Operators) คือเครื่องหมายพิเศษที่กำหนดให้คอมพิวเตอร์ทำการประมวลผลข้อมูล ไม่ว่าจะเป็นการคำนวณทางคณิตศาสตร์ การเปรียบเทียบค่า หรือการจัดการตรรกะ เพื่อให้ได้ผลลัพธ์ใหม่ ### Arithmetic Operators (ตัวดำเนินการทางคณิตศาสตร์) ใช้สำหรับการคำนวณตัวเลขพื้นฐาน ```{list-table} :widths: 15 25 35 25 :header-rows: 1 * - Operator - Name - Description - Example * - `+` - Addition - การบวก - `10 + 20` -> `30` * - `-` - Subtraction - การลบ - `10 - 5` -> `5` * - `*` - Multiplication - การคูณ - `5 * 4` -> `20` * - `/` - Division - การหาร (ผลลัพธ์เป็น float เสมอ) - `10 / 4` -> `2.5` * - `%` - Modulus - การหารเอาเศษ - `10 % 3` -> `1` * - `**` - Exponentiation - การยกกำลัง - `2 ** 3` -> `8` * - `//` - Floor Division - การหารปัดเศษทิ้ง (เอาเฉพาะจำนวนเต็ม) - `10 // 4` -> `2` ``` #### ตัวอย่างการใช้ตัวดำเนินการทางคณิตศาสตร์ ```{code-block} python :linenos: a = 10 b = 3 # Standard operations print(f"Addition: {a + b}") # 13 print(f"Division: {a / b}") # 3.3333... # Floor Division vs Modulus # Useful for grid navigation or indexing print(f"Floor Division: {a // b}") # 3 print(f"Modulus (Remainder): {a % b}") # 1 # Exponentiation print(f"Power: {a ** 2}") # 100 ``` ### Assignment Operators (ตัวดำเนินการกำหนดค่า) ใช้สำหรับกำหนดค่าหรืออัปเดตค่าให้กับตัวแปร (ให้ `x=5`) ```{list-table} :widths: 15 35 25 25 :header-rows: 1 * - Operator - Description - Equivalent To - Result (x=5) * - `=` - กำหนดค่าทางขวาให้ตัวแปรทางซ้าย - `x = 5` - `x` is 5 * - `+=` - บวกค่าเพิ่มแล้วเก็บผลลัพธ์ - `x += 3` - `x` is 8 * - `-=` - ลบค่าออกแล้วเก็บผลลัพธ์ - `x -= 3` - `x` is 2 * - `*=` - คูณค่าเพิ่มแล้วเก็บผลลัพธ์ - `x *= 3` - `x` is 15 * - `/=` - หารค่าแล้วเก็บผลลัพธ์ - `x /= 2` - `x` is 2.5 * - `//=` - หารปัดเศษทิ้งแล้วเก็บผลลัพธ์ - `x //= 2` - `x` is 2 * - `%=` - หารเอาเศษแล้วเก็บผลลัพธ์ - `x %= 3` - `x` is 2 * - `**=` - ยกกำลังแล้วเก็บผลลัพธ์ - `x **= 3` - `x` is 125 * - `&=` - Bitwise AND แล้วเก็บผลลัพธ์ - `x &= 3` - `x` is 1 * - `|=` - Bitwise OR แล้วเก็บผลลัพธ์ - `x |= 3` - `x` is 7 * - `^=` - Bitwise XOR แล้วเก็บผลลัพธ์ - `x ^= 3` - `x` is 6 * - `>>=` - Bitwise Right Shift แล้วเก็บ - `x >>= 1` - `x` is 2 * - `<<=` - Bitwise Left Shift แล้วเก็บ - `x <<= 1` - `x` is 10 ``` #### ตัวอย่างการใช้ตัวดำเนินการกำหนดค่า ```{code-block} python :linenos: speed = 10.0 # Increase speed by 5 speed += 5.0 # Same as: speed = speed + 5.0 print(f"Speed up: {speed}") # Decrease speed by 2 speed -= 2.0 # Same as: speed = speed - 2.0 print(f"Slow down: {speed}") # Multiply speed by 2 speed *= 2.0 # Same as: speed = speed * 2.0 print(f"Double speed: {speed}") # Divide speed by 4 speed /= 4.0 # Same as: speed = speed / 4.0 print(f"Reduce speed: {speed}") ``` ### Comparison Operators (ตัวดำเนินการเปรียบเทียบ) ใช้เปรียบเทียบค่าสองค่า ผลลัพธ์ที่ได้จะเป็น **Boolean** (`True` หรือ `False`) เสมอ ```{list-table} :widths: 15 25 35 25 :header-rows: 1 * - Operator - Name - Description - Example (a=5, b=3) * - `==` - Equal - เท่ากับหรือไม่ - `a == b` -> `False` * - `!=` - Not Equal - ไม่เท่ากับหรือไม่ - `a != b` -> `True` * - `>` - Greater than - มากกว่า - `a > b` -> `True` * - `<` - Less than - น้อยกว่า - `a < b` -> `False` * - `>=` - Greater or Equal - มากกว่าหรือเท่ากับ - `a >= 5` -> `True` * - `<=` - Less or Equal - น้อยกว่าหรือเท่ากับ - `a <= 2` -> `False` ``` #### ตัวอย่างการใช้ตัวดำเนินการเปรียบเทียบ ```{code-block} python :linenos: target_distance = 1.0 current_distance = 0.5 # Check if we reached the target is_reached = (current_distance >= target_distance) print(f"Target Reached: {is_reached}") # Check equality print(f"Is distance exactly 0.5? {current_distance == 0.5}") ``` ### Logical Operators (ตัวดำเนินการทางตรรกศาสตร์) ใช้เชื่อมประโยคเงื่อนไขหลายๆ เงื่อนไขเข้าด้วยกัน ```{list-table} :widths: 15 25 35 25 :header-rows: 1 * - Operator - Description - Logic Rule - Example * - `and` - และ - จริงเมื่อ **ทั้งคู่** เป็นจริง - `True and False` -> `False` * - `or` - หรือ - จริงเมื่อ **อย่างน้อยหนึ่งค่า** เป็นจริง - `True or False` -> `True` * - `not` - นิเสธ (กลับค่า) - เปลี่ยนจริงเป็นเท็จ เท็จเป็นจริง - `not True` -> `False` ``` #### ตารางค่าความจริง (Truth Table) เพื่อความเข้าใจที่ชัดเจนขึ้น ด้านล่างคือตารางแสดงผลลัพธ์ของการนำตัวแปร A และ B มาผ่านตัวดำเนินการทางตรรกศาสตร์ ```{list-table} :widths: 20 20 20 20 20 :header-rows: 1 * - A - B - A ``and`` B - A ``or`` B - ``not`` A * - ``True`` - ``True`` - ``True`` - ``True`` - ``False`` * - ``True`` - ``False`` - ``False`` - ``True`` - ``False`` * - ``False`` - ``True`` - ``False`` - ``True`` - ``True`` * - ``False`` - ``False`` - ``False`` - ``False`` - ``True`` ``` #### ตัวอย่างการใช้ตัวดำเนินการทางตรรกศาสตร์ ```{code-block} python :linenos: battery_ok = True plugged_in = False obstacle_detected = False # การใช้ not: หุ่นยนต์จะเดินหน้าได้ ทางต้อง "ไม่" เจอสิ่งกีดขวาง path_clear = not obstacle_detected print(f"Path Clear (not): {path_clear}") # การใช้ or: หุ่นยนต์มีไฟเลี้ยงระบบ ถ้าแบตเตอรี่ยังมีไฟ "หรือ" เสียบปลั๊กชาร์จอยู่ has_power = battery_ok or plugged_in print(f"Has Power (or): {has_power}") # การใช้ and: ระบบหุ่นยนต์จะพร้อมเคลื่อนที่ ถ้ามีไฟเลี้ยง "และ" ทางเดินสะดวก system_ready = has_power and path_clear print(f"System Ready (and): {system_ready}") ``` ### Identity Operators (ตัวดำเนินการตรวจสอบวัตถุ) ตัวดำเนินการนี้ใช้สำหรับตรวจสอบว่า ตัวแปรสองตัวกำลังชี้ไปยัง **"วัตถุชิ้นเดียวกัน"** (Same Object) ในหน่วยความจำหรือไม่ **ข้อควรระวัง:** มือใหม่มักสับสนระหว่าง `is` กับ `==` * **`==` (Equality):** เช็คว่า **"หน้าตา"** หรือ **"ค่า"** (Value) เหมือนกันหรือไม่ * **`is` (Identity):** เช็คว่าเป็น **"ของชิ้นเดียวกัน"** (Memory Address เดียวกัน) หรือไม่ เปรียบเทียบง่ายๆ: * `==` เหมือนถามว่า "รถสองคันนี้รุ่นเดียวกัน สีเดียวกันใช่ไหม?" * `is` เหมือนถามว่า "รถสองคันนี้ คือคันเดียวกัน (ทะเบียนเดียวกัน) ใช่ไหม?" ```{list-table} :widths: 15 35 50 :header-rows: 1 * - Operator - Description - Example * - `is` - เป็นวัตถุชิ้นเดียวกัน (Memory Address ตรงกัน) - `x is y` * - `is not` - ไม่ใช่วัตถุชิ้นเดียวกัน - `x is not y` ``` #### ตัวอย่างความแตกต่างระหว่าง is และ == ```{code-block} python # 1. Lists with SAME values but DIFFERENT objects list_a = [1, 2, 3] list_b = [1, 2, 3] # Check Equality (Do they have the same content?) print(f"Values are equal? {list_a == list_b}") # True # Check Identity (Are they the same object in memory?) print(f"Are they the same object? {list_a is list_b}") # False # 2. Assignment creates a Reference list_c = list_a # list_c points to the same object as list_a print(f"list_a is list_c? {list_a is list_c}") # True # Proof using id() function (shows memory address) print(f"ID of A: {id(list_a)}") print(f"ID of B: {id(list_b)}") # Different ID from A print(f"ID of C: {id(list_c)}") # Same ID as A ``` ### Membership Operators (ตัวดำเนินการตรวจสอบสมาชิก) ใช้ตรวจสอบว่ามีข้อมูลที่ต้องการ อยู่ในลำดับข้อมูล (เช่น List, String, Tuple) หรือไม่ ```{list-table} :widths: 15 35 50 :header-rows: 1 * - Operator - Description - Example (list = [1, 2, 3]) * - `in` - มีค่าอยู่ในลำดับข้อมูลหรือไม่ - `1 in list` -> `True` * - `not in` - ไม่มีค่าอยู่ในลำดับข้อมูลหรือไม่ - `5 not in list` -> `True` ``` ### Bitwise Operators (ตัวดำเนินการระดับบิต) ในงานหุ่นยนต์และระบบฝังตัว (Embedded Systems) เรามักต้องสื่อสารกับฮาร์ดแวร์ในระดับต่ำสุด คือระดับ **เลขฐานสอง (Binary)** เช่น การอ่านค่าสถานะจาก Register ของมอเตอร์ หรือการถอดรหัสข้อมูลจากเซนเซอร์ ก่อนใช้งาน เราต้องเข้าใจการแปลงเลขฐานสิบเป็นฐานสองก่อน: *`5` ในฐานสิบ = `...000101` ในฐานสอง ($4 + 1$) *`3` ในฐานสิบ = `...000011` ในฐานสอง ($2 + 1$) ```{list-table} :widths: 15 20 65 :header-rows: 1 * - Operator - Name - Description * - `&` - **AND** - เปรียบเทียบทีละบิต ต้องเป็น 1 ทั้งคู่ ผลลัพธ์จึงจะเป็น 1 (นิยมใช้ตัดค่า หรือ Masking) * - `|` - **OR** - เปรียบเทียบทีละบิต ถ้ามี 1 อย่างน้อยหนึ่งตัว ผลลัพธ์จะเป็น 1 (นิยมใช้รวมค่า หรือ Setting Flags) * - `^` - **XOR** - (Exclusive OR) ถ้าบิตเหมือนกันได้ 0 ถ้าต่างกันได้ 1 (นิยมใช้เช็คการเปลี่ยนแปลง หรือ Toggle) * - `~` - **NOT** - กลับบิตจาก 0 เป็น 1 และ 1 เป็น 0 (Invert) * - `<<` - **Left Shift** - เลื่อนบิตไปทางซ้าย เติม 0 ด้านหลัง (มีค่าเท่ากับคูณด้วย 2 ยกกำลัง n) * - `>>` - **Right Shift** - เลื่อนบิตไปทางขวา (มีค่าเท่ากับหารด้วย 2 ยกกำลัง n) ``` #### คำอธิบายและตัวอย่างการคำนวณ (Visual Calculation) เพื่อให้เห็นภาพ เราจะใช้ตัวแปร `a = 5` (0101) และ `b = 3` (0011) * **Bitwise AND (`&`)** เอาเฉพาะบิตที่เป็น 1 ทั้งคู่ (ตำแหน่งที่ตรงกัน) ```text 0 1 0 1 (5) & 0 0 1 1 (3) --------- 0 0 0 1 (Result = 1) ``` * **Bitwise OR (`|`)** เอาบิตที่เป็น 1 ทั้งหมดมารวมกัน ```text 0 1 0 1 (5) | 0 0 1 1 (3) --------- 0 1 1 1 (Result = 7) ``` * **Bitwise XOR (`^`)** ดูความต่าง (เหมือน=0, ต่าง=1) ```text 0 1 0 1 (5) ^ 0 0 1 1 (3) --------- 0 1 1 0 (Result = 6) ``` * **Left Shift (`<<`)** เลื่อนบิตไปทางซ้าย 1 ตำแหน่ง (5 << 1) ```text 0 0 1 0 1 (5) << 1 ----------- 0 1 0 1 0 (Result = 10) ``` * **Right Shift (`>>`)** เลื่อนบิตไปทางขวา 1 ตำแหน่ง (12 >> 1) ```text 0 1 1 0 0 (12) >> 1 ----------- 0 0 1 1 0 (Result = 6) ``` #### ตัวอย่างโค้ด Bitwise ใน Python ```{code-block} python :linenos: a = 5 # Binary: 0101 b = 3 # Binary: 0011 # Helper function to print binary easily # bin() converts number to binary string (e.g., '0b101') def show_bin(name, val): print(f"{name}: {val} (Binary: {bin(val)})") print("--- Initial Values ---") show_bin("a", a) show_bin("b", b) print("\n--- Operations ---") # AND show_bin("a & b", a & b) # 0001 (1) # OR show_bin("a | b", a | b) # 0111 (7) # XOR show_bin("a ^ b", a ^ b) # 0110 (6) # Left Shift (Multiply by 2) show_bin("a << 1", a << 1) # 1010 (10) # Right Shift (Divide by 2) show_bin("a >> 1", a >> 1) # 0010 (2) ``` ### Operator Precedence (ลำดับความสำคัญของตัวดำเนินการ) เมื่อในหนึ่งประโยคคำสั่งมีตัวดำเนินการหลายตัว Python จะทำงานตามลำดับความสำคัญจาก **บนลงล่าง** ดังนี้: ```{list-table} :widths: 20 80 :header-rows: 1 * - Priority - Operator * - 1 (Highest) - `()` (วงเล็บ) * - 2 - `**` (ยกกำลัง) * - 3 - `*`, `/`, `//`, `%` (คูณ, หาร) * - 4 - `+`, `-` (บวก, ลบ) * - 5 - `==`, `!=`, `>`, `<`, `in`, `is` (เปรียบเทียบ) * - 6 - `not` * - 7 - `and` * - 8 (Lowest) - `or` ``` ### คำแนะนำ (Best Practice) แม้ Python จะมีลำดับความสำคัญที่ชัดเจน แต่การเขียนโค้ดที่ซับซ้อนโดยไม่ใส่วงเล็บ อาจทำให้ผู้อ่าน (หรือแม้แต่ตัวผู้เขียนเอง) สับสนได้ หลักการของ **Pythonic Way** คือ "Explicit is better than implicit" (ชัดเจนดีกว่าคลุมเครือ) **ดังนั้น ควรใส่วงเล็บ () เสมอ** เมื่อมีตัวดำเนินการหลายชนิดผสมกัน เพื่อให้อ่านง่ายและมั่นใจว่าผลลัพธ์ถูกต้อง #### ตัวอย่างลำดับความสำคัญของตัวดำเนินการ ```{code-block} python :linenos: # BAD: Confusing to read # Does it mean (5 + 10) * 2 or 5 + (10 * 2)? result = 5 + 10 * 2 > 20 and 5 - 1 == 4 # GOOD: Clear and explicit # The logic is immediately obvious to human readers result = (5 + (10 * 2)) > 20 and ((5 - 1) == 4) ``` ## Control Flow: If-Else (การควบคุมทิศทางโปรแกรม) โปรแกรมที่ดีต้องสามารถ "ตัดสินใจ" เลือกทำงานในเส้นทางที่แตกต่างกันได้ตามเงื่อนไขที่กำหนด โครงสร้างพื้นฐานในการควบคุมทิศทางคือ `if`, `else` และ `elif` (ย่อมาจาก else if) ### If Statement ใช้ตรวจสอบเงื่อนไข หากเป็นจริง (`True`) จะทำคำสั่งภายในบล็อกนั้น (สังเกตการย่อหน้า Indentation) ```{code-block} python :linenos: battery_level = 10 # Note: No parentheses () needed around the condition in Python if battery_level < 20: print(f"Warning: Low Battery ({battery_level}%)") ``` ### If-Else Statement ใช้เมื่อมีทางเลือก 2 ทาง คือ "ถ้าจริงทำ A" และ "ถ้าไม่จริงทำ B" ```{code-block} python :linenos: is_connected = False if is_connected: print("Status: System Online") else: print("Status: Connection Failed") ``` ### If-Elif-Else Statement ใช้เมื่อมีทางเลือกมากกว่า 2 ทาง โปรแกรมจะตรวจสอบเงื่อนไขจากบนลงล่าง และจะทำงานในบล็อกแรกที่เป็นจริงเพียงบล็อกเดียวเท่านั้น ```{code-block} python :linenos: # Robot Modes: 0=Idle, 1=Manual, 2=Auto mode = 1 if mode == 0: print("Mode: Idle") elif mode == 1: print("Mode: Manual Control") elif mode == 2: print("Mode: Autonomous") else: print("Error: Unknown Mode") ``` **แบบฝึกหัด (Exercise)** **โจทย์:** จงเขียนโปรแกรม **ระบบตัดเกรด (Grading System)** โดยกำหนดตัวแปร `score` ให้มีค่าคะแนน 0-100 และแสดงผลเกรดตามเงื่อนไขดังนี้: * **A:** คะแนน $\geq$ 80 * **B:** คะแนน $\geq$ 70 * **C:** คะแนน $\geq$ 60 * **D:** คะแนน $\geq$ 50 * **F:** คะแนน $<$ 50 ```{only} latex **หมายเหตุ:** สำหรับเฉลย สามารถดูได้ที่เวอร์ชันออนไลน์ (Interactive Web) ``` ````{only} html :::{dropdown} **คลิกเพื่อดูเฉลย (Click to show solution)** :color: primary :icon: check ```{code-block} python :linenos: # Define the score score = 75 if score >= 80: grade = "A" elif score >= 70: grade = "B" elif score >= 60: grade = "C" elif score >= 50: grade = "D" else: grade = "F" print(f"Score: {score}, Your Grade is: {grade}") ``` ::: ```` ## Data Structures (โครงสร้างข้อมูล) เมื่อข้อมูลมีจำนวนมาก การเก็บในตัวแปรเดี่ยวๆ (เช่น `x1`, `x2`, `x3`...) จะจัดการยากและไม่ยืดหยุ่น Python จึงมีโครงสร้างข้อมูลแบบกลุ่มให้ใช้งาน เพื่อให้เราสามารถเก็บข้อมูลหลายๆ ตัวไว้ในตัวแปรชื่อเดียวได้ ### List (ลิสต์) **List** คือกลุ่มข้อมูลที่เรียงต่อกันเป็นลำดับ (Ordered) และ **แก้ไขได้ (Mutable)** ข้อมูลภายในจะมีหมายเลขลำดับกำกับเรียกว่า **Index** (เริ่มนับจาก 0) นิยมใช้เก็บข้อมูลชุดที่อาจมีการเพิ่มลดจำนวนได้ เช่น ข้อมูลระยะทางจาก Lidar 360 จุด ```{code-block} python :linenos: # Create a list with mixed data types list_data = ["a", 2, 3, "5", "cat"] # 1. Indexing (Accessing individual elements) print(list_data[0]) # First element -> "a" print(list_data[-1]) # Last element -> "cat" # 2. Slicing (Extracting a part of the list) # Format: [start : stop] *Note: excludes the 'stop' index print(list_data[0:3]) # Index 0 to 2 -> ['a', 2, 3] print(list_data[2:]) # Index 2 to the end -> [3, '5', 'cat'] # 3. Modifying the list list_data[0] = "dog" # Change first element list_data.append(100) # Add new element to the end ``` ### Tuple (ทูเพิล) **Tuple** คล้ายกับ List แต่มีคุณสมบัติสำคัญคือ **แก้ไขไม่ได้ (Immutable)** เมื่อสร้างขึ้นมาแล้วจะไม่สามารถเปลี่ยนค่า เพิ่ม หรือลบข้อมูลภายในได้ นิยมใช้เก็บข้อมูลค่าคงที่ที่มาเป็นชุดและไม่ควรถูกเปลี่ยนแปลง เช่น พิกัด $(x, y, z)$ หรือค่าสี $(R, G, B)$ ```{code-block} python :linenos: # Tuples use parentheses () instead of brackets [] tuple_data = (1, 2, 3, 4, 5) list_data = [1, 2, 3, 4, 5] # Accessing data works the same as List print(tuple_data[0]) # Output: 1 # Modifying data list_data[0] = 45 # OK: List is mutable # tuple_data[0] = 45 # ERROR: TypeError: 'tuple' object does not support item assignment ``` ### Dictionary(ดิคชันนารี) **Dictionary** เป็นโครงสร้างข้อมูลที่เก็บข้อมูลเป็นคู่ของ **Key** และ **Value** (คล้าย JSON Format) * **Key:** เปรียบเสมือนคำศัพท์ (ต้องไม่ซ้ำกัน) * **Value:** เปรียบเสมือนคำแปล (เป็นข้อมูลอะไรก็ได้) ในงานหุ่นยนต์ **Dictionary** มีความสำคัญมาก ใช้สำหรับเก็บค่าการตั้งค่า (Configuration) หรือพารามิเตอร์ต่างๆ ของระบบ ```{code-block} python :linenos: # Define a dictionary for robot configuration robot_config = { "name": "Batly_R1", "id": 101, "max_speed": 1.5, "battery_type": "Li-Po" } # 1. Accessing values by Key print(robot_config["name"]) # Output: Batly_R1 print(robot_config["max_speed"]) # Output: 1.5 # 2. Adding or Updating values robot_config["max_speed"] = 2.0 # Update existing key robot_config["location"] = "Lab" # Add new key-value pair # 3. Checking keys # Useful to prevent errors before accessing if "battery_type" in robot_config: print(f"Battery: {robot_config['battery_type']}") ``` ## Loops (การวนซ้ำ) ในการเขียนโปรแกรม บ่อยครั้งที่เราจำเป็นต้องสั่งให้คอมพิวเตอร์ทำงานเดิมซ้ำๆ หลายครั้ง เช่น การอ่านค่าจากเซนเซอร์ 100 ครั้งเพื่อหาค่าเฉลี่ย หรือการสั่งให้หุ่นยนต์เดินไปเรื่อยๆ จนกว่าจะเจอสิ่งกีดขวาง ภาษา Python มีคำสั่งสำหรับการวนซ้ำ 2 รูปแบบหลัก คือ `for` และ `while` ### For Loop **For Loop** เหมาะสำหรับการวนซ้ำที่ **"รู้จำนวนรอบที่แน่นอน"** หรือใช้สำหรับไล่อ่านข้อมูลสมาชิกภายใน **List**, **Tuple**, หรือ **String** ทีละตัวจนครบ #### การวนอ่านค่าใน List (Iteration) ```{code-block} python :linenos: # List of sensor readings (e.g., distances in meters) sensor_readings = [1.2, 0.8, 3.5, 0.4, 2.1] print("Analyzing sensor data...") # Loop through each item in the list for dist in sensor_readings: # Check condition inside the loop if dist < 0.5: print(f"Warning: Obstacle detected at {dist}m") else: print(f"Path clear at {dist}m") ``` #### การใช้งานฟังก์ชัน range() หากต้องการวนรอบตามจำนวนครั้งที่กำหนด สามารถใช้ฟังก์ชัน range() เพื่อสร้างลำดับตัวเลขได้ โดยมีรูปแบบการใช้งาน 3 แบบ: * **range(stop):** เริ่มจาก 0 ถึง stop-1 * **range(start, stop):** เริ่มจาก start ถึง stop-1 * **range(start, stop, step):** เพิ่มค่าทีละ step ```{code-block} python :linenos: # 1. Basic range (0 to 4) print("--- Count 0 to 4 ---") for i in range(5): print(f"Count: {i}") # 2. Range with start and stop: range(start, stop) -> Count 5 to 9 print("\n--- 2. Start to Stop (5 to 9) ---") for i in range(5, 10): print(f"Count: {i}") # 3. Range with step (Start at 2, Stop before 20, Step 3) print("\n--- Step Example ---") for i in range(2, 20, 3): print(f"Step: {i}") ``` ### While Loop **While Loop** เหมาะสำหรับการวนซ้ำที่ **"ไม่รู้จำนวนรอบที่แน่นอน"** โดยโปรแกรมจะทำงานวนไปเรื่อยๆ ตราบใดที่เงื่อนไขยังเป็นจริง (`True`) ในงานควบคุมหุ่นยนต์ เรามักใช้ While Loop สำหรับสร้าง **Main Loop** ของโปรแกรม เพื่อให้หุ่นยนต์ทำงานตลอดเวลาจนกว่าจะถูกสั่งปิด หรือใช้รอจนกว่าเซนเซอร์จะอ่านค่าได้ตามที่ต้องการ #### ตัวอย่างที่ 1: การใช้งาน While Loop พื้นฐาน โค้ดด้านล่างจะจำลองการชาร์จแบตเตอรี่ โดยจะวนลูปชาร์จทีละ 10% ไปเรื่อยๆ จนกว่าแบตเตอรี่จะเต็ม ```{code-block} python :linenos: battery_level = 10 # วนลูปตราบใดที่แบตเตอรี่ยังน้อยกว่า 100 while battery_level < 100: battery_level += 10 # ชาร์จเพิ่มรอบละ 10% print(f"Charging... Battery at {battery_level}%") print("Battery Full!") ``` ตัวอย่างที่ 2: การใช้ while ร่วมกับ else ในภาษา Python เราสามารถใช้ else ต่อท้าย while ได้ โดยคำสั่งในบล็อก else จะทำงาน ก็ต่อเมื่อเงื่อนไขของ while กลายเป็นเท็จ (False) (ลูปทำงานจนจบตามปกติ) เราสามารถนำมาประยุกต์ใช้เพื่ออัปเดตสถานะของระบบเมื่อการทำงานในลูปเสร็จสิ้นสมบูรณ์ได้ เช่น การเปลี่ยนสถานะการชาร์จ ```{code-block} python :linenos: battery_level = 10 charging = True print(f"System State -> Charging: {charging}") # วนลูปตราบใดที่แบตเตอรี่ยังน้อยกว่า 100 while battery_level < 100: battery_level += 10 print(f"Charging... Battery at {battery_level}%") else: # ทำงานเมื่อ battery_level < 100 กลายเป็นเท็จ (ชาร์จเต็มแล้ว) charging = False print("Loop finished naturally.") print("Battery Full!") print(f"System State -> Charging: {charging}") ``` ### Loop Control Statements (คำสั่งควบคุมลูป) บางครั้งเราอาจต้องการบังคับให้ลูปหยุดทำงานทันที หรือข้ามการทำงานบางรอบ เราสามารถใช้คำสั่งพิเศษได้ดังนี้: * **break:** สั่งให้ **"หยุดและออกจากลูปทันที"** (มักใช้เมื่อเจอสิ่งที่ค้นหาแล้ว หรือเมื่อเจอ Error) * **continue:** ใช้สำหรับ **"ข้ามการทำงานในรอบปัจจุบัน"** (Skip) เพื่อกระโดดไปเริ่มรอบถัดไปทันที โดยไม่ทำคำสั่งที่เหลืออยู่ด้านล่าง นิยมใช้ในการกรองข้อมูลที่ไม่ต้องการทิ้งไป (Filtering) #### ตัวอย่างการใช้งาน break ```{code-block} python :linenos: # Search for a specific ID target_id = 55 data_stream = [10, 23, 55, 89, 12] for id_num in data_stream: if id_num == target_id: print(f"Found target ID: {id_num}. Stopping loop.") break # Exit the loop immediately print(f"Checking ID: {id_num}...") ``` #### ตัวอย่างการใช้งาน continue ```{code-block} python :linenos: # Mock sensor data (negative numbers represent errors) sensor_data = [1.5, -1.0, 2.3, -1.0, 0.8] print("Start processing sensor data...") for val in sensor_data: # Check for error code if val < 0: print(f"Found error value: {val}, skipping...") continue # Jump to the next iteration immediately # This part runs ONLY if val is valid (not skipped) print(f"Valid distance: {val} m") ``` ## Functions (ฟังก์ชัน) ฟังก์ชันเปรียบเสมือน **"เครื่องจักร"** หรือ **"สูตรคำนวณ"** ที่เราสร้างเตรียมไว้ เมื่อต้องการใช้งานเพียงแค่เรียกชื่อและส่งวัตถุดิบ (Arguments) เข้าไป ฟังก์ชันจะทำการประมวลผลและส่งผลลัพธ์ (Return) กลับออกมา ช่วยให้โค้ดเป็นระเบียบ ลดการเขียนซ้ำซ้อน และง่ายต่อการแก้ไข ### การสร้างและเรียกใช้ฟังก์ชันพื้นฐาน โครงสร้างพื้นฐานเริ่มต้นด้วยคีย์เวิร์ด `def` ตามด้วยชื่อฟังก์ชัน และวงเล็บ ตัวอย่างนี้คือการแปลงสูตรคณิตศาสตร์ $f(x) = x^2 + 2x + 1$ ให้เป็นโค้ด Python โดยรับค่า `x` เข้าไปคำนวณ และส่งผลลัพธ์กลับออกมาด้วยคำสั่ง `return` ```{code-block} python :linenos: # สร้างฟังก์ชันชื่อ func1 ที่รับค่า x def func1(x): # คำนวณสมการและส่งค่ากลับ return x**2 + 2*x + 1 # เรียกใช้งานฟังก์ชันโดยส่งเลข 3 เข้าไปแทนที่ x y = func1(3) print(f"f(3) = {y}") # ผลลัพธ์: 16 ``` ### ฟังก์ชันกับการนำไปประยุกต์ใช้งาน เราสามารถประยุกต์ใช้ฟังก์ชันกับสูตรทั่วไปได้ เช่น การคำนวณพื้นที่วงกลม ทำให้เราไม่ต้องเขียนสูตร $\pi r^2$ ซ้ำๆ ทุกครั้งที่ต้องการคำนวณ ```{code-block} python :linenos: # สร้างฟังก์ชันคำนวณพื้นที่วงกลม รับค่ารัศมี (r) def circle_area(r): area = 3.14 * (r**2) return area # กำหนดค่ารัศมีและเรียกใช้งาน radius = 5.0 result = circle_area(radius) print(f"Circle area of radius {radius} is {result} sq-m") ``` ### การรับค่าเข้าหลายตัว (Multiple Arguments) ฟังก์ชันไม่จำเป็นต้องรับค่าแค่วัตถุดิบเดียว เราสามารถส่งตัวแปรเข้าไปกี่ตัวก็ได้โดยใช้เครื่องหมายลูกน้ำ (`,`) คั่น ```{code-block} python :linenos: # ฟังก์ชันรับค่า 2 ตัว (a และ b) เพื่อนำมาบวกกัน def summation(a, b): return a + b # เรียกใช้งานโดยส่งเลข 4 และ 5 เข้าไป val1 = 4 val2 = 5 total = summation(val1, val2) print(f"Sum of {val1} and {val2} is: {total}") ``` ### การคืนค่ากลับหลายตัว (Multiple Return Values) จุดเด่นของ Python ที่ทรงพลังมากในงานหุ่นยนต์ คือฟังก์ชันสามารถ **"คืนค่าผลลัพธ์ได้หลายตัวพร้อมกัน"** ซึ่งเหมาะมากกับฟังก์ชันประเภทอ่านค่าเซนเซอร์ที่มักจะได้ค่ากลับมาพร้อมๆ กัน (เช่น ได้ทั้งพิกัดแกน X และ Y) ผลลัพธ์ที่ได้กลับมาจะอยู่ในรูปแบบของ **Tuple** ซึ่งเราสามารถประกาศตัวแปรมารับค่าแยกกันได้ทันที (เรียกว่า Unpacking) ```{code-block} python :linenos: # ฟังก์ชันนี้ส่งค่ากลับ 2 ค่าพร้อมกัน คือ ผลบวก และ ผลคูณ def sum_mul(a, b): return a + b, a * b # สังเกตการใช้ตัวแปร 2 ตัว (res_sum, res_mul) มารอรับค่าผลลัพธ์ res_sum, res_mul = sum_mul(4, 5) print(f"Result Sum: {res_sum}") print(f"Result Multiply: {res_mul}") ``` ### Functions with List (การใช้งานร่วมกับ List) ในงานเขียนโปรแกรมจริง เรามักไม่ได้ส่งแค่ตัวเลขตัวเดียวเข้าฟังก์ชัน แต่สามารถส่งโครงสร้างข้อมูลที่เก็บค่าหลายๆ ค่าอย่าง List เข้าไปประมวลผลได้เลย ซึ่งมีประโยชน์มาก เช่น การส่งชุดข้อมูลจากเซนเซอร์เข้าไปหาค่าเฉลี่ย #### 1. การส่ง List เข้าไปหาผลรวม (Summation) ฟังก์ชัน `sum_list` จะรับค่าข้อมูล List เข้ามา จากนั้นใช้ `for` loop ดึงตัวเลขออกมาทีละตัวเพื่อบวกสะสมไว้ในตัวแปร `total` ```{code-block} python :linenos: # Function to calculate sum of a list def sum_list(list_input): total = 0 for i in list_input: total += i return total # Test Data number_list = [1, 5, 8, 10, 4] result_sum = sum_list(number_list) print(f"Summation: {result_sum}") ``` #### 2. การส่ง List เข้าไปตรวจสอบเงื่อนไข (Counting Items) ฟังก์ชัน `count_even` มีความซับซ้อนขึ้นมาอีกระดับ โดยจะใช้ `if` ซ้อนเข้าไปในลูป เพื่อกรองและนับเฉพาะตัวเลขที่ตรงตามเงื่อนไข (ในที่นี้คือหาเลขคู่โดยใช้ `i % 2 == 0`) ```{code-block} python :linenos: # Function to count even numbers def count_even(my_list): count = 0 for i in my_list: # Check if divisible by 2 (Even number) if i % 2 == 0: count += 1 print(f"Even number found: {i}") return count # Test Data number_list = [1, 5, 8, 10, 4] total_even = count_even(number_list) print(f"Total even numbers: {total_even}") ``` ### Algorithm Optimization (การปรับปรุงประสิทธิภาพโค้ด) ในงานหุ่นยนต์ที่ต้องประมวลผลข้อมูลจำนวนมหาศาล (เช่น ข้อมูลภาพ หรือ Lidar) ความเร็วเป็นเรื่องสำคัญ การตรวจสอบเลขคู่/คี่ สามารถทำได้ 2 วิธี: * **Modulo (`% 2`):** เป็นวิธีมาตรฐานทางคณิตศาสตร์ * **Bitwise AND (`& 1`):** เป็นวิธีทางคอมพิวเตอร์ระดับล่าง ซึ่งทำงานได้เร็วกว่าการหาร หลักการคือ เลขคู่ในฐานสอง บิตสุดท้ายจะเป็น 0 เสมอ ส่วนเลขคี่จะเป็น 1 ดังนั้นการนำไป `& 1` จะได้ผลลัพธ์ทันที ```{code-block} python :linenos: # Optimized version using Bitwise Operator def count_even_binary(my_list): count = 0 for i in my_list: # Check the last bit # If (i & 1) is 0 (False), it means Even. # So we use 'not' to make it True for Even numbers. if not (i & 1): count += 1 print(f"Even number: {i}") return count large_data = [124958373, 3494385735934, 39385384357, 34835375385834] print(f"Total even (Bitwise): {count_even_binary(large_data)}") ``` ## Object-Oriented Programming (การเขียนโปรแกรมเชิงวัตถุ) ในการเขียนโปรแกรมขนาดใหญ่ การจัดการข้อมูลกระจัดกระจายด้วยตัวแปรเดี่ยวๆ จะทำให้โค้ดยุ่งเหยิงและแก้ไขยาก ตัวอย่างเช่น หากต้องเก็บข้อมูลพนักงานด้วยตัวแปรปกติ: ```{code-block} python :linenos: # Without OOP: Variables are scattered and hard to manage emp1_name = "John" emp1_age = 28 emp1_salary = 28000 emp2_name = "Jane" emp2_age = 33 emp2_salary = 50000 ``` หากมีพนักงาน 100 คน จำเป็นต้องประกาศตัวแปรถึง 300 ตัว ซึ่งไม่สามารถจัดการได้จริง **OOP** จึงเข้ามาแก้ปัญหานี้โดยการมองทุกอย่างเป็น **วัตถุ (Object)** ที่มีทั้งข้อมูล (Attributes) และการกระทำ (Methods) รวมอยู่ในก้อนเดียว ### Class and Object (คลาสและวัตถุ) * **Class (คลาส):** เปรียบเสมือน **"แม่พิมพ์"** หรือแบบแปลน (Blueprint) ที่กำหนดว่าวัตถุนี้ต้องมีข้อมูลอะไรบ้าง * **Object (วัตถุ):** เปรียบเสมือน **"ชิ้นงานจริง"** (Instance) ที่ถูกสร้างออกมาจากแม่พิมพ์นั้น โดยอัดข้อมูลจริงๆ เข้าไป ### 1. การสร้าง Class พื้นฐาน (Constructor) ในการสร้าง Class เราจะใช้ฟังก์ชันพิเศษที่ชื่อว่า `__init__` (ย่อมาจาก Initialize) ซึ่งทำหน้าที่เป็น **"พนักงานต้อนรับ"** ที่จะทำงานอัตโนมัติทันทีที่มีการสร้าง Object ใหม่ เพื่อเอาข้อมูลเริ่มต้นไปบรรจุไว้ในตัวแปร **ข้อควรรู้:** คำว่า `self` ในภาษา Python หมายถึง **"ตัววัตถุเอง"** เป็นคำบังคับที่ต้องใส่ไว้เป็น Parameter แรกเสมอ เพื่อให้ตัวแปรต่างๆ รู้ว่ามันเป็นข้อมูลของวัตถุก้อนไหน ```{code-block} python :linenos: # สร้างแบบแปลน (Class) class Employee: # Constructor: ทำงานอัตโนมัติเมื่อถูกสร้าง (สังเกตเครื่องหมาย Underscore 2 ตัวหน้าหลัง) def __init__(self, name, age, salary): # นำค่าที่รับมา ไปเก็บไว้ในตัวแปรของตัวมันเอง (self) self.name = name self.age = age self.salary = salary print(f"Created Employee: {self.name}, Age: {self.age}, Salary: {self.salary}") # สร้างชิ้นงานจริง (Objects) โดยไม่ต้องส่งค่าให้ self print("--- Creating Objects ---") emp1 = Employee("John", 28, 28000) emp2 = Employee("Jane", 33, 50000) # การเรียกดูข้อมูลภายใน Object print("\n--- Accessing Data ---") print(f"Employee 1 is {emp1.name} and he earns {emp1.salary}") ``` เราสามารถปรับปรุงโครงสร้างของ Class ให้รองรับการทำงานมากขึ้นได้ดังนี้ **ตัวอย่างโครงสร้าง Class** ```{code-block} python :linenos: class Employee: # Constructor: Runs automatically when a new object is created def __init__(self, name, age, salary): self.name = name # Public Attribute self.age = age # Public Attribute self.__salary = salary # Private Attribute (Hidden) print(f"Created Employee: {self.name}") # Method to update salary def update_salary(self, amount): self.__salary += amount print(f"Name: {self.name}, New Salary: {self.__salary}") # Method to update age def update_age(self, amount): self.age += amount print(f"Name: {self.name}, New Age: {self.age}") # Getter method for private attribute def get_salary(self): return self.__salary # Create Objects (Instanciation) emp1 = Employee("John", 28, 28000) emp2 = Employee("Jane", 33, 50000) # Calling methods emp1.update_age(1) # John is now 29 emp1.update_salary(2000) # John's salary increased ``` ### คำอธิบายเชิงลึก: ส่วนประกอบของ OOP เพื่อให้เข้าใจการทำงานของโค้ดข้างต้น จำเป็นต้องทำความรู้จักกับ 5 คำศัพท์สำคัญและการทำงานภายใน ดังนี้: #### Class vs Object (Instance) * **Class (คลาส):** เปรียบเสมือน **"แม่พิมพ์"** หรือ **"แบบแปลน"** (Blueprint) ซึ่งมีไว้กำหนดโครงสร้างว่าสิ่งนี้คืออะไร มีข้อมูลอะไรบ้าง แต่ยังจับต้องไม่ได้และยังไม่ได้ถูกสร้างขึ้นในหน่วยความจำ (เช่น `class Employee`) * **Object หรือ Instance (วัตถุ):** เปรียบเสมือน **"ชิ้นงานจริง"** ที่ถูกสร้างออกมาจากแม่พิมพ์นั้น เมื่อสร้างออกมาแล้วจะมีตัวตนจริงๆ ในหน่วยความจำ และสามารถนำไปใช้งานได้ (เช่น `emp1`, `emp2`) #### `__init__` (Constructor) ในภาษา Python ฟังก์ชันชื่อ `__init__` คือฟังก์ชันพิเศษที่เรียกว่า **Constructor** (ตัวก่อสร้าง) * **หน้าที่:** เตรียมความพร้อมและกำหนดค่าเริ่มต้นให้กับวัตถุ * **การทำงาน:** จะถูกเรียกใช้งาน **อัตโนมัติทันที** เพียงครั้งเดียว เมื่อมีการสร้าง Object ใหม่ (ตอนที่พิมพ์ `Employee(...)`) หากไม่มีส่วนนี้ วัตถุที่สร้างมาจะว่างเปล่าไม่มีข้อมูล #### Attributes (ตัวแปรภายในคลาส) เมื่อตัวแปรเข้าไปอยู่ใน Class จะถูกเรียกว่า **Attributes** หรือ **Properties** ซึ่งเปรียบเสมือน "ข้อมูลประจำตัว" ของวัตถุนั้นๆ * **Public Attribute (เช่น `self.name`):** ข้อมูลสาธารณะ สามารถเข้าถึงหรือแก้ไขได้โดยตรง เปรียบเสมือนป้ายชื่อพนักงานที่ใครก็มองเห็น * **Private Attribute (เช่น `self.__salary`):** ข้อมูลส่วนตัว ที่มี `__` นำหน้า เปรียบเสมือนเงินในกระเป๋าสตางค์ที่ไม่ควรให้ผู้อื่นหยิบไปดูหรือแก้ไขโดยตรง ต้องผ่านขั้นตอนที่ถูกต้องเท่านั้น #### Methods (ฟังก์ชันภายในคลาส) เมื่อฟังก์ชันเข้าไปอยู่ใน Class จะถูกเปลี่ยนชื่อเรียกว่า **Method** * **Function:** คือคำสั่งที่อยู่อย่างอิสระ เรียกใช้เมื่อไหร่ก็ได้ (เช่น `print()`, `len()`) * **Method:** คือความสามารถเฉพาะตัวของ Object นั้นๆ จะเรียกใช้ได้ก็ต่อเมื่อมี Object เกิดขึ้นมาแล้วเท่านั้น (เช่น `emp1.update_salary()`) เปรียบเสมือนกริยาอาการ เช่น "พนักงานเดิน", "พนักงานรับเงินเดือน" #### `self` (ตัวระบุตัวตน) ในทุก Method จะมีคำว่า `self` เป็นพารามิเตอร์ตัวแรกเสมอ * **ความหมาย:** `self` หมายถึง **"ตัวของวัตถุเอง"** * **ประโยชน์:** เนื่องจาก Class เป็นแค่แม่พิมพ์ที่ใช้สร้าง Object ได้หลายตัว (emp1, emp2, ...) คอมพิวเตอร์จำเป็นต้องรู้ว่าคำสั่งนั้นกำลังกระทำกับวัตถุตัวไหน * เมื่อสั่ง `emp1.update_age()` -> `self` จะหมายถึง `emp1` * เมื่อสั่ง `emp2.update_age()` -> `self` จะหมายถึง `emp2` --- **ภาพสรุปการทำงาน** ```{code-block} python :linenos: # 1. Class (The Blueprint) class Employee: # 2. Constructor (Runs automatically when creating object) def __init__(self, name, age, salary): # 3. Attributes (Data stored in the instance) self.name = name self.age = age ``` ```{code-block} python :linenos: # 4. Object/Instance (Creating the actual object) emp1 = Employee("John", 28, 28000) # Internal process: # 1. Python calls __init__ automatically # 2. 'self' refers to 'emp1' # 3. emp1.name is set to "John" ``` ### Encapsulation (การห่อหุ้มข้อมูล) สังเกตตัวแปร `__salary` (มีขีดล่าง 2 ขีดนำหน้า) นี่คือ **Private Attribute** ซึ่งมีคุณสมบัติพิเศษคือ **"ห้ามเข้าถึงจากภายนอกโดยตรง"** * `print(emp1.age)` -> **ทำได้** (Public) * `print(emp1.__salary)` -> **Error!** (Private) จำเป็นต้องเข้าถึงหรือแก้ไขผ่าน **Method** ที่เตรียมไว้ให้เท่านั้น (เช่น `get_salary()` หรือ `update_salary()`) เพื่อความปลอดภัยของข้อมูล --- ### แบบฝึกหัด (Exercise) **โจทย์:** จงเขียนคลาส **Car** โดยมีข้อกำหนดดังนี้: 1. เก็บข้อมูล: แบรนด์ (`brand`), โมเดล (`model`), ปี (`year`), สี (`color`) 2. เก็บข้อมูลลับ (Private): ระยะทางที่ขับแล้ว (`__mileage`), น้ำมันคงเหลือ (`__fuel`) 3. สร้าง Method: * `update_mileage(amount)`: เพิ่มระยะทางตามค่าที่รับมา * `update_fuel(amount)`: เปลี่ยนค่าน้ำมันให้เป็นค่าใหม่ (เติมน้ำมัน/ใช้น้ำมัน) ````{only} html :::{dropdown} **คลิกเพื่อดูเฉลย (Click to show solution)** :color: primary :icon: check ```{code-block} python :linenos: class Car: # Constructor: Function to initialize the object def __init__(self, brand, model, year, color, mileage, fuel): self.brand = brand # Public self.model = model self.year = year self.color = color self.__mileage = mileage # Private (Mileage) self.__fuel = fuel # Private (Fuel Level) print(f"Created {self.brand} {self.model}") # Method to update mileage def update_mileage(self, amount): self.__mileage += amount print(f"Model: {self.model}, New Mileage: {self.__mileage}") # Method to update fuel level def update_fuel(self, amount): self.__fuel = amount print(f"Model: {self.model}, Fuel Level: {self.__fuel}") # Object Instantiation (Creating objects from class) c1 = Car("Tesla", "Model 3", 2024, "White", 0, 50) c2 = Car("Honda", "Civic", 2022, "Blue", 24015, 35) # Method Calls c1.update_mileage(1000) # Drive c1 for 1000 km c2.update_fuel(50) # Refuel c2 ``` ::: ```` ```{only} latex **หมายเหตุ:** สำหรับเฉลย สามารถดูได้ที่เวอร์ชันออนไลน์ (Interactive Web) ``` ## Custom Modules (การสร้างโมดูลใช้เอง) เมื่อโปรแกรมเริ่มซับซ้อนขึ้น การเขียนโค้ดทุกอย่างรวมไว้ในไฟล์เดียวจะทำให้จัดการยากและแก้ไขลำบาก แนวทางปฏิบัติที่ดีคือการแยกโค้ดออกเป็นส่วนๆ ตามหน้าที่ เรียกว่า **Module** ### หลักการทำงาน 1. สร้างไฟล์ Python (`.py`) เพื่อเก็บฟังก์ชันหรือคลาสที่เกี่ยวข้องกัน (เช่น `sensor_lib.py` เก็บคำสั่งอ่านเซนเซอร์) 2. ในไฟล์หลัก (`main.py`) ให้ใช้คำสั่ง `import` เพื่อดึงความสามารถจากไฟล์นั้นมาใช้ **ตัวอย่างการสร้าง Module** สมมติเราสร้างไฟล์ชื่อ **`my_math.py`** เพื่อเก็บสูตรคำนวณทางคณิตศาสตร์ ```{code-block} python :linenos: :caption: my_math.py # Variable PI = 3.14159 # Function to calculate circle area def circle_area(radius): return PI * (radius ** 2) # Class inside a module class Rectangle: def __init__(self, w, h): self.w = w self.h = h def area(self): return self.w * self.h ``` **ตัวอย่างการเรียกใช้ในไฟล์หลัก** สร้างไฟล์ **`main.py`** และเรียกใช้ module ข้างต้น ```{code-block} python :linenos: :caption: main.py # Method 1: Import the whole module import my_math print(f"PI value: {my_math.PI}") print(f"Circle Area: {my_math.circle_area(5)}") # Method 2: Import specific parts (Recommended for clean code) from my_math import Rectangle # Use the class directly without 'my_math.' prefix rect = Rectangle(10, 20) print(f"Rectangle Area: {rect.area()}") # Method 3: Import with Alias (Nicknaming) import my_math as mm print(f"Using Alias: {mm.circle_area(10)}") ``` ```{tip} **Pro Tip:** ในงาน ROS2 เราจะใช้การ Import แบบนี้ตลอดเวลา เช่น `from rclpy.node import Node` ซึ่งแปลว่า "ไปที่โมดูล `rclpy` เข้าไปที่ส่วน `node` แล้วเอาคลาส `Node` มาใช้" ``` ### รูปแบบการเรียกใช้งานโมดูล (Import Styles) ในการดึงโค้ดจาก Module อื่นมาใช้งาน ภาษา Python มีคำสั่ง `import` ให้เลือกใช้หลายรูปแบบ ขึ้นอยู่กับความเหมาะสมและเพื่อลดความซ้ำซ้อนในการพิมพ์โค้ด สมมติว่าเรามีไฟล์ `my_math.py` ที่มีทั้งตัวแปร `PI`, ฟังก์ชัน `circle_area()`, และคลาส `Rectangle` เราสามารถเรียกใช้ได้ 3 รูปแบบหลักดังนี้: **1. การ Import ทั้งโมดูล (Basic Import)** ดึงมาทั้งหมด เวลาเรียกใช้ต้องอ้างอิงโดยพิมพ์ **"ชื่อโมดูล.ชื่อฟังก์ชัน"** เสมอ วิธีนี้ปลอดภัยที่สุดแต่อาจจะพิมพ์ยาว ```{code-block} python :linenos: import my_math # ต้องมี my_math. นำหน้าเสมอ print(my_math.PI) print(my_math.circle_area(5)) ``` **2. การ Import เฉพาะส่วนที่ต้องการ (From ... Import ...)** ดึงมาเฉพาะฟังก์ชัน หรือคลาสที่ระบุ วิธีนี้ **เป็นที่นิยมมากที่สุด** เพราะทำให้โค้ดหลักสั้นลง สามารถเรียกใช้ฟังก์ชันได้โดยตรงโดยไม่ต้องพิมพ์ชื่อโมดูลนำหน้า ```{code-block} python :linenos: # ระบุเจาะจงเลยว่าจะดึงแค่ฟังก์ชัน circle_area และคลาส Rectangle from my_math import circle_area, Rectangle # สามารถเรียกชื่อฟังก์ชันหรือคลาสได้โดยตรงเลย! print(circle_area(5)) rect = Rectangle(10, 20) ``` > **ข้อควรระวัง:** หลีกเลี่ยงการใช้ `from my_math import *` (การใส่ดอกจันหมายถึงดึงมาทั้งหมดโดยไม่ต้องระบุชื่อ) เพราะอาจทำให้ชื่อฟังก์ชันที่ดึงมา ไปซ้ำซ้อนกับตัวแปรหรือโค้ดที่เราเขียนไว้ในไฟล์หลักได้ **3. การ Import แบบตั้งชื่อเล่น (Alias Import)** กรณีที่ชื่อโมดูลเดิมยาวเกินไป หรือกลัวชื่อซ้ำ เราสามารถเปลี่ยนชื่อเรียก (ตั้งชื่อเล่น) ให้สั้นลงได้ด้วยคำว่า `as` ```{code-block} python :linenos: import my_math as mm # พิมพ์แค่ mm แทนคำว่า my_math print(mm.circle_area(10)) ``` ### แบบฝึกหัด: การสร้างและเรียกใช้งานโมดูล (Module Practice) **โจทย์:** ให้นักศึกษานำความรู้เรื่อง Function และ Class ที่ได้เรียนไปก่อนหน้านี้ มาลองจัดระเบียบใหม่ด้วยการแยกไฟล์โค้ด **คำสั่ง:** 1. **สร้างไฟล์ชื่อ `my_module.py`** * ให้นำโค้ดฟังก์ชัน `sum_list` (ฟังก์ชันหาผลรวมตัวเลขใน List) และคลาส `Employee` (ระบบจัดการพนักงาน) ที่เราเคยสร้างไว้ มาวางรวมกันไว้ในไฟล์นี้ * ไฟล์นี้จะทำหน้าที่เป็น "คลังเก็บเครื่องมือ" ของเรา ดังนั้น **ห้าม** มีการเรียกใช้งานฟังก์ชัน (Calling) หรือสร้าง Object ใดๆ ในไฟล์นี้ ให้มีเฉพาะโครงสร้าง `def` และ `class` เท่านั้น 2. **สร้างไฟล์ชื่อ `test_module.py`** (ต้องเซฟให้อยู่ในโฟลเดอร์เดียวกันกับไฟล์แรก) * ให้ Import คลาส `Employee` เข้ามาโดยใช้วิธี `from ... import ...` * ให้ Import ฟังก์ชัน `sum_list` เข้ามาโดยใช้วิธีตั้งชื่อเล่น (Alias) เป็น `sl` (เช่น `import ... as ...`) * จากนั้นให้เขียนโค้ดในไฟล์นี้เพื่อ: * สร้างออบเจกต์ (Object) พนักงานใหม่ขึ้นมา 1 คน และทดลองอัปเดตเงินเดือน * สร้าง List ของตัวเลขขึ้นมา 1 ชุด แล้วส่งเข้าไปหาผลรวมผ่านตัวแปร `sl` ที่เราตั้งชื่อเล่นไว้ * สั่ง `print()` แสดงผลลัพธ์ข้อมูลพนักงานและผลรวมของตัวเลขออกทางหน้าจอ