วันอังคารที่ 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 กับ ตัวแปรทั่วไป

ไม่มีความคิดเห็น:

แสดงความคิดเห็น