Tutorial

Python กับ None ร่างจริงที่ไม่ใช่ร่างทรง (ซะที่ไหน !)

By Arnon Puitrakul - 12 พฤศจิกายน 2021

Python กับ None ร่างจริงที่ไม่ใช่ร่างทรง (ซะที่ไหน !)

เมื่อไม่กี่วันก่อนนั่งคิดขำ ๆ กับเพื่อน เรามาลองแกล้ง Python กันมั้ย เราสงสัยกันเรื่องของ None เป็นของที่เราใช้กันบ่อยมาก ๆ แต่เรามักจะมองว่า เออ มันก็เอาไว้แค่เป็นค่าส่งกลับเวลามันไม่มีของที่เราตามหาอะไรแบบนั้น ทำให้เราก็อาจจะเอามาใช้เป็น Flag ในการเช็คอีกทอดก็มี ทำให้มันเกิดเป็นบทความในวันนี้ เพราะเราจะบอกว่า None ที่ไม่มี จริง ๆ แล้วมันมีนะ

จริง ๆ None มีตัวตน

>>> type(None)
<class 'NoneType'>

หลาย ๆ คนอาจจะคิดว่า None มันเป็นแค่ Type พิเศษที่ไม่ได้มีตัวตนอะไร แต่เราจะบอกว่า จริง ๆ แล้ว None เนี่ย มันมีตัวตนจริง ๆ นะ เป็น Object ด้วย ถ้าไม่เชื่อลองรัน Code ด้านบนดู มันก็จะได้ออกมาบอกว่า None มันเป็น Object จาก NoneType Class ทำให้จริง ๆ แล้ว None มีตัวตนจริง ๆ เป็น Object ปกติที่เราสร้างนี่แหละ

>>> hex(id(None))
'0x104d18398'

หนักกว่านั้นอีก ถ้าเราลองดู Memory Address ของ None ที่เหมือนจะไม่มี มันมีอยู่ใน Memory จริง ๆ เพื่อการสังเกตที่ลึกเข้าไปอีก เราลองทำแบบด้านล่างดู

>>> a = type(None)()
>>> hex(id(a))
'0x104d18398'

เราลองสร้าง Object  a ให้เป็น NoneType ดู และเราลองเอา Memory Address ของ a ออกมาดู เราจะเห็นได้เลยว่า มันตรงกับ None เฉย ๆ เลย ซึ่งถ้าเราเข้าไปอ่านลึก ๆ เราจะทราบว่าจริง ๆ แล้ว None เป็น Singleton เลยนะ ทำให้เราสามารถใช้ None ในการเปรียบเทียบกับ None ได้ทั้งโปรแกรมเลย โดยที่มั่นใจได้เลยว่า None จะเท่ากับ None จริง ๆ ในทุก ๆ เคส เพราะเราเอาของชิ้นเดียวกันเป๊ะ มาเทียบกันเลย ไม่ว่า มันจะมี Alias เป็นอะไรก็ตาม ลองเข้าไปอ่านเพิ่มเติมได้ที่ Python Document

Check None ยังไงให้รอด

def checker (a : int) -> Union[None, int] :
    if a > 0 :
        return a

โดยทั่วไปแล้ว เมื่อเราสร้าง Function ขึ้นมา และ เราไม่ได้ทำการเรียก keyword return หรือ อาจจะเดินไปไม่ถึง ตัว Python มันก็จะใช้ Default เป็น None เลย ทำให้เราสามารถทำมันเป็น Flag ในการเช็คได้ว่าถ้ามันมีอะไรผิดพลาด แทนที่มันจะ return อะไรกลับมา ถ้ามันกลับมาเป็น None แปลว่าแตกก็ได้เหมือนกัน

>> checker(0)
None

จากตัวอย่าง เราเขียน Function ไว้เช็คแค่ว่าถ้า a มันมากกว่า 0 ให้มันเอา a กลับมาเลย แต่ถ้าไม่ใช่ละ เราไม่ได้ให้มันทำอะไรต่อแล้ว ถ้าเราคิดแบบนี้ มันไม่น่าจะเอาอะไรกลับมาเลย แต่ถ้าเราลองเอาตัวแปรมารับ หรือลองใน Interactive Shell เลย เราจะเห็นว่า จริง ๆ แล้วมันไม่ได้เป็นอย่างที่เราคิด มันได้กลับมาเป็น None เฉยเลย ทำให้สามารถทำอย่างที่เราบอกได้ว่า อาจจะใช้เป็น Flag ในการเช็คได้

class MyClass :
    def __eq__ (self, other) :
        return True

test_obj = MyClass()

print (test_obj == None)
print (test_obj is None)

ทีนี้ ปัญหามันจะเริ่มเกิดขึ้นเมื่อเราพยายามที่จะเปรียบเทียบ None เพราะคนทั่ว ๆ ไปคิดว่า None มันเป็นแค่ Keyword แต่จากที่เราคุยกันมา มันไม่ได้เป็นแบบนั้นเลย เราลองสร้าง Class และ Object ง่าย ๆ เลย โดยที่เรา Override Dunder Function eq ให้มันเอา True กลับไปเสมอ ดังนั้น ใน Print ด้านล่างควรจะเกิดอะไรขึ้น

