วันพฤหัสบดีที่ 10 เมษายน พ.ศ. 2568

Python Virtual Environment Part1 Overview

 

Virtual Environment คืออะไร?

เวลาเราเขียนโปรแกรม (โดยเฉพาะภาษา Python) เรามักจะต้องใช้ไลบรารีหรือเครื่องมือจากคนอื่นมาช่วย เช่น requests, numpy, django อะไรแบบนี้ ซึ่งแต่ละโปรเจกต์อาจจะใช้ไลบรารีต่างเวอร์ชันกัน เช่น โปรเจกต์ A ใช้ Django 3 แต่โปรเจกต์ B ใช้ Django 4 ถ้าเราติดตั้งรวมกันในเครื่องเดียว มันจะตีกันได้ง่ายๆ เลย

Virtual Environment หรือเรียกสั้นๆ ว่า venv คือพื้นที่แยกเฉพาะของแต่ละโปรเจกต์ ที่เราสามารถติดตั้งไลบรารีที่ต้องใช้ได้แบบไม่เกี่ยวกับของระบบหรือโปรเจกต์อื่น ทำให้แต่ละโปรเจกต์มี "โลกส่วนตัว" ของมัน


ทำไมต้องใช้ Virtual Environment?

  • กันปัญหาความเข้ากันไม่ได้ของไลบรารี

    เช่น โปรเจกต์เก่าใช้เวอร์ชันเก่า โปรเจกต์ใหม่ใช้เวอร์ชันใหม่ ถ้าไม่มี venv จะมั่วไปหมดเลย

  • ง่ายต่อการจัดการ

    เรารู้เลยว่าโปรเจกต์นี้ต้องการไลบรารีอะไรบ้าง แค่ดูไฟล์ requirements.txt แล้วติดตั้งใน venv ได้เลย

  • ย้ายเครื่องก็ง่าย

    ถ้าเราจะส่งโปรเจกต์ให้คนอื่นแค่บอกให้สร้าง venv แล้วติดตั้งตาม requirements.txt ทุกอย่างก็พร้อมรัน

     

🔝 venv ที่ได้รับความนิยม

1. venv (builtin)

เป็นเครื่องมือสร้าง virtual environment ที่ติดมากับ Python ตั้งแต่เวอร์ชัน 3.3 ขึ้นไป

✅ ข้อดี:

  • ติดมากับ Python ไม่ต้องติดตั้งเพิ่ม

  • ใช้งานง่าย คำสั่งไม่ซับซ้อน (python -m venv env)

  • รองรับการใช้งานทั่วไปได้ดี

❌ ข้อเสีย:

  • ไม่มีระบบจัดการ dependency แบบ lock file (เช่น Pipfile.lock หรือ poetry.lock)

  • ต้องใช้ pip แยกต่างหากในการจัดการแพ็กเกจ

🎯 ทำไมถึงได้รับความนิยม:

  • เพราะมัน “ติดมากับ Python” เลยไม่ต้องคิดเยอะ ใช้ได้เลย เหมาะกับมือใหม่หรือคนที่อยากควบคุมแบบเรียบง่าย


2. virtualenv

เป็นเครื่องมือรุ่นก่อนที่ venv จะถูกเพิ่มเข้า Python

✅ ข้อดี:

  • ใช้ได้กับ Python ได้หลายเวอร์ชัน รวมถึง Python 2.x

  • ทำงานเร็วกว่า venv เล็กน้อย

  • มีฟีเจอร์เสริมบางอย่างมากกว่า venv

❌ ข้อเสีย:

  • ต้องติดตั้งแยก (pip install virtualenv)

  • ถ้าคนไม่รู้จัก อาจสับสนกับ venv

🎯 ทำไมยังมีคนใช้:

  • ยังใช้ในโปรเจกต์เก่า หรือในระบบที่ต้องรองรับ Python 2.x

  • บางคนคุ้นมือจากยุคก่อน venv จะมี


3. Pipenv

เป็นเครื่องมือที่รวมทั้ง virtual environment และการจัดการ dependency แบบมี lock file

✅ ข้อดี:

  • จัดการ environment + package ได้ในคำสั่งเดียว (pipenv install)

  • มี Pipfile และ Pipfile.lock สำหรับจัดการ dependency อย่างชัดเจน

  • แยก dev dependencies ได้

❌ ข้อเสีย:

  • บางครั้งทำงานช้า

  • มีบั๊กหรือปัญหากับ Python เวอร์ชันใหม่ๆ บ้าง

  • โครงการ Python เองหยุดแนะนำ (แม้ยังใช้ได้)

🎯 ความนิยมลดลง:

  • เคยเป็นทางเลือกหลักช่วงหนึ่ง แต่หลายคนหันไปใช้ poetry หรือ venv + requirements.txt แทน


4. Poetry

เครื่องมือรุ่นใหม่ที่มาแรงมากสำหรับ Python packaging + dependency management

