อันนี้เป็นส่วนเสริมของบทความเรื่อง 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 ที่ใช้ได้ |
|---|
list | a.copy() / list(a) / a[:] |
dict | a.copy() / dict(a) |
set | a.copy() / set(a) |
Deep Copy (copy ทุกชั้นแบบแยกขาด)
ต้องใช้:
📌กลุ่ม 2 เป็นปัญหาที่เกี่ยวกับการใช้ Mutable Arguments กับ function
กรณีที่ 1: ใช้ Mutable Default Argument ในฟังก์ชัน
💡 ปัญหา: item_list ถูกแชร์กันข้ามการเรียกใช้ฟังก์ชัน ทำให้ค่าถูกเก็บสะสมเรื่อย ๆ
✅ วิธีแก้ไข
กรณีที่ 2: ส่ง Mutable Object เข้าไปในฟังก์ชัน
💡 สังเกต: data ถูกแก้ไขโดยตรงเพราะ list เป็น Mutable Object
✅ ถ้าต้องการป้องกันการเปลี่ยนแปลงโดยไม่ตั้งใจ ให้ใช้ .copy()
ไม่มีความคิดเห็น:
แสดงความคิดเห็น