True
False

ผลที่ได้จะเป็นแบบด้านบนเลย เห้ย ทั้ง ๆ ที่เราเช็คเหมือนกันแท้ ๆ ทำไมแค่เปลี่ยนวิธีมันได้ไม่เหมือนกันละ นั่นเป็นเพราะความแตกต่างของ Identity และ Equality Operator นั่นเองไว้ในโอกาสหน้า ๆ เราจะมาเล่าให้อ่านกัน แต่จากตรงนี้เราจะเห็นว่า ถ้าเราต้องการที่จะเช็คว่า อะไรสักอย่างเป็น None การใช้ Equality Operator อาจจะไม่ทำให้เราได้คำตอบที่ถูกต้องทุกอย่าง อย่างในเคสนี้เราสามารถ Override ให้มันออกจริงตลอดได้เลย แต่ใน Identity เราทำแบบนั้นไม่ได้ซะทีเดียว ทำให้เราแนะนำว่า เวลาเราจะเช็ค None ให้เราใช้ Identity Operator จะปลอดภัยกว่าเยอะมาก

สรุป

วันนี้เรามาเล่าเรื่องของ Object ตัวนึงที่เราเจอมันบ่อย ๆ แต่เราก็ไม่ได้คิดว่ามันเป็น Object ด้วยซ้ำ นั่นก็คือ None Object ที่ถูกสร้างมาจาก Class ที่ชื่อว่า NoneType นั่นเอง โดยมันมรสมบัติหลาย ๆ อย่างที่ช่วยทำให้เราเอามาใช้งานเป็นพวก Flag ในการเช็ค รวมไปถึงเป็น Default Value ในหลาย ๆ เวลา เช่น Return Function Value ถ้าเราไม่มี Return มันก็จะเอากลับไปเป็น None ให้เราเองเลย แต่เวลาใช้งานจริง ๆ แนะนำให้ระวังเรื่องของวิธีการเช็คให้ดี ๆ เพราะอาจจะเจอเคสแปลก ๆ ที่ทำให้หลุดได้ แนะนำให้ใช้ Identity Operator ในการเช็คโอกาสรอดจะเยอะกว่า

Read Next...

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

จัดการข้อมูลบน Pandas ยังไงให้เร็ว 1000x ด้วย Vectorisation

เวลาเราทำงานกับข้อมูลอย่าง Pandas DataFrame หนึ่งในงานที่เราเขียนลงไปให้มันทำคือ การ Apply Function เข้าไป ถ้าข้อมูลมีขนาดเล็ก มันไม่มีปัญหาเท่าไหร่ แต่ถ้าข้อมูลของเราใหญ่ มันอีกเรื่องเลย ถ้าเราจะเขียนให้เร็วที่สุด เราจะทำได้โดยวิธีใดบ้าง วันนี้เรามาดูกัน...

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

ปั่นความเร็ว Python Script เกือบ 700 เท่าด้วย JIT บน Numba

Python เป็นภาษาที่เราใช้งานกันเยอะมาก ๆ เพราะความยืดหยุ่นของมัน แต่ปัญหาของมันก็เกิดจากข้อดีของมันนี่แหละ ทำให้เมื่อเราต้องการ Performance แต่ถ้าเราจะบอกว่า เราสามารถทำได้ดีทั้งคู่เลยละ จะเป็นยังไง เราขอแนะนำ Numba ที่ใช้งาน JIT บอกเลยว่า เร็วขึ้นแบบ 700 เท่าตอนที่ทดลองกันเลย...

Humanise the Number in Python with "Humanize"

Humanise the Number in Python with "Humanize"

หลายวันก่อน เราทำงานแล้วเราต้องการทำงานกับตัวเลขเพื่อให้มันอ่านได้ง่ายขึ้น จะมานั่งเขียนเองก็เสียเวลา เลยไปนั่งหา Library มาใช้ จนไปเจอ Humanize วันนี้เลยจะเอามาเล่าให้อ่านกันว่า มันทำอะไรได้ แล้วมันล่นเวลาการทำงานของเราได้ยังไง...

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

ทำไม 0.3 + 0.6 ถึงได้ 0.8999999 กับปัญหา Floating Point Approximation

การทำงานกับตัวเลขทศนิยมบนคอมพิวเตอร์มันมีความลับซ่อนอยู่ เราอาจจะเคยเจอเคสที่ เอา 0.3 + 0.6 แล้วมันได้ 0.899 ซ้ำไปเรื่อย ๆ ไม่ได้ 0.9 เพราะคอมพิวเตอร์ไม่ได้มองระบบทศนิยมเหมือนกับคนนั่นเอง บางตัวมันไม่สามารถเก็บได้ เลยจำเป็นจะต้องประมาณเอา เราเลยเรียกว่า Floating Point Approximation...