✅ ข้อดี:

  • ใช้ไฟล์ pyproject.toml ซึ่งเป็นมาตรฐานใหม่

  • จัดการ dependency และ environment ได้ในตัว

  • สร้าง package เพื่อแจกจ่ายได้ง่าย

  • ทำงานเร็ว และมีระบบ lock ที่ดี

❌ ข้อเสีย:

  • ต้องเรียนรู้เพิ่ม (แต่ไม่ยาก)

  • ไม่ติดมากับ Python ต้องติดตั้งเพิ่ม

🎯 ทำไมเริ่มได้รับความนิยม:

  • เป็นเครื่องมือ “ครบจบในตัวเดียว” สำหรับคนที่ต้องการความเป็นระเบียบและวางแผนพัฒนาโปรเจกต์ระยะยาว

  • โดดเด่นในสาย open source และ professional dev



5. Anaconda / Miniconda

✅ ข้อดี:

  • มีระบบจัดการ environment ในตัว (conda env create)

  • ใช้ package manager ของตัวเองชื่อ conda ที่จัดการ dependency ได้แม่นยำมากกว่าการใช้ pip อย่างเดียว

  • มีไลบรารีสาย data science ติดมาพร้อม เช่น numpy, pandas, scikit-learn, jupyter ฯลฯ

  • เหมาะกับงานที่ต้องการติดตั้งแพ็กเกจที่ซับซ้อนหรือ compile ยาก เช่น opencv, tensorflow ในบางระบบ

❌ ข้อเสีย:

  • ตัว Anaconda ใหญ่พอสมควร (กินพื้นที่เยอะมาก)

  • อัปเดตช้ากว่า pip บางทีเวอร์ชันใหม่ๆ มาไม่ทัน

  • ไม่ใช่มาตรฐานตรงๆ ของ Python packaging (ไม่ใช้ pyproject.toml)

  • บางครั้ง conda กับ pip ใน environment เดียวกันอาจตีกันได้

🎯 ทำไมถึงได้รับความนิยม:

  • ในกลุ่มสาย Data Science, AI, ML, วิจัย ใช้เยอะมาก เพราะใช้ง่ายและติดตั้งแพ็กเกจยากๆ ได้สบาย

  • Miniconda เป็นเวอร์ชันเล็กกว่า เลือกติดตั้งเฉพาะที่ต้องใช้ได้ (แนะนำสำหรับคนที่ไม่อยากได้ของแถมเยอะ)


เปรียบเทียบเครื่องมือจัดการ Virtual Environment สำหรับ Python

เครื่องมือติดกับ Pythonใช้งานง่ายมีระบบ lockรายละเอียดความนิยม
venvจุดเด่น : เบา เร็ว ใช้ได้ทันที
เหมาะกับสายงาน : ทั่วไป, dev มือใหม่
⭐⭐⭐⭐⭐
virtualenv
จุดเด่น : รองรับหลายเวอร์ชัน รวมถึง Python 2
เหมาะกับสายงาน : โปรเจกต์เก่า, ระบบยืดหยุ่น
⭐⭐⭐⭐
Pipenv⚠️จุดเด่น : จัดการ env + package พร้อมกัน
เหมาะกับสายงาน : โปรเจกต์ระดับกลาง
⭐⭐
Poetry⚠️
จุดเด่น : ครบเครื่อง จัดการ dependency + build package ได้
เหมาะกับสายงาน : dev จริงจัง, สาย production
⭐⭐⭐⭐
Anaconda✅ (conda)
จุดเด่น : ใช้กับแพ็กเกจสาย data science ได้ดี ติดตั้งง่าย
เหมาะกับสายงาน : data science, ML, งานวิจัย
⭐⭐⭐⭐⭐

วันอังคารที่ 8 เมษายน พ.ศ. 2568

Python Mutable arguments

     อันนี้เป็นส่วนเสริมของบทความเรื่อง Python PEP 8 นะครับ 

    จะมีข้อนึงบอกว่า ควรหลีกเลี่ยงการใช้ Mutable Default Arguments งั้นเราก็ต้องรู้จัก Mutable Arguments ก่อน แล้วมันสร้างปัญหาให้กับเราได้ยังไง แต่ก่อนจะไปรู้จัก Mutable Arguments มารู้จักกับ Immutable Arguments กันก่อนเพื่อให้เข้าใจง่ายขึ้น


Immutable Arguments หรือ Immutable Data Types คือ 

  • ประเภทของข้อมูลที่ไม่สามารถเปลี่ยนค่าได้ หลังจากที่ถูกสร้างขึ้น
  • หากต้องการเปลี่ยนค่า Python จะสร้าง object ใหม่ แล้วให้ตัวแปรอ้างอิงไปที่ object ใหม่นั้นแทน

  • การเปลี่ยนค่าของ Immutable Object จะไม่ส่งผลกระทบต่อ object เดิม

  • ตัวอย่างของตัวแปรประเภท Immutable ก็จะมี int, float, bool, str, tuple 

    ถ้าบอกอย่างนี้ อาจจะงง เพราะเวลาเราใช้ตัวแปรพวกนี้ เราก็เปลี่ยนค่าได้หนิ ซึ่งในมุมของคนเขียนใช่ครับ ตามนั้นเลย แต่ในมุมของการทำงานของภาษา มันจะไม่ได้เป็นการเปลี่ยนค่า แต่จะเป็นการจอง memory ใหม่ เก็บค่าใหม่เข้าไป แล้วตัวแปรนั้นก็เปลี่ยนมาชี้ที่ memory address ใหม่ 

    ถ้าอธิบายให้เข้าใจง่ายๆก็เหมือนตัวแปรเป็นชื่อกล่อง ซึ่งกล่องนี้เก็บของอยู่คือค่าที่เรากำหนดไว้ เวลาเราเปลี่ยนค่า มันไม่ได้เป็นการใช้กล่องเดิมแล้วเปลี่ยนของทีอยู่ข้างใน แต่เป็นการทิ้งกล่องเดิมไปเลย แล้วเอากล่องใหม่มาติดป้ายชื่ออันเดิม ไม่รู้ว่าจะช่วยให้เข้าใจหรือจะงงกว่าเดิมนะอันนี้ แต่ก็ประมาณนี้

   ซึ่งตัวแปรประเภทนี้ เวลาเขียนมันก็จะเป็นอิสระจากกันเลย ไม่ว่าจะเป็นการส่งค่าเข้าไปใน function หรือเอาตัวแปรนี้ไปกำหนดค่าให้ตัวแปรอื่น มันก็จะแยกกันทำงานอย่างชัดเจน ของใครของมัน

ยกตัวอย่างเช่น
เราเขียนโปรแกรม กำหนดค่าตัวแปร

a = 2
b = a

แล้วอยู่ดีๆเราเกิดไปเปลี่ยนค่าตัวแปร b เช่น

b += 5

หลังจากนั้นเราลองเช็กค่าของตัวแปรทั้งสองดู

a =2
b = 7

ซึ่งอย่างนี้ก็ปกติอย่างที่ควรจะเป็น เพราะเป็นตัวแปรคนละตัวกัน แต่ พวก Mutable ไม่ได้เป็นแบบนี้


Mutable Arguments หรือ Mutable Data Types คือ 
  • ประเภทของข้อมูลที่สามารถเปลี่ยนแปลงค่าได้ โดยที่ object ยังคงเป็นตัวเดิม
  • สามารถเปลี่ยนแปลงค่าได้โดย ไม่ต้องสร้าง object ใหม่

  • การเปลี่ยนค่า Mutable Object จะมีผลต่อ object เดิม และส่งผลให้ค่าที่อ้างอิงไปยัง object นั้นเปลี่ยนไปด้วย

  • ตัวอย่างตัวแปลประเภท Mutable คือ list, dict, set แล้วก็ bytearray

เวลาเราเอาตัวแปรประเภท Mutable นี้ไปกำหนดค่าให้ตัวแปรอื่น มันไม่ได้เป็นการสร้าง object ใหม่ หมายถึงจอง memory address ใหม่น่ะ แต่มันเป็นการบอกให้ตัวแปรใหม่ ชี้ไปที่ memory address เดียวกันเลย เพราะฉนัน เวลาแก้ตัวที่ 2 ตัวแรกเปลี่ยนด้วย ลองดู

a = [1,2,3]
b = a
b[0] = 5

แล้วมาเช็กค่า จะได้เป็น
a = [5, 2, 3]
b = [5, 2, 3]

ผมก็ไม่เข้าใจ มันจะมีตัวแปรแบบนี้เพื่อ????????????

เค้าว่ามันมีประโยชน์นะ คือ

ใช้เมื่อ...
ต้องการจำสถานะในฟังก์ชันโดยไม่สร้าง classเช่น counter, logger, cache
ต้องการหลีกเลี่ยง global variablesทำให้โค้ดเป็น modular มากขึ้น
ต้องการ performance optimizationเช่น การ cache ค่าในการคำนวณซ้ำ ๆ

แต่ผมไม่ได้ใช้อ่านะ มาดูวิธีป้องกันดีกว่า

📌กลุ่มแรกเป็นปัญหาที่เกี่ยวกับการใช้ Mutable Arguments กับ ตัวแปรทั่วไป

    หากข้อมูลเราไม่ซับซ้อน เป็นข้อมูล level เดียว 

สามารถกำหนดค่าให้กับตัวแปรโดยใช้คำสั่งสร้าง object นั้นๆขึ้นมาใหม่ เช่น list() dict() set()

b = set(a)
b = list(a)
b = dict(a)

หรือใช้ .copy ก็ได้ ซึ่งวิธีนี้ใช้ได้กับตัวแปรทั้ง 3 แบบเลย ทั้ง list set dict เลย

b = a.copy()

ในกรณีที่เป็น list สามารถทำแบบนี้ได้อีกวิธี
b = a[:] จะเป็นการ copy ทั้งหมดมา

    แต่หากข้อมูลเราซับซ้อน มีหลายชั้น เช่น list ซ้อน list , dict ซ้อน dict หรือ เป็น json เลย ให้ใช้วิธีนี้

import copy

b = copy.deepcopy(a)

สรุปกลุ่มแรกนะครับ

Shallow Copy (ได้เฉพาะ object ชั้นนอก)

ประเภทวิธี copy ที่ใช้ได้
lista.copy() / list(a) / a[:]
dicta.copy() / dict(a)
seta.copy() / set(a)

 Deep Copy (copy ทุกชั้นแบบแยกขาด)

ต้องใช้:

import copy deep = copy.deepcopy(a)


📌กลุ่ม 2 เป็นปัญหาที่เกี่ยวกับการใช้ Mutable Arguments กับ function

 กรณีที่ 1: ใช้ Mutable Default Argument ในฟังก์ชัน


def add_item(item, item_list=[]): # ❌ ค่าถูกแชร์ระหว่างการเรียกใช้ item_list.append(item) return item_list print(add_item("apple")) # ✅ ['apple'] print(add_item("banana")) # ❌ ['apple', 'banana'] (ค่าถูกแชร์) print(add_item("cherry")) # ❌ ['apple', 'banana', 'cherry']

💡 ปัญหา: item_list ถูกแชร์กันข้ามการเรียกใช้ฟังก์ชัน ทำให้ค่าถูกเก็บสะสมเรื่อย ๆ

วิธีแก้ไข


def add_item(item, item_list=None): # ✅ ใช้ None แทน if item_list is None: item_list = [] # ✅ สร้าง list ใหม่ในแต่ละครั้ง item_list.append(item) return item_list print(add_item("apple")) # ✅ ['apple'] print(add_item("banana")) # ✅ ['banana'] print(add_item("cherry")) # ✅ ['cherry']

 กรณีที่ 2: ส่ง Mutable Object เข้าไปในฟังก์ชัน


def modify_list(my_list): my_list.append("new item") data = ["old item"] modify_list(data) print(data) # ✅ ['old item', 'new item'] (ค่าเปลี่ยนแปลง!)

💡 สังเกต: data ถูกแก้ไขโดยตรงเพราะ list เป็น Mutable Object

ถ้าต้องการป้องกันการเปลี่ยนแปลงโดยไม่ตั้งใจ ให้ใช้ .copy()


def modify_list(my_list): new_list = my_list.copy() # ✅ ใช้สำเนาแทน new_list.append("new item") return new_list data = ["old item"] new_data = modify_list(data) print(data) # ✅ ['old item'] (ค่าเดิมไม่เปลี่ยน) print(new_data) # ✅ ['old item', 'new item']


กลุ่มแรกเป็นปัญหาที่เกี่ยวกับการใช้ Mutable Arguments กับ ตัวแปรทั่วไป

Python map(), filter() vs comprehensions และ lambda

    หลังจากที่เรารู้จัก List Comprehensions & Dictionary Comprehensions  ต่อมาเรามารู้จักอีก 2 คำสั่งที่ให้ผลคล้ายๆกัน คือ map และ filter 


เอาจริงๆก็ผมไม่ได้ใช้ 2 function นี้นะ แต่เค้าบอกให้เรียนก็เรียนๆไป คิดว่าน่าจะได้ใช้แหละ


มาดูความหมายและวิธีใช้งานกัน

map(function, iterable) ใช้สำหรับแปลงข้อมูลใน iterable ทุกตัว โดยใช้ function

filter(function, iterable) ใช้คัดกรองเฉพาะค่าที่ function คืนค่าเป็น True

 iterable หมายถึงตัวแปรหรือ object ที่สามารถ loop ได้นะครับ


map  

    การใช้ map จะต้องมี function กับ iterable ไว้ loop นะครับ โดยที่การทำงานจะประมาณว่า loop ข้อมูล แล้วแปลงค่าผ่าน function แล้วเอาค่าที่ return จาก function นั้นมาแทนที่

เริ่มจากเราต้องมี function ก่อน เพื่อให้ map ใช้ในการแปลงค่า ในตัวอย่างนี้ ให้เป็น function หาพื้นที่สี่เหลี่ยมจตุรัส เอาจริงๆก็ function ยกกำลัง 2 แหละ

ต่อมาเราต้องมี ข้อมูลอะไรสักอย่างที่มัน loop ได้ ตัวอย่างนี้เรามี list ความยาวด้านอยู่

list_length = [1,2,3,4]

def square_area(length: int) -> int:
    return length*length

list_square_area = list(map(square_area,list_length))


ไปลองมา เหมือนต้องใส่ list ครอบด้วย เพราะ function map จะ return มาเป็น object map

ครับ แล้วเราก็ได้ function การแปลงค่ามา ซึ่งถ้าเอาไปเทียบกับ list comprehension มันก็จะประมาณนี้

list_length = [1,2,3,4]
list_square_area = [_*_ for _ in list_lange]

# or
list_length = [1,2,3,4]
def square_area(length: int) -> int:
    return length*length

list_square_area = [square_area(_) for _ in list_length]


หรือถ้าเป็นแบบเต็มๆก็

list_length = [1,2,3,4]
list_square_area = []

for length in list_length:
    square_area = length*length
    list_square_area.append(square_area)

# or
list_length = [1,2,3,4]
list_square_area = []

def square_area(length: int) -> int:
    return length*length

for length in list_length:
    area = square_area(length)
    list_square_area.append(area)


ก็จะประมาณนี้แหละครับ


filter

    ต่อมาเป็นคิวของคำสั่ง filter  ตัวนี้ทำงานคล้ายกับคำสั่ง map ครับ แต่เปลี่ยนจาก เอาค่าที่ได้จาก function มา replace เป็นใช้ function ในการเช็กว่าค่านี้จะเอาป่าว ถ้า function return เป็น True ก็จะเก็บไว้ แต่ถ้าเป็น False ก็ทิ้งไป ตัวนี้ไม่เอา จบเท่านี้เลยครับ


จากที่อ่านผมก็มีคำถามว่า แล้วจะมี map กับ filter ไว้ทำไม คำตอบที่ผมได้มาคือ

    map() กับ filter() ว่า “ไม่จำเป็นต้องสร้าง list ทันที” เพราะทั้งคู่จะคืนค่าเป็น iterator แบบ lazy evaluation นั่นแปลว่า มันจะยังไม่คำนวณอะไรเลย จนกว่าจะมีคนไปดึงข้อมูลออกมาใช้จริงๆ เช่นแปลงเป็น list หรือเข้าไป loop อ่านค่าจากตัวมัน และ ถ้ามีข้อมูลเป็นล้านรายการ มันจะประหยัด memory มาก เพราะไม่โหลดทั้งหมดเข้าหน่วยความจำ


map() กับ filter() นิยมใช้ร่วมกับ lambda 

lambda คืออะไร?

lambda คือ ฟังก์ชันแบบย่อ (anonymous function)
ไม่มีชื่อเหมือนฟังก์ชันปกติ (def) และเขียนได้ในบรรทัดเดียว

รูปแบบการเขียน:


lambda arguments: expression

  • arguments → ตัวแปรรับค่า

  • expression → นิพจน์ที่คืนค่ากลับ (return โดยอัตโนมัติ)

  • ไม่มีคำว่า return


ถ้าให้อธิบายแบบภาษาที่ผมเข้าใจก็ประมาณว่า
ใช้คำสั่ง lambda แล้วก็ตามด้วยชื่อตัวแปร อะไรก็ได้ ใส่ๆไปเลย คั่นด้วย : ทีนึง 
จากนั้นก็ใส่ expression หรือ process ที่อยากให้ function ทำงาน 
ซึ่งข้อจำกัดคือ ใส่ได้แค่ expression เดียว ต่อให้ใส่ได้ก็ไม่ควร อ่านยาก



ตัวอย่าง map ด้านบน

list_length = [1,2,3,4]

def square_area(length: int) -> int:
    return length*length

list_square_area = list(map(square_area,list_length))


นำมาเขียนใหม่เป็น lambda ได้แบบนี้

list_length = [1,2,3,4]

list_square_area = list(map(lambda length: length*length, list_length))



หน้าแบบที่เค้านิยมกันก็

list_length = [1,2,3,4]

list_square_area = list(map(lambda i: i ** 2, list_length))




วันอาทิตย์ที่ 6 เมษายน พ.ศ. 2568

Python List Comprehensions & Dictionary Comprehensions

 อันนี้ใช้บ่อยอยู่นะ ก๊อบมาลงละกัน ให้เนื้อหามันครบๆ


    List Comprehensions คือ วิธีสร้าง list แบบกระชับ โดยใช้ไวยากรณ์ที่สั้นกว่า for loop

เอาแบบง่ายๆก่อนนะ ยกตัวอย่างเป็นตัวเลขก่อนละกัน 

สร้าง list 1-10 ปกติก็แค่ range(1,11) แหละนะ แต่มันสำเร็จรูปไป

เอามาเขียนเป็น for ละกัน

ตัวอย่าง code

range_10 = []

for _ in range(1,11):

    range_10.append(_)

ผมใช้ _ นะครับ เค้าบอกว่าตัวแปรแบบใช้แล้วทิ้ง ก็แนะนำให้ใช้ _ หรือใครสะดวกใช้ i ก็ได้ ไม่ติด

append เป็นคำสั่งที่ใช้กับตัวแปรประเภท list ใน python (ภาษาอื่นจะเรียก array) ใช้สำหรับเพิ่มค่าต่อท้ายเข้าไปใน list


พอมาเขียนเป็น List Comprehensions ก็จะเหลือแค่นี้

ตัวอย่าง code

range_10 = [_ for _ in range(1,11)]


ใช้ _ อาจมองไม่ชัด จัด i มาให้อีกอัน

range_10 = [i for i in range(1,11)]


จบแล้วบันทัดเดียว ให้มองแยกเป็น 2 อันนะครับ คือ

  • ตัวสีน้ำเงิน ก็คือรูปแบบ for ปกติ
  • ตัวสีแดง คือค่าที่จะถูกเพิ่มเข้าไปใน list เหมือนตอนที่ใช้คำสั่ง append

จากตัวอย่างนี้เหมือนจะไม่ค่อยมีประโยชน์เลยเนาะ ส่วนใหญ่ผมใช้ loop get ค่าออกมาจาก object หรือพวก query (ถ้าใครมีวิธีดีๆแนะนำผมได้นะ)

เราสามารถเพิ่ม if หรือ if else ไปใน list comprehensoin ได้ด้วยนะ เช่น


ตัวอย่าง code List Comprehensions + if

range_10 = [_ for _ in range(1,11) if _%2 == 0]

ก็จะเหมือนเอาเฉพาะเลขคู่มา


ตัวอย่าง code List Comprehensions + if else

a = ['1', '2', '3', 'a', 'b', 'c']

result = [int(_) if _.isdigit() else 0 for _ in a]

อันนี้ loop เช็กว่าค่าแต่ละอันเป็นจำนวนเต็มหรือเปล่า ถ้าใช่ให้แปลงเป็น int ถ้าไม่ใช่ ให้ fix เป็น 0


    จริงๆมันก็คือ loop ธรรมดานี่แหละ แค่เขียนย่อให้มันสั้นลง ควรใช้กับ loop ง่ายๆแต่ยังจำเป็นต้อง Loop ถ้าซับซ้อนไปเขียนแบบเต็มดีกว่า อ่านง่ายแก้ง่ายกว่า เคยลองเลย ปวดหัวตอนกลับมาแก้


    Dict Comprehensions อันนี้ก็แทบเหมือนกันเลย ต่างกันแค่ผลที่ได้จะเป็น dict กับวิธีเขียนนิดหน่อย

ถ้าเป็น list จะใช้ [ _ for _ in list_data ]

ถ้าเป็น dict จะเปลี่ยนเป็น {key:value for _ in list_data}

ซึ่งเราจะเอา _ ไปใส่แทน key หรือ value ก็ได้ 

    ตัวอย่างที่ผมใช้งานจริงคือผมเขียนโปรแกรมไปอ่านค่า config จากอุปกรณ์ แล้วใช้ key เป็นชื่อ แล้วก็ value เป็นค่าที่ได้

ตัวอย่าง code

config = getconfig(ip='192.168.1.1', user='admin', passwd='password')

json_config = {_.name:_.value for _ in config}


    ข้อดีของ dict คือเราใช้ชื่อเป็น key เลย ง่ายต่อการจำ และเข้าใจครับ แต่ก็ใช้ mem เยอะกว่านิดหน่อย ซึ่งข้อมูลผมไม่ได้เยอะขนาดนั้นเลยไม่ซีเรียส


ตัว Dict Comprehensions ก็ใช้ if else ได้เหมือนกับ ListComprehensions เลยนะ



สรุปนะครับ 

ข้อดี สั้น ถ้าเราจำเป็นต้อง loop เพื่อสร้าง list หรือ dict ก็เหมาะเลย

แต่ไม่ควรใช้กับพวกที่เงื่อนไขเยอะๆหรือมี process ที่ซับซ้อน ไม่งั้นตอนกลับมาแก้ กุมขมับแน่นอน


Python Docstrings - VS-Code

Docstrings เป็นตัวช่วยที่สำคัญตัวนึงเลยสำหรับผม อันนี้เคยไปดูมาจาก Youtube ช่องนึงมา ใครขี้เกียจอ่านก็กด Link ไปดูได้เลยครับ


เริ่มจาก Docstrings  คืออะไร

     ถ้าให้ผมอธิบายมันก็จะเป็นเหมือน comment หลายบันทัดแหละ แต่ผมจะไม่ได้ใช้งานแบบนั้นเท่าไหร่ เพราะผมใช้ # ในการ comment หมดเลย แล้วด ใช้ Docstrings ในการอธิบายการทำงานของ function ที่เขียนขึ้นมา โดยโครงสร้างก็จะประมาณใช้ double code 3 ตัวเปิด แล้วก็อีก 3 ปิดท้าย

"""อธิบายการทำงานของ function

Args input มีอะไรบ้าง type เป็นอะไร พร้อมคำอธิบายนิดหน่อย

Return มี type เป็นอะไร แล้วก็คำอธิบายนิดหน่อย

"""

ผมใช้ vs-code เป็นหลัก ก็จะมี extendtion ตัวนึงที่ทำงานร่วมกับ Docstrings คือตัวนี้




ไปที่กล้องสี่เหลี่ยมแตกๆด้านซ้ายนะครับ มันคือ extendtion ของ vs-code แล้ว search docstring ได้เลย

ติดตั้งไอตัวเขียวๆเหมือนในรูปนี้แหละ


เวลาใช้งานก็ เขียน function ให้เสร็จก่อน ไปที่ด้านบน ใต้ def แล้วพิมพ์ double code 3 ตัว มันก็จะขึ้นอันนี้มาให้

แล้วเราก็กด enter ได้เลย

หลังจากนั้นมันก็จะขึ้นมาประมาณนี้



คือมีตัวแปรให้หมดเลย เราก็ไปใส่คำอธิบายกับ type เอา ถ้าเรายังเขียนไม่เสร็จ มันก็จะไม่มีพวกนี้ขึ้นมาให้นะ

หลังจากแก้เสร็จของผมก็จะหน้าตาประมาณนี้


เวลาเรียกใช้ ก็จะมีคำอธิบายตามที่เขียนไว้ให้เลย ตัวอย่างก็ประมาณนี้



ก็จะช่วยให้เราเขียนโปรแกรมได้ง่ายขึ้น ไม่ต้องจำว่ามี input อะไรบ้าง return gxHovtwi

ตัวอย่างนี้เป็น code ส่ง alert telegram นะครับ จริงๆเค้า limit ไว้ที่ 4026 ตัวอักษรมั้ง ประมาณนี้แหละ แต่ผมว่า 3500 ก็เยอะละ ถ้าจะยาวขนาดนี้ไปอ่าน log เอาเถอะ

Python Type Hinting

 หลังจากเรียน PEP8 มาแล้ว หัวข้อต่อมาเป็น Type Hinting


Type Hinting คืออะไร?

    Type hinting คือการระบุประเภทของตัวแปรที่ใช้ในฟังก์ชันหรือคลาสใน Python เพื่อช่วยให้โค้ดอ่านง่ายขึ้น และช่วยลดข้อผิดพลาดที่อาจเกิดจากการใช้ตัวแปรผิดประเภท


    ใน python ไม่ได้บังคับให้ใส่ type hinting คือโปรแกรมก็ยังทำงานได้ปกติแม้จะไม่มี type hinting

ใน Python เริ่มรองรับอย่างเป็นทางการตั้งแต่ Python 3.5 เป็นต้นไป ผ่านมาตรฐานใน PEP 484


Type Hinting ช่วยทำให้โค้ดอ่านง่ายขึ้นและลดข้อผิดพลาดอย่างไร?

  1. ช่วยให้โค้ดอ่านง่ายขึ้น

    • คนอื่นที่อ่านโค้ดจะเข้าใจได้ง่ายขึ้นว่าแต่ละตัวแปรควรเป็นประเภทไหน

    • ลดความสับสนโดยเฉพาะเมื่อโค้ดมีความซับซ้อน

  2. ลดข้อผิดพลาดจากการใช้ตัวแปรผิดประเภท

    • ตัวแปรที่ใช้ผิดประเภทอาจทำให้เกิดข้อผิดพลาดในระหว่างรันไทม์ (Runtime error)

  3. ช่วยให้ออโต้คอมพลีตและเครื่องมือพัฒนาโค้ดทำงานได้ดีขึ้น

    • IDE อย่าง PyCharm หรือ VS Code สามารถให้คำแนะนำและตรวจสอบโค้ดอัตโนมัติได้ดีขึ้นเมื่อมี Type Hinting


    ถ้าให้อธิบายง่ายๆก็คือ เหมือน comment บอกหน่อยว่าตัวแปรนี้ใช้ type ไหนบ้าง ถ้ากลับมาอ่านก็จะได้นึกออกง่ายขึ้น แต่ที่ไม่เขียนเป็น comment เพราะ มี format เฉพาะให้ IDE มาอ่านกับเราด้วย มีเครื่องมือช่วยตรวจสอบด้วย (mypy)

    ที่บอกว่าเหมือน comment เพราะว่า ต่อให้ใส่ไปแล้ว แต่เราใช้ผิด type จากที่เขียนไว้ โปรแกรมเราก็ยังคงทำงานได้ปกติ ไม่มี error อะไร

การเขียน type hinting แบบต่างๆ มีดังนี้

 1. การใช้ Type Hinting กับตัวแปร (Variable Annotation)

Python อนุญาตให้กำหนดชนิดของตัวแปรโดยใช้ : ตามด้วยชนิดของข้อมูล


x: int = 10 y: float = 3.14 name: str = "Alice" is_active: bool = True

📌 ข้อดี

  • ช่วยให้คนอ่านโค้ดเข้าใจได้ทันทีว่าตัวแปรนี้ควรเก็บค่าอะไร

  • IDE สามารถให้คำแนะนำที่แม่นยำขึ้น


🔹 2. การใช้ Type Hinting กับฟังก์ชัน (Function Annotations)

สามารถกำหนดชนิดของพารามิเตอร์และค่าที่ฟังก์ชันคืนค่าได้


def add(x: int, y: int) -> int: return x + y

📌 โค้ดด้านบนหมายความว่า

  • x และ y ต้องเป็น int

  • ฟังก์ชันต้องคืนค่าเป็น int

🔹 รองรับหลายชนิดของข้อมูล (Multiple Types)


from typing import Union def process(value: Union[int, float]) -> float: return value * 2.5 print(process(10)) # 25.0 print(process(3.2)) # 8.0
  • ใช้ Union กำหนดให้พารามิเตอร์รับได้มากกว่าหนึ่งชนิด

หรือหากใช้งาน python 3.10 ขึ้นไป สามารถใช้ | คั่นระหว่าง type ได้

from typing import Union def process(value: int | float) -> float: return value * 2.5 print(process(10)) # 25.0 print(process(3.2)) # 8.0



🔹 3. การใช้ Optional สำหรับค่าที่อาจเป็น None

บางฟังก์ชันอาจต้องรับค่าหรือคืนค่า None ได้ เราใช้ Optional (จริง ๆ ก็คือ Union[type, None])


from typing import Optional def get_username(user_id: int) -> Optional[str]: users = {1: "Alice", 2: "Bob"} return users.get(user_id) # อาจคืนค่า str หรือ None print(get_username(1)) # "Alice" print(get_username(3)) # None

📌 ควรใช้ Optional เมื่อค่าที่คืนกลับอาจเป็น None

🔹 4. การใช้ Type Hinting กับ List, Tuple, Set, Dict

หากต้องการระบุชนิดของข้อมูลในโครงสร้างข้อมูลหลายตัว เราใช้ List[type], Dict[key, value] เป็นต้น


from typing import List, Tuple, Set, Dict numbers: List[int] = [1, 2, 3, 4] coordinates: Tuple[float, float] = (13.5, 100.7) unique_values: Set[str] = {"apple", "banana", "cherry"} user_info: Dict[str, int] = {"Alice": 25, "Bob": 30}

📌 โครงสร้างข้อมูลหลัก ๆ ที่ใช้บ่อย

  • List[type] → ใช้กับลิสต์ของค่าชนิดเดียวกัน

  • Tuple[type, ...] → ใช้กับทูเพิลที่มีขนาดแน่นอน

  • Set[type] → ใช้กับเซ็ตที่มีค่าชนิดเดียวกัน

  • Dict[key_type, value_type] → ใช้กับดิกชันนารีที่มีคีย์และค่าที่กำหนดชนิดได้


🔹 5. การใช้ Any เมื่อไม่ต้องการกำหนดชนิดแน่นอน

หากตัวแปรสามารถเป็นอะไรก็ได้ สามารถใช้ Any


from typing import Any def process(value: Any) -> Any: return value x: Any = 42 y: Any = "Hello"

📌 ข้อเสียของ Any

  • ทำให้ Type Checking ไร้ประโยชน์

  • ควรใช้เมื่อจำเป็นเท่านั้น เช่น ฟังก์ชันที่ต้องรองรับทุกประเภทข้อมูล

ตรงนี้คือถ้าใส่ any ก็เหมือนเราไม่ได้เขียน type hinting แหละ คิดว่าน่าจะมีเผื่อไว้ในบางกรณี

🔹 6. การใช้ Callable สำหรับฟังก์ชันเป็นพารามิเตอร์

หากต้องการรับฟังก์ชันเป็นอาร์กิวเมนต์ สามารถใช้ Callable


from typing import Callable def apply_function(x: int, func: Callable[[int], int]) -> int: return func(x) def square(n: int) -> int: return n * n print(apply_function(5, square)) # 25

📌 Callable[[พารามิเตอร์], ReturnType]

  • Callable[[int], int] → รับ int คืนค่า int


🔹 7. การใช้ TypeVar สำหรับ Generic Type

หากต้องการให้ฟังก์ชันรองรับได้หลายชนิดของข้อมูล ใช้ TypeVar


from typing import TypeVar T = TypeVar('T') def identity(value: T) -> T: return value print(identity(10)) # 10 print(identity("Hello")) # "Hello"

📌 TypeVar เหมาะสำหรับการสร้างโค้ด Generic ที่รองรับหลายชนิดข้อมูล


🔹 8. การใช้ TypedDict สำหรับดิกชันนารีที่มีโครงสร้างแน่นอน

ใช้ TypedDict กำหนดดิกชันนารีที่มีคีย์และชนิดข้อมูลที่แน่นอน


from typing import TypedDict class User(TypedDict): name: str age: int user: User = {"name": "Alice", "age": 30}

📌 เหมาะกับการกำหนดโครงสร้างของ JSON หรือข้อมูลที่แน่นอน


🔹 9. การใช้ Literal สำหรับค่าที่แน่นอน

ใช้ Literal กำหนดว่าตัวแปรสามารถมีค่าอะไรได้บ้าง


from typing import Literal def set_status(status: Literal["active", "inactive"]) -> str: return f"Status set to {status}" print(set_status("active")) # ✅ ถูกต้อง # print(set_status("unknown")) # ❌ mypy จะเตือนว่าไม่ถูกต้อง

📌 **เหมาะสำหรับค่าที่เป็นตัวเลือกคงที่ เช่น "GET" | "POST" | "PUT"


สำหรับหัวข้อนี้ ผมยังไม่ค่อยเข้าใน TypeVar เท่าไหร่ ไว้อ่านเพิ่มจนเข้าใจแล้วจะมาอธิบายเพิ่มนะครับ

ช่วงนี้ก็ใช้ตัวอื่นไปก่อน